
做 Agent 这个方向也有一阵子了从最早纯 Prompt 硬怼到后来接各种框架聊到真正能在生产环境里天天跑的项目我手上迭代最久、收益也最直接的一个就是标题里这个基于 LlamaIndex ReAct 框架手搓全自动博客监控 Agent。简单解释一下这行字LlamaIndex 是当前大模型应用里非常流行的数据与 Agent 框架ReAct 是“推理 行动 观察”的智能体循环范式“手搓”则是说整个监控流程从工具函数到调度逻辑都是自己写、自己可控的。博客监控 Agent 的意思是让大模型自动盯着若干个博客或技术资讯源发现新文章、判断是否值得看、生成结构化摘要、推送通知全程不需要人盯着屏幕。这篇文章就把这个 Agent 从零到一的完整思路、代码细节、踩坑记录都整理出来适合已经会一点 Python、想搞懂 Agent 工具调用与 LlamaIndex 使用方式的开发者也适合想把“看周报、盯更新”这类重复动作自动化的人。这个项目跑起来之后是什么样我简单描述一下实际效果每天早上 9 点开始每 30 分钟自动抓取指定 RSS 源一旦发现新文章大模型先判断“这篇跟我的关注主题相关吗”相关就生成一段摘要然后把结果追加到本地日志文件同时推送通知到群机器人整个流程无人值守。中间 Agent 单篇判断大概消耗 10 到 20 秒对监控场景来说完全可接受。下面进入正题。1. 项目整体设计与思路拆解1.1 博客监控 Agent 要解决的真实痛点很多人第一反应是“这不就是一个 RSS 阅读器吗”确实纯“监控”这件事RSS、浏览器插件、定时爬虫都能做到。但一旦加上“判断、筛选、总结、分层告警”传统方案就显得很僵。RSS 能告诉你“有更新”不能告诉你“这篇值不值得我读”爬虫能把文章抓下来但抓下来的几百篇文档没人帮你整理。以前的手动流程基本是打开阅读器划两下看到感兴趣的标题点进去复制正文丢给聊天助手要一个总结再把总结转给同事。这个过程每天重复 5 到 10 次大量操作是纯重复劳动。所以我给这个项目定的目标不是“做一个监控脚本”而是“做一个懂得筛选和总结的博客监控助手”。具体拆成四个需求点捕捉更新定时抓取博客 RSS 或网页发现新发布的文章。内容判断结合用户定义的关注主题判断每篇文章是否值得读、优先级多高。生成摘要不是简单截取前 200 字而是要求结构化输出——结论一句、要点最多三条、原文链接一个。执行通知把监控结果推送给自己或团队支持多种渠道至少保证本地有完整存档。这四个需求里捕捉更新是确定性任务内容判断和摘要生成是语义性任务执行通知又是确定性任务。Agent 只负责中间两个语义任务两头都用常规代码解决。这个决策是整个项目最核心的架构思想后面所有代码和调优都是围绕它展开的。1.2 为什么用 LlamaIndex 而不是自己硬写 ReAct 循环ReAct 的原理听起来不复杂Reason思考→ Act行动调用工具→ Observe观察结果→ 循环。理论上可以自己用几十行代码实现一个循环每次把“当前任务 历史思考 工具返回”拼进 prompt再调一次模型。但真正落地时自己写一个简化版 ReAct 循环会碰到一堆边界情况模型生成的“下一次行动”里经常混入多余文本导致 JSON 解析失败需要反复容错。工具返回结果如果太长下一轮 prompt 会装不下得设计截断策略。模型可能连续调用同一个工具三次形成死循环没有人干预。没有日志系统的话模型在哪一步想错、哪一步调用错根本无从排查。这些边界都可以被一个成熟的框架解决。LlamaIndex 虽然以“文档检索与索引”出名但它的 Agent 体系其实相当完善尤其是ReActAgent可以把任意 Python 函数包装成FunctionTool后交给大模型调度。相比其他同类框架LlamaIndex 对数据结构的感知能力更强工具输入输出可以直接是 JSON索引、存储、查询一体化特别适合“既看上下文又翻历史”的监控加知识沉淀场景。另外LlamaIndex 的 Agent 是模型无关的。可以用云端大模型 API 的模型也可以换成完全本地部署的开源模型ReAct 机制本身不挑模型。这一点非常实际因为很多团队因为数据合规问题不能调用外部 API而 LlamaIndex 支持的模型生态很广对生产环境落地至关重要。1.3 最终架构一个循环、两个仓库、三张表我最终落地的架构可以概括为“一个循环、两个仓库、三张表”。这个说法是我在笔记本上记的后来项目传开几个同事也沿用这个叫法。一个循环ReAct 循环。每次拿到新文章Agent 依次完成“判断文章是否相关 → 生成结构化摘要 → 保存通知结果”这一串工具调用。两个仓库待处理队列和已处理记录。待处理队列是抓回来的原始文章列表已处理记录是已经跑过的 URL 集合专门用来去重。三张表文章元信息表、Agent 决策日志表、通知记录表。前两张放 SQLite通知记录表量很小直接用 JSON 文件。为什么要这样设计核心原则是Agent 只做“思考型”任务不做“搬运型”任务。抓取、存储、调度是确定性的不应该依赖大模型的“临场发挥”Agent 只嵌入在“这篇文章是否值得看、摘要怎么写、优先级多高”这些没有标准答案的地方。这样有几点好处Agent 调用频率可控成本不失控某篇处理失败时可以手动改数据、重新喂给 Agent不用去改代码逻辑整个系统的稳定性由常规代码保证而不是由模型的随机性决定。2. 核心细节解析与实操要点2.1 ReAct Agent 的工作循环到底怎么转ReAct 这个框架的核心是让模型不再一次性输出最终答案而是在推理过程中不断产生 Thought当前任务需要什么信息、Action调用哪个工具获取信息、Observation工具返回了什么直到模型认为自己拥有足够信息才输出最终答案。在 LlamaIndex 中ReActAgent 把这个循环封装成了一个agent.query(task)调用。我最初以为它只是“把任务发给模型模型直接输出答案”后来打开 verbose 日志才发现它内部循环调用了好几次模型。举例来说当任务描述是“处理这篇博客文章判断是否相关如果相关就生成摘要并保存”它的内部决策日志大致长这样Thought: 我需要先把文章信息拿到手才能判断相关性。 Action: get_blog_updates Action Input: {feed_url: https://example.com/rss, max_entries: 5} Observation: [RSS 返回的文章列表 JSON] Thought: 我已经拿到文章列表需要判断《Retrieval-Augmented Generation》是否与关注主题相关。 Action: judge_article Action Input: {title: Retrieval-Augmented Generation, url: https://..., content: 摘要内容} Observation: 相关 Thought: 这篇文章相关我需要生成一份结构化摘要。 Action: summarize_article Action Input: {title: Retrieval-Augmented Generation, content: ...} Observation: {conclusion: ..., points: [...]} Thought: 摘要已生成需要保存通知。 Action: save_notification Action Input: {article: ..., summary: ...} Observation: 已保存到 ./monitor_logs/20250608.md Thought: 所有任务已完成可以给出最终回复。 Answer: 处理完成通知已保存。看这份日志就明白为什么说 ReAct 是“动态推理 工具调用”的循环。模型不是一步到位而是把任务拆解成子步骤每个子步骤自己判断该调用哪个工具。这也是为什么我反复强调工具函数 docstring 要写清楚——如果模型说不出工具的作用它就没法推理出该调用谁。2.2 FunctionTool 包装工具的四个铁律LlamaIndex 的FunctionTool.from_defaults可以把任意函数包装成 Agent 可调用的工具。这个设计确实省心但实际使用中我总结出四条铁律全是踩坑踩出来的。第一docstring 必须写成给模型看的说明书而不是给程序员看的注释。普通 Python 函数 docstring 可以写“这个函数用于判断文章是否相关”但对 Agent 来说这种描述不够明确。我后来改成def judge_article(title: str, url: str, content: str) - str: 判断文章是否与关注主题相关。title 为标题url 为链接content 为摘要。当且仅当内容命中大模型、RAG、Agent 等关键词时返回相关否则返回不相关。第二参数越扁平越好。工具函数如果有嵌套结构比如传一个自定义文章对象进去模型生成的 JSON 参数经常会丢字段或写错类型进而报校验错误。我建议所有工具函数参数都用str、int、bool这类简单类型参数数量控制在 4 个以内。第三工具返回字符串要短、结构要固定。ReAct 每轮工具返回都会拼到下一轮 prompt 里如果返回又长又杂Agent 很容易迷失在文本里。我给自己定了一个规范所有工具返回不超过 600 个字符。对于需要长内容的场景工具只返回截断后的摘要完整内容让外层代码去读。第四工具的失败要“可观察”。比如某个工具因为网络问题失败了千万不要在函数内部静默吞掉异常后返回空字符串。Agent 一旦观察到空字符串就可能认为“没有新文章”然后停止造成漏报。工具要么成功返回有意义的 JSON要么抛出异常并让外层代码记录日志不要让模型在信息缺失的情况下硬做判断。2.3 系统提示词的调优给 Agent 立规矩LlamaIndex 的 ReActAgent 自带一套默认 Prompt但通用 Prompt 在监控场景下不够聚焦。我写了一套自己的系统提示词核心是给 Agent 立下几条硬规矩。最关键的四条分别是明确工具调用顺序先判断、再摘要、再保存不要跳步。明确摘要格式结论一句、要点最多三条、必须带原文链接。明确不确定时怎么办无法确认相关性时宁可标记为低优先级也不能直接忽略。明确禁止编造原文里没有的信息禁止写进摘要。我用的系统提示词模板大致长这样from llama_index.core.prompts import PromptTemplate MONITOR_SYSTEM_PROMPT PromptTemplate( 你是一个博客监控助手。你的任务是根据用户的关注主题审视新发布的博客文章。 必须遵守以下规则 1. 先调用 get_blog_updates 获取最新文章列表。 2. 对每篇文章调用 judge_article 判断相关性。 3. 如果判断结果为相关再调用 summarize_article 生成结构化摘要。 4. 摘要必须包含conclusion一句话结论、points最多3个要点、source_url原文链接。 5. 如果无法确认相关性标记为低优先级但仍要生成摘要。 6. 禁止编造原文中不存在的信息。 用户关注主题 {user_topics} 请开始处理任务。 )这中间最有价值的是第 5 条“无法确认时标记低优先级但仍要生成摘要。”监控场景里漏掉一篇重要文章的代价远大于多收到一条不一定想看的通知。我们要的是一个“高召回率”的监控系统而不是“高精准率”的系统。这个权衡几乎完全靠这一句话规定下来。3. 实操过程与核心环节实现3.1 环境准备与依赖清单先说结论我最终在 Python 3.10 下跑通核心依赖版本如下依赖版本范围用途llama-index-core0.11.0Agent 与索引核心llama-index-llms-openai0.2.0云端大模型封装feedparser6.0.11RSS/Atom 解析beautifulsoup44.12.3HTML 正文提取apscheduler3.10.4定时调度openai1.30.0底层 API 客户端安装依赖一条命令pip install llama-index-core llama-index-llms-openai feedparser beautifulsoup4 apscheduler openai这里要特意说明一点LlamaIndex 从 0.10 版之后拆包拆得非常细不再是一个大llama-index包涵盖所有功能。很多旧教程里写from llama_index import ...在新版本下会直接报导入错误。我建议直接安装最新版并且以llama-index-core作为导包根。三个最常用的稳定导入路径是from llama_index.core.agent import ReActAgent from llama_index.core.tools import FunctionTool from llama_index.llms.openai import OpenAI这三条导入路径在不同小版本之间比较稳定值得记下来。3.2 四个工具函数的完整实现开始写核心代码。第一个工具是获取博客更新。我用feedparser它支持 RSS 2.0、Atom 等主流格式容错性比手写正则强太多。import json import feedparser def get_blog_updates(feed_url: str https://example.com/rss, max_entries: int 10) - str: 获取博客最新文章列表。feed_url 为 RSS/Atom 地址max_entries 为最多返回多少篇。返回 JSON 数组每项含 title、url、published、summary 字段summary 已截断为 200 字。 feed feedparser.parse(feed_url) entries [] for entry in feed.entries[:max_entries]: entries.append({ title: entry.get(title, ).strip(), url: entry.get(link, ).strip(), published: entry.get(published, ), summary: entry.get(summary, )[:200], }) return json.dumps(entries, ensure_asciiFalse)这段代码有两个细节。一是返回类型统一成 JSON 字符串毕竟 Agent 需要的是文本形式二是max_entries给到 10宁可多返回几篇旧的也不要漏掉最新的。关于 HTML 清理feedparser 返回的 summary 可能带p标签最好再套一层 BeautifulSoup 把所有标签剥掉def clean_html(raw: str) - str: from bs4 import BeautifulSoup return BeautifulSoup(raw, html.parser).get_text( , stripTrue)第二个工具是相关性判断。这里给出一套“关键词兜底 Agent 语义判断”的组合方案TOPIC_KEYWORDS [大模型, Agent, RAG, 知识库, 推理, 微调, 编程] def judge_article(title: str, url: str, content: str) - str: 判断文章是否与关注主题相关。title 为标题url 为链接content 为正文或摘要。如果 title 或 content 中命中任一关键词返回相关否则返回不相关。 text (title content).lower() for kw in TOPIC_KEYWORDS: if kw.lower() in text: return 相关 return 不相关需要再次强调这个工具本身没有调用大模型只是一个确定性兜底。真正的语义判断发生在 Agent 的思考阶段工具只是给 Agent 提供一个可验证的最终结果。这样设计的好处是即使模型在语义上产生了幻觉工具也能用关键词把不相关的文章揪出来确保外层代码不会误发通知。第三个工具是结构化摘要。实际运行时摘要的大逻辑由 Agent 在最终回复里直接生成但工具函数仍然需要它充当“强制格式整理器”防止 Agent 偶尔偷懒只输出一句话def summarize_article(title: str, content: str) - str: 根据文章内容生成结构化摘要。必须返回 JSON{conclusion: ..., points: [...], source_url: ...}。title 为标题content 为正文摘要。 sentences [s for s in content.replace(\n, 。).split(。) if s] conclusion sentences[0][:120] if sentences else title points [] for s in sentences[1:]: if len(points) 3: break if len(s) 20: points.append(s[:80]) return json.dumps({ conclusion: conclusion, points: points, source_url: , }, ensure_asciiFalse)第四个工具是通知与存档。我把“写 Markdown 文件”和“发群机器人消息”拆成两个函数因为通知渠道随时会变存档不能跟着变。群机器人用的是 webhook实现非常轻量import os import requests def save_notification(article: str, summary: str) - str: 将监控结果追加写入本地 Markdown 文件并返回路径。article 为文章标题summary 为摘要文本。 from datetime import datetime path f./monitor_logs/{datetime.now():%Y%m%d}.md os.makedirs(./monitor_logs, exist_okTrue) with open(path, a, encodingutf-8) as f: f.write(f\n## {article}\n\n{summary}\n) return path def push_webhook(text: str) - str: 通过群机器人 webhook 推送消息。text 为要发送的内容。返回ok或错误原因。 webhook_url os.getenv(WEBHOOK_URL, ) if not webhook_url: return 未配置 webhook跳过推送 resp requests.post(webhook_url, json{msgtype: text, text: {content: text}}, timeout10) return ok if resp.status_code 200 else fpush failed: {resp.status_code}3.3 组装 ReActAgent 并跑一轮监控工具定义好之后就是组装。我用的初始化代码如下from llama_index.core.agent import ReActAgent from llama_index.llms.openai import OpenAI llm OpenAI(modelgpt-4o-mini, temperature0.2, timeout120) tools [ FunctionTool.from_defaults(get_blog_updates), FunctionTool.from_defaults(judge_article), FunctionTool.from_defaults(summarize_article), FunctionTool.from_defaults(save_notification), FunctionTool.from_defaults(push_webhook), ] agent ReActAgent.from_tools( toolstools, llmllm, system_promptMONITOR_SYSTEM_PROMPT.format(user_topics大模型、RAG、Agent、编程效率), max_iterations8, verboseTrue, )max_iterations必须设置我一开始没设结果遇到一个模糊任务Agent 在思考与行动之间兜圈子最后把上下文撑爆了。在博客监控场景正常任务链路很短判断一次、摘要一次、保存一次最多加一次兜底查询8 到 10 次迭代完全够用。verboseTrue在调试阶段更不能省它能把每一步的 Thought/Action/Observation 都打印出来排查问题全靠它。然后定义一轮监控任务。注意监控不是一次调用agent.query处理全部文章而是先拿到文章列表再逐个喂给 Agent。为什么不一次全喂因为多篇文章会让上下文快速膨胀Agent 也容易在总结 A 篇时惦记 B 篇导致输出混杂。逐个处理更清晰也更好排错。processed_cache set() CACHE_PATH ./monitor_logs/processed_urls.json def load_processed_cache(): global processed_cache if os.path.exists(CACHE_PATH): with open(CACHE_PATH, encodingutf-8) as f: processed_cache set(json.load(f)) def save_processed_cache(): os.makedirs(./monitor_logs, exist_okTrue) with open(CACHE_PATH, w, encodingutf-8) as f: json.dump(list(processed_cache), f, ensure_asciiFalse) def run_monitor_once(): feed_json get_blog_updates(https://example.com/rss, max_entries10) raw_articles json.loads(feed_json) for item in raw_articles: url item[url] if url in processed_cache: continue processed_cache.add(url) save_processed_cache() task ( f请处理这篇新文章。\n f标题{item[title]}\n f链接{url}\n f摘要{item[summary]}\n f任务步骤先判断相关性再生成摘要最后保存通知。 ) try: result str(agent.query(task)) print(fOK {item[title]} - {result[:80]}) except Exception as e: logging.error(f处理失败 {url}: {e}) continue这段代码有一个容易被忽略但很重要的点processed_cache.add(url)放在agent.query之前也就是“先占坑、再处理”。如果先处理再写缓存进程在两者之间崩溃重启后同一条 URL 会被再处理一次造成重复通知。先占坑是去重策略里最稳的一招。3.4 定时调度与异常隔离使用apscheduler做每 30 分钟一次的定时调度from apscheduler.schedulers.blocking import BlockingScheduler if __name__ __main__: logging.basicConfig(levellogging.INFO, format%(asctime)s %(levelname)s %(message)s) load_processed_cache() scheduler BlockingScheduler() scheduler.add_job(run_monitor_once, interval, minutes30, next_run_timedatetime.now()) scheduler.start()next_run_timedatetime.now()让任务启动后立即先跑一次方便验证。正式部署时可以用进程托管工具把这个 Python 进程守护起来保证崩溃自动拉起。我自己在服务器上用一个简单的服务文件跑它日志单独打到文件里非常省心。异常隔离也很关键。run_monitor_once的最外层必须保证单篇文章处理失败不会中断整个循环单个轮次失败不会让进程退出。这通过 try/except logging 实现。云端模型 API 偶尔会限流或超时重试机制建议放在工具层而不是调度层。常用的做法是给run_monitor_once加一个带重试的装饰器遇到网络错误最多重试 3 次每次退避 5 秒。重试只针对网络类异常不针对业务逻辑异常不然会把重复通知问题放大。4. 常见问题与排查技巧实录4.1 Agent 工具参数乱填的排查路径这是所有 ReAct Agent 最容易出的问题。我曾经遇到一次judge_article的三个参数模型居然把整篇文章正文塞进了title导致后续判断完全错乱。排查路径其实很固定第一步确认verboseTrue已开启在日志里找到该次调用的 Action Input。第二步对照工具 docstring看字段说明是否足够精确。第三步如果 docstring 没问题看是不是输入的文章摘要太长干扰了模型对参数结构的识别。我后来在任务派发代码里把summary从 800 字截到 200 字问题立刻缓解。参数乱填的另一个原因是工具名太相似。我最早的工具叫judge_article和summarize_article模型偶尔会选错。后来我把工具名改成check_article_relevance、generate_article_summary和save_monitor_result选错率明显下降。工具名带明确动词对大模型是最友好的提示。4.2 上下文膨胀与长期运行ReActAgent默认会把每次query的对话都叠加到上下文里。如果在同一个 agent 实例上处理几十篇文章上下文会越来越大最终超过模型窗口。我的经验是监控任务不要复用 Agent 实例每一轮监控都新建一个。这样上下文只包含当前文章的几轮思考与工具输出长度完全可控。同时工具返回截断也是控制 token 的有效手段。比如get_blog_updates返回整个 JSON 数组每篇文章都带 200 字摘要10 篇就是 2000 字这个量级对下一轮思考是很大的干扰。后来我改成get_blog_updates只返回每篇文章的标题和 URL摘要不放进工具返回而是在外层代码拿到文章列表之后把摘要作为任务上下文的一部分单独喂给 Agent。工具返回体积大幅下降Agent 的推理质量反而上来了。4.3 重复通知与去重策略重复通知最常见的原因就是“处理成功但缓存没写成”。可能agent.query执行成功但进程在写 JSON 之前崩溃重启后同一条 URL 又被处理一遍。解决办法就是前面说的“先占坑、再处理”并且每处理一篇都立即持久化缓存。如果确实想保留“失败后重新处理”的能力可以单独维护一个failed_urls集合待人工确认后再重新入队不要和processed_cache混在一起。4.4 幻觉与误判的对策监控场景里最容易出现的幻觉是 Agent 在判断相关性时脑补“这篇文章虽然没有直接提到 RAG但作者在讨论知识库应该也算相关。”这种脑补在小型模型上尤其严重。我的对策分三层提示词层系统提示词里明确“只能根据给出的原文内容进行判断不得推理出原文没有的信息”。工具层关键词工具结果作为硬性过滤条件外层代码碰到“不相关”就直接跳过不跟 Agent 商量。兜底层如果 Agent 最终回复里把“不相关”的文章硬写成了摘要外层代码可以根据judge_article的返回值做二次校验确保摘要只出现在“相关”的文章上。早期版本里我还遇到过更离谱的Agent 在摘要里写了一句“这篇文章获得了大量关注”但原文全文都没提“关注量”三个字。加了硬性校验之后至少这类无中生有的关键信息能被拦住。4.5 常见问题速查表现象可能原因解决方向工具参数乱填工具描述不清晰、输入太长优化 docstring、截断输入、扁平化参数Agent 死循环max_iterations 没设或设太大设置 max_iterations8~10上下文超限同一 Agent 实例复用太久每轮监控重建 Agent重复通知缓存未持久化、崩溃重启先占坑、再处理、立即写缓存摘要出现幻觉内容模型无依据生成提示词禁止编造 外层校验漏抓文章RSS 只返回少量条目提高 max_entries、缩短调度间隔5. 后续扩展方向与个人总结5.1 从“监控”到“知识库 Agent”博客监控 Agent 完全可以作为其他 Agent 应用的基础层。我后来做了一个扩展把每轮监控生成的结构化摘要和原始链接自动写入 LlamaIndex 的向量索引。这样当你想问“过去一个月有哪些文章提到过 RAG 评估方法”时可以通过另一个 ReActAgent 先在索引里检索再基于检索结果回答。监控产生积累积累反过来辅助新判断这个循环非常顺。实现上向量索引那个 Agent 不需要额外工具只需要把 LlamaIndex 自带的VectorStoreIndex查询函数包装成工具即可from llama_index.core import SimpleDirectoryReader, VectorStoreIndex def search_knowledge(query: str, top_k: int 3) - str: 在历史监控内容库中检索与 query 最相关的文章摘要。query 为检索词top_k 为返回条数。 index VectorStoreIndex.from_documents(SimpleDirectoryReader(./monitor_logs).load_data()) retriever index.as_retriever(similarity_top_ktop_k) nodes retriever.retrieve(query) return json.dumps([n.text for n in nodes], ensure_asciiFalse)这段代码每次调用都会重建索引效率不高。实际使用时最好在进程启动时加载一次历史文档并构建索引后续一直复用只有监控轮次结束才增量更新。5.2 个人实操体会这个博客监控 Agent我从第一个能跑的版本到后来稳定使用的版本大概迭代了两三个月期间换过模型、换过工具设计、换过调度策略。最大的体会不是“Prompt 技巧有多重要”而是“要搞清楚哪件事该让 Agent 干哪件事不该让 Agent 干”。Agent 适合做的事情是有语义模糊性的判断文章相不相关、摘要怎么写、优先级怎么定。Agent 不适合做的事情是可预期的、确定性的步骤抓取、去重、存储、调度。把确定性步骤全部抽到普通代码里Agent 的工作范围变得非常清晰系统可维护性也会好很多。如果你也想做一个类似的 Agent我的建议是先想清楚三个问题这个流程里哪些环节是确定性的可以直接写死哪些环节真的有语义模糊性必须依赖大模型每个环节失败以后会造成什么后果怎么设计降级方案想清楚这三个问题再回来翻这些代码你会发现几乎每一个设计决策都是被这三个问题推着走的而不是拍脑袋定的。最后再分享一个我坚持了很久的小习惯永远把 Agent 的 verbose 日志保存下来。我排查所有奇怪问题的第一步几乎都是回放日志从 Thought、Action、Observation 的完整链路里定位到底是推理错、工具错还是数据错。有了这个习惯遇到什么问题都不会太慌。服务跑了这么久真正出大问题的次数屈指可数绝大多数故障都能靠日志在半小时内定位到根因。这个习惯也是整个项目里我觉得最值得带走的东西。