- 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/门禁模式
7.0 KiB
7.0 KiB
LLM 执行协议(开发规范)
任何"LLM 输出 → 代码校验 → 写入 DB"的场景,必须遵循本协议。 本协议从重评流程(batch_reassess)提取,适用于所有 LLM→DB 交互。
一、协议总览
输入数据 → Prompt模板 → LLM输出 → 节标题解析 → 代码校验 → DB写入 → 确认反馈
核心原则:
- LLM 只输出文本,不直接写 DB
- 代码层做所有校验(LLM 输出不可信)
- 写入前快照,失败可恢复
- 白名单控制写入权限(不是所有 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) 函数:
- 从各表读取数据
- 填充
datadict - 返回给 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 节标题解析器
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 门禁输出
每个门禁失败必须:
- 打印
⚠️日志(含具体原因和数值) - 跳过该字段写入(保留原值)或拒绝整个写入
- 不能静默失败
六、写入层
6.1 写入前操作
snapshot_strategy_history(conn, code, source_trigger) # 快照
6.2 写入权限(白名单)
_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 门禁模式
# 区间合理性门禁
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(自选池进出管理)