第 5 课 · 情绪分析 Agent:工具调用循环

让 LLM 自己决定"先查什么、再查什么"——多步取证之后,产出一份每条结论都挂证据的持仓诊断报告

预计用时:40 分钟 上一课:第 4 课 · 策略骨架与人生第一回测

注:本课接续第 3 课,把"单次打分器"升级成"会取证的 Agent"。

这节课你将得到什么

一个能跑的持仓诊断 Agent:你扔给它一句话——"我持有茅台,提价后是去是留?"——它自己规划取证路径:先调 get_news 看消息面,发现"批价回落"的分歧信号后,再调 get_price_history 用价格走势交叉验证,最后调 save_report 落笔成文,产出 data/diagnosis_report.md。报告里每条结论都标注证据来源

这个"LLM 决定调哪个工具 → 执行 → 把结果喂回去 → 再决策"的循环,就是 ReAct(Reasoning + Acting)模式,也是 TradingAgents 论文里每个分析师角色的工作方式。第 3 课的打分器是"一次性组件",这节课的循环才是"Agent"。

一、为什么 Agent > 单次 Prompt

第 3 课的模式是:你把新闻塞给 LLM,它打分,结束。对"持仓诊断"这种真问题,单次 Prompt 有三个硬伤:

单次 PromptAgent 工具调用循环
证据由你手工收集、一次性塞入,漏了就没了LLM 发现证据不足会自己再去取——看到"批价回落"才去查价格验证
结论基于你给的文本,无法核实出处每个结论对应一次真实的工具调用,有据可查、可重放
换一个问题就要重写一遍流程工具是稳定的地基,换个持仓问题只换一句任务描述

一句话:单次 Prompt 是"问答题",Agent 是"调研员"。对真金白银的持仓决策,你要的是后者。

二、工具设计三原则(本课的知识核心)

打开 scripts/sentiment_agent.py,整个 Agent 只有三个工具,这是刻意的:

工具职责返回
get_news(symbol)拉个股新闻,东财接口失败退回内置样本dict:来源 + 条数 + 结构化新闻列表
get_price_history(symbol, start, end)读行情摘要,优先第 2 课存的本地 CSVdict:区间涨跌幅、最高最低、近 5 日收盘
save_report(text)把最终报告写入 data/diagnosis_report.mddict:保存路径 + 字数
docstring 就是工具的"招聘启事"

真实模式里,模型选择工具时读的是 tools schema 里的 description——它写得好坏直接决定模型会不会用错。"拉取最新新闻,用于收集消息面证据"比"获取数据"强十倍。写 description 时想象你在给一个聪明但完全不了解你代码的同事写交接文档。

三、循环解剖:二十行代码的 ReAct

脚本里 run_live() 的循环骨架只有几步,值得逐行读懂(这是展示用代码,不必复制运行):

for step in range(1, MAX_STEPS + 1):
    msg = chat_with_tools(messages, api_key)["choices"][0]["message"]
    messages.append(msg)
    if not msg.get("tool_calls"):      # 模型不再要工具 = 它认为可以收尾
        break
    for tc in msg["tool_calls"]:       # 模型这一步可能要调多个工具
        result = run_tool(tc["function"]["name"], json.loads(tc["function"]["arguments"]))
        messages.append({"role": "tool", "tool_call_id": tc["id"],
                         "content": json.dumps(result, ensure_ascii=False)})

关键点只有一个:工具的执行结果会以 role: "tool" 的消息追加回对话历史,下一轮请求时模型连同自己的"思考"和"观察"一起看到,于是它能基于新证据改变主意——第 2 步去查价格,正是因为第 1 步的新闻里出现了"批价回落"。这个"观察改变计划"的能力,单次 Prompt 给不了。

另一个工程细节:MAX_STEPS = 8 是循环保险丝。模型偶尔会陷入"调工具 → 不满意 → 再调同一个工具"的空转,没有上限就是烧钱死循环。任何 Agent 循环都必须有。

四、动手:跑演示模式(无需 API Key,不访问网络)

演示模式里,LLM 的"决策"是预计算的轨迹,但三个工具全部真实执行——你看到的每个数字都是工具现场算出来的:

cd "$HOME/AI Project/Quant Learning"
source .venv/bin/activate
python scripts/sentiment_agent.py

你会看到(我已在你的环境实测通过):

────────── 第 1 步 ──────────
思考:先拿新闻面证据——提价事件后,公司公告与媒体分别怎么表述
调用:get_news(symbol="600519")
观察:6 条新闻(来源:内置样本(演示模式不访问网络))
  · 飞天茅台 官宣涨价!+100元
  · 贵州茅台:上调飞天茅台i茅台平台零售价及合同价
  · 茅台调价后今日股价盘中大涨5%,白酒股集体跟涨
  · ……共 6 条

────────── 第 2 步 ──────────
思考:新闻里出现「批价回落」的空方信号,必须用价格走势交叉验证
调用:get_price_history(symbol="600519", start="2026-07-01", end="2026-07-21")
观察:16 个交易日(来源:内置样本(本地CSV仅覆盖 2024-01-02 ~ 2024-12-31,不含 2026-07-01 ~ 2026-07-21))
  1612.50 → 1771.20(+9.84%)|最高 1784.79(2026-07-20)

────────── 第 3 步 ──────────
思考:新闻(利好)/ 批价(隐忧)/ 走势(验证)三方证据齐全,落笔成文
调用:save_report(text=<报告正文,874 字>)
观察:已写入 data/diagnosis_report.md(874 字)

注意第 2 步的观察行:get_price_history 先读了本地 CSV,发现它只覆盖 2024 年、不含事件区间,于是退回内置样本——并且把这个事实写进了 source 字段。数据覆盖范围检查以后会成为你的肌肉记忆:拿 2024 年的数据去论证 2026 年的事件,是回测之外的另一种"数据造假"。

五、报告解剖:每条结论都必须挂证据

打开生成的 data/diagnosis_report.md(以下为真实生成内容的节选):

### 1. 新闻面:提价落地,直接利好 【来源:get_news】
- 公司公告:飞天茅台零售价上调 100 元至 1639 元/瓶,7 月 18 日起执行
  (《飞天茅台 官宣涨价!+100元》)。

### 2. 渠道面:批价回落,与提价背离 【来源:get_news】
- 官方提价的同时,渠道批价(经销商之间的真实成交价)在回落。
- 含义:终端需求偏弱时提价,可能挤压渠道利润——本案最大的空方证据。

### 3. 价格走势:市场短期投票支持提价 【来源:get_price_history】
- 2026-07-01 ~ 2026-07-21 共 16 个交易日:1612.50 → 1771.20(+9.84%)。
- 提价公告日(2026-07-17)收盘 1658.40,此后 +6.80%。

这份报告的骨架就是"三方证据对质":新闻面利好(提价公告)vs 渠道面隐忧(批价回落)vs 价格面验证(区间 +9.84%)。两多一空,结论是"继续持有、暂不加仓、盯批价"——而不是拍脑袋的"强烈看涨"。每个【来源】标注让你可以倒查任何一条结论:不信,就去翻那一步的工具输出。这就是"结论有据可查"的落地形态。

报告结尾还自带两条纪律:T+1 提醒(盘后分析是"为明天写计划",最早明日才能执行)和数据局限声明(样本数据如实标注)。

六、真实模式:OpenAI 兼容 function calling

演示模式的决策轨迹是我预计算的;真实模式把"决定调哪个工具"交还给模型。协议就是 OpenAI 兼容的 function calling:请求里带 tools schema(脚本里的 TOOLS 列表),模型回复里带 tool_calls,程序执行后把结果以 role: "tool" 追加回对话,循环直到模型调用 save_report 收尾或给出最终文本。

export LLM_API_KEY="sk-你的key"
python scripts/sentiment_agent.py --live

换别家模型只需改 LLM_BASE_URLLLM_MODEL 两个环境变量。真实模式我已写好但尚未实测(你机器上还没有 Key)——拿到 Key 后跑一次,有报错直接贴给我。真实模式下 get_news 会真实请求东财接口(带 15 秒超时与样本兜底,单次调用,不会循环猛刷)。

七、批判性思考:Agent 不是更聪明的 LLM,是更诚实的流程

自测

延伸阅读

下一课预告

第 6 课 · 盘中异动监控 Agent:把今天的工具循环搬到盘中——定时轮询行情,放量异动时让 Agent 自动取证(新闻+走势)并推送提醒,搭起"信号 → 人工确认"的半自动闭环。

有任何不清楚的地方(比如 function calling 协议、工具 description 写法、保险丝设计),直接问我——我是你的老师。