- docs/llm-execution-protocol.md: 从重评流程提取的通用协议(输入/prompt/解析/校验/写入/错误处理) - specs/schemas/reassess-output.json: 重评LLM输出节标题schema - specs/schemas/trade-capture-output.json: 截图识别LLM输出schema - 可复用组件: _section_line/snapshot_strategy_history/write_holding_strategy/门禁模式
255 lines
7.0 KiB
Markdown
255 lines
7.0 KiB
Markdown
# LLM 执行协议(开发规范)
|
||
|
||
> 任何"LLM 输出 → 代码校验 → 写入 DB"的场景,必须遵循本协议。
|
||
> 本协议从重评流程(batch_reassess)提取,适用于所有 LLM→DB 交互。
|
||
|
||
---
|
||
|
||
## 一、协议总览
|
||
|
||
```
|
||
输入数据 → Prompt模板 → LLM输出 → 节标题解析 → 代码校验 → DB写入 → 确认反馈
|
||
```
|
||
|
||
**核心原则**:
|
||
1. **LLM 只输出文本**,不直接写 DB
|
||
2. **代码层做所有校验**(LLM 输出不可信)
|
||
3. **写入前快照**,失败可恢复
|
||
4. **白名单控制写入权限**(不是所有 source 都能写所有字段)
|
||
|
||
---
|
||
|
||
## 二、输入数据层
|
||
|
||
### 2.1 数据来源规范
|
||
|
||
| 数据类型 | 来源 | 层级 | 示例 |
|
||
|---|---|---|---|
|
||
| 价格 | stock_daily.close / live_prices.price | 采集层 | 收盘价/盘中价 |
|
||
| 基本面 | stock_fundamentals.pe/mcap | 加工层 | PE/市值 |
|
||
| 技术指标 | ta.full_analysis() / stock_indicators | 加工层 | 支撑阻力/MA/RSI |
|
||
| 策略参数 | holding_strategies | 业务层 | entry/stop/tp |
|
||
| 消息面 | signal_news | 采集层 | 新闻摘要 |
|
||
| 资金流 | capital_flow_cache | 采集层 | 净流入/主力 |
|
||
| 组合状态 | portfolio_summary + holdings | 业务层 | 总资产/持仓股数 |
|
||
|
||
**铁律**:使用层不得直接调 API 采集数据。所有数据必须从 DB 表读取(由采集层写入)。
|
||
|
||
### 2.2 数据收集函数
|
||
|
||
每个 LLM 任务实现一个 `collect_data(code)` 函数:
|
||
- 从各表读取数据
|
||
- 填充 `data` dict
|
||
- 返回给 build_prompt 使用
|
||
|
||
---
|
||
|
||
## 三、Prompt 模板规范
|
||
|
||
### 3.1 结构
|
||
|
||
```
|
||
角色定义(一句话)
|
||
|
||
【输入数据段1】(系统提供,非 LLM 生成)
|
||
【输入数据段2】
|
||
...
|
||
【输出格式要求】(节标题列表 + 每个字段的约束)
|
||
|
||
⚠️ 输出纪律(必须遵守)
|
||
1. 直接以【节标题】开头
|
||
2. 禁止寒暄/开场白
|
||
3. 所有【】节标题一个都不能少
|
||
```
|
||
|
||
### 3.2 节标题设计原则
|
||
|
||
- **每个字段一个节标题**(如 `【买入区间】`、`【建议止损】`)
|
||
- **节标题行首匹配**(不使用关键词包含)
|
||
- **一个字段一个值**(禁止节标题内嵌套多个值)
|
||
- **"无"显式表达**(如 `【买入区间】无`,不写 `0.0~0.0`)
|
||
|
||
### 3.3 输出纪律
|
||
|
||
prompt 末尾必须包含:
|
||
```
|
||
⚠️ 输出纪律:
|
||
1. 直接以【xxx】开头
|
||
2. 禁止寒暄
|
||
3. 所有【】节标题一个都不能少
|
||
4. 每个字段只填一个值
|
||
```
|
||
|
||
---
|
||
|
||
## 四、解析层(parse_response)
|
||
|
||
### 4.1 节标题解析器
|
||
|
||
```python
|
||
def _section_line(text, name):
|
||
"""从 LLM 输出中提取指定节标题的内容"""
|
||
for line in text.split("\n"):
|
||
if re.match(r'^\s*【' + name + r'】', line):
|
||
return line
|
||
return ""
|
||
```
|
||
|
||
**规则**:
|
||
- 行首匹配(可含空白)
|
||
- 只认精确节标题(不用 "包含xxx的第一行")
|
||
- 返回整行内容(含节标题本身)
|
||
|
||
### 4.2 解析约束
|
||
|
||
每个字段的解析必须包含:
|
||
- **类型校验**:数字字段必须是数字
|
||
- **范围校验**:如仓位必须 1-30%
|
||
- **一致性校验**:止损 < 区间下沿 < 区间上沿 < 止盈
|
||
- **空值处理**:显式 "无" → 清空;空字符串 → 跳过(保留原值)
|
||
|
||
---
|
||
|
||
## 五、校验层(save_result / 门禁)
|
||
|
||
### 5.1 门禁清单
|
||
|
||
| 门禁 | 规则 | 失败行为 |
|
||
|---|---|---|
|
||
| 空输出 | LLM 返回空/无节标题 | 拒绝写入 |
|
||
| 区间合理性 | 下沿 < 上沿 < 下沿 × 3 | 跳过区间写入 |
|
||
| 区间偏离 | 区上沿 < 现价 × 0.5 或 区下沿 > 现价 × 1.5 | 拒写并清空 |
|
||
| 止损锚定 | 止损 ≥ 区间下沿 | 用技术位修正 / 拒写 |
|
||
| 信号一致性 | 操作建议否定买入但信号是"买入" | 降级为"关注" |
|
||
| 数量正整数 | 股数必须 > 0 且为整数 | 拒绝写入 |
|
||
| 代码存在性 | 股票代码必须在 stock_daily 中存在 | 拒绝写入 |
|
||
|
||
### 5.2 门禁输出
|
||
|
||
每个门禁失败必须:
|
||
1. 打印 `⚠️` 日志(含具体原因和数值)
|
||
2. 跳过该字段写入(保留原值)或拒绝整个写入
|
||
3. **不能静默失败**
|
||
|
||
---
|
||
|
||
## 六、写入层
|
||
|
||
### 6.1 写入前操作
|
||
|
||
```python
|
||
snapshot_strategy_history(conn, code, source_trigger) # 快照
|
||
```
|
||
|
||
### 6.2 写入权限(白名单)
|
||
|
||
```python
|
||
_PARAM_WHITELIST = ('per_stock_12d', 'batch_12d', 'promote')
|
||
if source_trigger not in _PARAM_WHITELIST:
|
||
# 非白名单 source:保留 DB 当前参数值
|
||
```
|
||
|
||
### 6.3 写入后操作
|
||
|
||
- 返回确认信息给调用方
|
||
- 触发必要的联动(如快照、信号同步)
|
||
|
||
---
|
||
|
||
## 七、错误处理
|
||
|
||
### 7.1 LLM 调用失败
|
||
|
||
```
|
||
重试 → 升级模型 → 最终失败则跳过该股票
|
||
```
|
||
|
||
### 7.2 解析失败
|
||
|
||
```
|
||
节标题未找到 → 该字段跳过(保留原值)
|
||
所有字段跳过 → 拒绝写入
|
||
```
|
||
|
||
### 7.3 写入失败
|
||
|
||
```
|
||
DB 异常 → 回滚到快照 → 打印错误
|
||
```
|
||
|
||
---
|
||
|
||
## 八、可复用组件
|
||
|
||
### 8.1 `_section_line(text, name)` — 节标题解析器
|
||
- 位置:batch_reassess.py
|
||
- 用途:从 LLM 输出提取指定节内容
|
||
- 通用性:✅ 任何 LLM 输出解析都可复用
|
||
|
||
### 8.2 `snapshot_strategy_history(conn, code, source)` — 写入前快照
|
||
- 位置:mofin_db.py
|
||
- 用途:记录修改前状态,失败可恢复
|
||
- 通用性:✅ 任何 DB 写入都应先快照
|
||
|
||
### 8.3 `write_holding_strategy(..., source_trigger)` — 白名单写入
|
||
- 位置:mofin_db.py
|
||
- 用途:按 source_trigger 控制写入权限
|
||
- 通用性:✅ 所有策略卡写入都走这个函数
|
||
|
||
### 8.4 门禁模式
|
||
|
||
```python
|
||
# 区间合理性门禁
|
||
if _el > 0 and _eh > _el and _eh < _el * 3:
|
||
# 合理,写入
|
||
elif _el > 0 or _eh > 0:
|
||
print("⚠️ 解析异常,跳过")
|
||
```
|
||
|
||
通用性:✅ 任何数值字段都可套用此模式
|
||
|
||
---
|
||
|
||
## 九、新场景接入清单
|
||
|
||
新增"LLM 输出 → 写入 DB"场景时,按以下清单逐项完成:
|
||
|
||
- [ ] 实现 `collect_data(code)` — 收集输入数据
|
||
- [ ] 实现 `build_prompt(data)` — 定义输出格式(节标题)
|
||
- [ ] 定义节标题列表 — 每个字段一个标题
|
||
- [ ] 实现 `parse_response(text)` — 按节标题解析
|
||
- [ ] 实现门禁逻辑 — 校验每个字段的合理性
|
||
- [ ] 选择写入函数 — 复用已有或新建
|
||
- [ ] 定义 source_trigger — 写入权限控制
|
||
- [ ] 测试:正常输入 → 正常输出
|
||
- [ ] 测试:异常输入(空/越界/格式错误)→ 正确拒绝
|
||
- [ ] 测试:并发写入 → 不互相覆盖
|
||
|
||
---
|
||
|
||
## 十、示例:截图识别更新持仓
|
||
|
||
### 输入
|
||
用户发交易截图 → 知微 LLM 解析
|
||
|
||
### 输出格式
|
||
```
|
||
【交易动作】买入 / 卖出
|
||
【股票代码】600262
|
||
【股票名称】北方稀土
|
||
【交易数量】500
|
||
【交易价格】15.20
|
||
【交易时间】2026-08-20 14:30
|
||
```
|
||
|
||
### 门禁
|
||
- 交易动作:必须是"买入"或"卖出"
|
||
- 股票代码:6位数字,必须在 stock_daily 中存在
|
||
- 交易数量:正整数,卖出时不超过当前持仓
|
||
- 交易价格:正数,与现价偏差不超过 ±10%
|
||
|
||
### 写入
|
||
- 买入 → INSERT OR REPLACE holdings(shares += 交易数量)
|
||
- 卖出 → UPDATE holdings(shares -= 交易数量,shares=0 时标记 is_active=0)
|
||
- 写入后自动调用 clean_watchlist(自选池进出管理)
|