Files
MoFin/docs/llm-execution-protocol.md
T
xxm 61c3cb52b2 docs: LLM执行协议开发规范+可复用JSON schema
- 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/门禁模式
2026-08-21 00:08:09 +08:00

7.0 KiB
Raw Blame History

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 节标题解析器

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 写入前操作

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 holdingsshares += 交易数量)
  • 卖出 → UPDATE holdingsshares -= 交易数量,shares=0 时标记 is_active=0
  • 写入后自动调用 clean_watchlist(自选池进出管理)