新闻详情

新闻详情

首页 / 资讯中心 / 详情

手写ReAct循环:Python实现大模型驱动的图书智能检索Agent

发布时间:2026/9/26 13:01:14来源:尧图网络
手写ReAct循环:Python实现大模型驱动的图书智能检索Agent
如果你最近也在琢磨 Agent 开发却被各种框架术语搞得一头雾水那这个项目应该能帮你把抽象概念真正落地。我前阵子用 Python 从零做了一个图书智能检索系统核心思路是让 AI 大模型当大脑由一个轻量级 Agent 负责理解用户随口一句话里的真实意图再自动完成图书检索、条件筛选、结果推荐这一整套动作。比如用户说帮我找几本适合零基础入门 Python 的机器学习书最好 2019 年之后出版的评分要高传统检索方案得先人工拆出四五组查询条件而换上 Agent 之后这句话丢进去系统自动拆解意图、组装查询、按热度排序最后还带一句推荐理由给你。这个项目从头到尾不依赖重型框架模型接入层、Agent 推理循环、检索工具、SSE 流式输出全部自己手写非常适合三类人刚学完 Python 基础语法、想找个完整项目练手的同学对大模型应用开发感兴趣但不知道从哪下手的初学者以及看过 Agent 概念但一直没搞懂它内部到底怎么跑的人。你不需要先啃完整本机器学习教材只要懂 Python 基础、会装依赖、能跑起一个 FastAPI 服务就能跟着走完整个流程。1. 这个项目到底做了什么一个能听懂人话的图书检索 Agent1.1 需求是怎么被逼出来的先说说我为什么要做这个系统。我之前给一个小型读书社群写过一个图书查询工具用的是最朴素的方案页面上放几个下拉框用户手动选分类、填关键词、设评分下限然后点查询。数据是从豆瓣和出版社网站爬了两千多条图书信息存进 SQLite 里的。工具上线后反馈很直接门槛太高。普通用户根本不想理解分类标签和出版年份区间这些概念他们习惯的表达方式是最近有什么好看的科普书或者有没有那种讲人工智能历史的、不要太厚的书。让我印象最深的一个需求是我想找一本适合在通勤路上读的短篇科幻小说集这种句子里的通勤场景短篇科幻合集四个条件用传统下拉框几乎没法表达。于是我就想能不能让用户直接说人话由程序来理解并完成检索这正好是 Agent 的看家本领。所谓 Agent本质就是一个大模型 工具 循环的组合大模型负责理解和决策工具负责执行具体动作我这里唯一的工具就是图书数据库查询循环负责让模型可以多轮思考、逐步逼近最终答案。用户说一句自然语言Agent 先分析出检索条件再查出候选图书最后组织成带推荐理由的回答。1.2 系统整体架构与工作流程整个系统分四层每一层解决一个明确问题接入层负责和大模型 API 通信。我用的配置方式是 OpenAI 兼容接口这样无论接国内厂商的模型还是本地跑的模型改一行 base_url 和 api_key 就能切换。Agent 层核心是手写的一个简化版 ReAct 循环。每一轮里大模型先输出一个思考决定是要调用检索工具还是直接给出最终回答。工具返回结果后模型再继续思考和决策直到它觉得信息够了。循环次数上限设为 8 次防止模型抽风进入死循环。执行层即检索工具本身。它接收模型生成的 JSON 格式查询条件动态拼 SQL 到 SQLite 里查询并把结果按一定策略排序后返回给 Agent。展示层一个极简的 Web 页面通过 SSE 把大模型的回复实时渲染出来避免用户盯着空白页干等 8 秒。整个流程举个例子用户输入推荐几本适合初学者的人工智能书出版社最好靠谱点的。Agent 第一轮思考判断出用户想找人工智能、入门级的书调用查询工具工具按这两个条件从库里捞出几十本书返回时附上每本书的评分和出版次数Agent 第二轮思考发现结果太多决定增加评分大于 8.0的过滤条件再次查询第三轮思考认为信息足够于是组织出一段回答列出三本书并分别说明推荐理由。1.3 零基础上手这个项目需要具备什么基础说实话这个项目真正的门槛不在技术而在心态。技术侧只需要三样第一Python 基础语法能看懂函数、类、字典和列表即可完全不需要会写装饰器、元类这些进阶特性。第二能装软件、能跑命令行比如创建虚拟环境和安装依赖。第三对大模型有一点感性认识哪怕只是用过 ChatGPT 对话框也算。比较容易被劝退的地方反而是Agent 好高级这种心理暗示。我特别强调一件事Agent 没有你想象的那么玄它不涉及神经网络训练只是借助大模型的语言理解和生成能力加上一套简单的决策循环。你不需要懂 transformer、注意力机制这些底层原理那是模型训练开发者的领域而 Agent 应用开发者只需要把模型当作一个能力很强的人来安排任务即可。如果你连 FastAPI 都没用过也不用慌。我用它只是因为它的异步原生支持让 SSE 写起来舒服你要是更熟悉 Flask逻辑是一模一样的后面我会把关键代码贴出来照抄就能跑。2. 技术选型复盘为什么最后选了这套组合拳2.1 大模型选择API 调用还是本地部署这是项目开始时第一个要拍板的问题。我当时的备选有三个直接调用商业 API、本地部署开源模型、用国内厂商的在线 API。本地部署开源模型比如 Qwen 系列的好处是数据不出门、长期使用没成本但对零基础玩家极不友好光是显存要求就能劝退一大半人一台普通笔记本跑 7B 模型做生成速度慢得你会怀疑人生。我当时在一台 16G 内存的 MacBook 上跑过量化版本单次推理延迟 5 秒以上完整检索一轮下来要 40 秒体验非常糟糕。最终我选了OpenAI 兼容 API 可切换配置的方案。也就是说代码里不写死任何一家厂商而是通过环境变量配置 base_url、api_key 和 model_name。这样有几个实际好处学习阶段可以充一点 token 零成本体验后期如果想切换到本地 Ollama 跑的模型只改三个配置值一行业务代码不用动更重要的是OpenAI 兼容接口是当前业界事实标准学了一遍到处能用。配置长这样# .env LLM_BASE_URLhttps://api.xxx.com/v1 LLM_API_KEYsk-your-key-here LLM_MODELqwen-plus对应到 Python 代码里就是用一个统一的函数封装 chat completion 请求。至于具体接哪家我建议你根据自己手头的资源来有哪个平台的 key 就用哪个关键是选支持 OpenAI 格式接口的。2.2 Agent 框架成熟框架还是手写轮子Landscape 里已经有 LangChain、AutoGen、CrewAI 这些现成框架直接拿来用十天就能跑起来那我为什么还要手写一个循环我的理由很朴素框架会掩盖原理。我自己最初就是先用了 LangChain 的 Agent跑通 Demo 之后很开心但被朋友问它内部是怎么决定调用哪个工具的时我发现自己完全答不上来。其实框架帮你做的核心事情就几件把用户输入拼进 prompt、调用模型、解析模型输出的动作指令、执行工具、把结果拼回去再调模型。这几行逻辑加起来不到 100 行 Python你一旦亲手写过再看 LangChain 的文档就会觉得豁然开朗——原来它包装的就是这么个东西。所以我做了个折中的教学设计Agent 推理循环自己写借助 Requests 调大模型接口不引入 LangChain。这样既不会陷入框架黑盒也不至于从零开始写模型通信层。等到你把循环跑通理解了本质再回头玩 LangChain 会非常轻松因为它的一切概念都围绕这个循环展开。2.3 为什么用 FastAPI 而不是 Flask 或 Django选 FastAPI 有三个决定性原因。一是SSE 支持顺手。FastAPI 基于 Starlette天然支持异步流式响应我可以用一个 StreamingResponse 把大模型的 SSE 输出直接透传给浏览器中间不需要开线程池或队列中转。如果用 FlaskSocket 支持要靠额外扩展事件循环的处理也绕。二是类型校验省心。FastAPI 的 Query 参数和 JSON Body 都有自动校验我在设计工具函数时可以直接用 Pydantic 模型约束 Agent 输出的 JSON 格式结构不对立刻报错而不是让脏数据一路跑到 SQL 拼接那里才爆雷。三是文档免费送。生成了自动的 /docs 页面调试工具调用时我直接在上面点按钮测试接口参数一目了然。你不用纠结这个选择对不对它就是四个 Web 框架里最适合做实时对话接口的那一个。如果你非要用 Flask把app.post和StreamingResponse相关内容换成 Flask 的响应对象即可核心 Agent 逻辑完全不受影响。3. 环境准备与项目骨架半小时跑通最小闭环3.1 Python 环境配置与虚拟环境我按常规实践给你列一个稳妥的初始化路径也是我个人在带项目时固定用的流程。首先确保 Python 版本在 3.10 以上太低的话 Pydantic 新版本和新语法支持会有限制。用命令确认版本python3 --version如果没有 3.10 以上版本去官网对应位置下载安装包。安装完之后建议立刻创建一个项目专属虚拟环境把依赖隔离在项目内部避免污染系统 Python。我在目录里执行这几步mkdir book-agent cd book-agent python3 -m venv venv source venv/bin/activate # Windows 下用 venv\Scripts\activate虚拟环境建好之后再安装依赖pip install fastapi uvicorn openai python-dotenv这里解释一下每个包在项目里的职责fastapi 管 Web 路由和接口uvicorn 是 ASGI 服务器负责真正把端口跑起来openai 不是必须的但我推荐直接用官方 Python SDK它内置了流式streamTrue处理比手写 Requests 拼 SSE 解析省非常多事python-dotenv 负责读 .env 文件把密钥等配置加载进环境变量。3.2 大模型接入层一个函数打通所有模型接入层是整个项目的地基。我不在业务代码里到处调用模型而是统一收敛到一个函数里方便以后替换模型或加缓存。from openai import OpenAI import os client OpenAI( base_urlos.getenv(LLM_BASE_URL, https://api.xxx.com/v1), api_keyos.getenv(LLM_API_KEY, ), ) def chat(messages, *, streamFalse, temperature0.3): 统一的大模型对话入口支持流式输出 resp client.chat.completions.create( modelos.getenv(LLM_MODEL, qwen-plus), messagesmessages, streamstream, temperaturetemperature, ) if stream: return resp # 返回流对象由调用方逐段消费 return resp.choices[0].message.content这个函数虽然短但值得说几点细节。temperature 我设为 0.3因为图书检索场景要的是确定性和准确性不希望模型编造书目或随机发挥如果以后做猜你喜欢这类偏创意场景可以再调高。stream 参数的设计是为了让同一个函数同时服务 Agent 内部非流式决策和对外部用户的流式展示这是项目里反复用到的一个函数它跑通了整个后续开发就成功了一大半。3.3 图书数据准备SQLite 里建一个微型图书馆为了专注 Agent 开发而不是爬虫我用了一个很取巧的办法准备一个 init_db.py 脚本内置一百多本图书样本数据自动建库并插入。数据结构按照检索需求设计成五张核心字段CREATE TABLE books ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, author TEXT NOT NULL, category TEXT NOT NULL, -- 分类计算机/社科/科幻... tags TEXT DEFAULT , -- 逗号分隔的标签如入门,机器学习 rating REAL DEFAULT 0, -- 豆瓣风格评分 0~10 pub_year INTEGER DEFAULT 0, -- 出版年份 publisher TEXT DEFAULT , intro TEXT DEFAULT , -- 内容简介用于展示 hot_score INTEGER DEFAULT 0 -- 热度权重用于排序 );插数据时我特意混合了一些区分度高的样本比如有一本《Python 编程从入门到实践》分类是计算机、标签含入门评分 8.8也有一本《深入理解计算机系统》分类是计算机、标签却是进阶、系统评分 9.5。这样设置是为了测试 Agent 后续能否区分入门和进阶的语义差异而不是把两本书无差别搜出来。初始化只要跑一遍python init_db.py就会在同目录生成 book.db 文件Agent 的工具层用 Python 内置的 sqlite3 模块连接它即可。4. Agent 核心逻辑手写一个简化版 ReAct 推理循环4.1 Agent 的本质模型、工具与循环的三角关系在写代码之前我强烈建议你用这个类比理解 Agent把它想象成你请了一位知识渊博但手边没有资料的实习生他能说会道但是想拿到具体数据必须用你给的特定工具。他每干一件事的流程是先想想目前缺什么信息Thought然后决定要查什么Action查完之后你告诉他结果Observation他再继续想下一步。这个思考-行动-观察的循环不断重复直到他觉得自己能直接回答了Final Answer。所以一个 Agent 最少要有四样东西模型实习生的大脑、工具的注册表告诉他有哪些工具可用、每个工具干嘛用、循环控制逻辑决定什么时候继续、什么时候停、状态管理把历史对话内容一直传递给模型让它不失忆。我的实现里没有引入复杂的状态机所有状态都放在 messages 列表里随着循环像滚雪球一样传给模型。这个做法的优点是对新手极其直观缺点我也在后面单独说——长对话会撑爆上下文。4.2 工具注册与调用让 Agent 学会查数据库工具怎么让模型知道并学会调用我的做法是零依赖的把工具描述直接写进 system prompt让模型用约定好的 JSON 格式输出动作。举个具体例子system prompt 里的工具声明是这样的可用工具 1. search_books(query_json) - 功能按条件检索图书数据库 - 参数格式{keyword: 关键词, category: 可选分类, min_rating: 最小评分, year_from: 出版起始年, sort: hot|rating|year} - 示例{keyword: Python, category: 计算机, min_rating: 8.0} - 注意如果不确定某个条件用户是否想要宁可少传不要乱猜然后我要求模型在需要检索时输出一行固定的动作前缀我用正则抓取ACTION: {tool: search_books, params: {keyword: 机器学习, category: 计算机}}为什么要用正则抓而不是直接用 function calling我承认 function calling 机制更成熟但正则是所有模型都支持的老实办法。国内很多模型对 function calling 的格式支持不完全一致而输出一行 JSON是各家模型都能做好的事情。对零基础项目来说少一个变量就少一类问题。等你熟练之后完全可以升级成官方 function calling 协议。4.3 循环主体的完整代码实现这是 Agent 最核心的执行循环一共不到 60 行。我贴的是完整可跑的伪代码与真实代码综合版本核心骨架就是while 循环 两轮模型调用import json, re from llm_client import chat def run_agent(user_input: str, message_history: list, max_steps: int 8): system 你是图书检索助手。你可以调用 search_books 工具获取图书数据也可以直接回答。严格按规定的 ACTION 格式输出动作。 messages [ {role: system, content: system}, *message_history, {role: user, content: user_input}, ] step 0 while step max_steps: step 1 reply chat(messages, streamFalse) # 检查是否是工具调用动作 m re.search(rACTION: (\{.*?\}), reply, re.S) if m: try: action json.loads(m.group(1)) except json.JSONDecodeError: messages.append({role: assistant, content: reply}) messages.append({role: user, content: 你输出的 ACTION 格式不正确请重新输出。只输出一行 ACTION: {...}}) continue tool_result execute_tool(action) # 调用检索工具 messages.append({role: assistant, content: reply}) messages.append({role: tool, content: json.dumps(tool_result, ensure_asciiFalse)}) continue # 没有 ACTION说明模型准备直接回答 return reply, messages return 抱歉经过多次检索仍未能确认答案。你可以把条件说得更具体一点。, messages这段代码里最容易被忽略的是max_steps上限。如果没有它模型一旦在某个问题上反复调用同一个工具整个请求就会被卡死。我实际测试时遇到过模型连续四次调用 search_books 但参数完全一样的情况当时就是靠这个循环上限把它截停转成了兜底回答。4.4 模型幻觉的防护让工具结果纠正模型Agent 项目里最难防的就是幻觉——模型会一本正经地编出根本不存在的书。我在检索推荐场景吃过一次亏模型在没查数据库的情况下凭空给我推荐了一本《人工智能简史第 3 版》而数据库里根本没有这本书。解决办法有两个都在循环逻辑里体现。第一我在 system prompt 里强制要求当你掌握的相关书目不足时必须调用 search_books 查询之后再回答等于把工具使用变成硬性任务。第二我在工具返回的 JSON 里把命中数量total放在显眼位置同时指导模型如果 total 为 0必须坦白告诉用户暂无匹配不许编造替代书目。这两招配合之后幻觉问题在检索类场景基本绝迹。5. 检索与排序让 Agent 推荐的图书真正靠谱5.1 关键词提取与多条件组合查询当 Agent 决定调用 search_books 工具时工具层拿到的是一个 JSON 对象比如{keyword: Python, category: 计算机, min_rating: 7.5, year_from: 2019, sort: hot}。我的工具函数需要把它变成一条合理的 SQL。用 Python 的 sqlite3 模块动态拼 SQL 时有一个经验之谈一定要用参数占位符千万不要用 f-string 直接拼值。SQL 注入之类的安全问题先不说光是引号嵌套和类型转换就会让人抓狂。正确写法是def search_books(params: dict) - dict: conditions [] args [] if params.get(keyword): # 模糊匹配书名、作者、标签等多个字段 like f%{params[keyword]}% conditions.append((title LIKE ? OR author LIKE ? OR tags LIKE ?)) args.extend([like, like, like]) if params.get(category): conditions.append(category ?) args.append(params[category]) if params.get(min_rating): conditions.append(rating ?) args.append(float(params[min_rating])) if params.get(year_from): conditions.append(pub_year ?) args.append(int(params[year_from])) where AND .join(conditions) if conditions else 11 sql fSELECT * FROM books WHERE {where} ORDER BY hot_score DESC LIMIT 10 # ... 执行查询返回结果列表和总数这里我故意把 SQL 模板里非值部分用 f-stringWHERE 结构值部分全部用参数占位符算是折中方案。因为有11这种默认条件即使 params 为空也能安全返回全表前 10 本书不会爆 SQL 语法错误。5.2 排序策略热度、评分与年份的博弈图书检索的排序直接影响用户感知。实际做下来我发现单一维度的排序都不够好只按评分排容易把《甘地自传》这种高分冷门书顶到第一名用户想要的热门入门 Python 书反而排到后面。只按热度排又会把广告投放多的书库书长期霸榜。我的最终方案是加权综合分用一个很简单的公式final_score rating * 0.5 hot_score_norm * 0.3 recency_score * 0.2其中 hot_score_norm 是将热度映射到 0~10 区间recency_score 根据出版年份计算比如 2023 年出版给 10 分每早一年减 0.5 分下限 0 分。这个公式写死在工具函数里把sort参数接到分支判断中。如果 Agent 发现用户明确说了要最新的工具层就会改用纯年份排序如果用户说要口碑好的就改为纯评分排序没说具体要求时走综合分排序。这样做的好处是排序偏好与自然语言直接挂上了钩。5.3 上下文记忆多轮对话里的连贯推荐Agent 和普通接口最大不同在于它天然支持多轮对话。在我这个系统里记忆并非单独实现而是靠把 message_history 传入 run_agent 函数完成的。前端每轮把用户消息和 Agent 回答都追加进历史数组在下一次提问时一并发送给后端。但这个懒人方案的坑很快就暴露了对话轮数一多messages 里塞了太多历史内容大模型的上下文窗口被占满。实际测试到第 15 轮左右模型开始忘记最早几轮里用户提到过的偏好。我目前的处理是设定一个简单的滑动窗口只保留最近 6 轮对话内容和系统 prompt 中的长期指令超出部分直接截掉。如果你想要更优雅的方案可以引入向量数据库做长期记忆那又是一个可以单独写一篇的深度话题。6. 前端交互与 SSE 流式渲染等 8 秒不如边生成边显示6.1 为什么必须用 SSE大模型响应慢的体验优化我们做的是 Agent 系统一次完整推理可能包含两三轮模型调用和工具查询所以用户从提问到看见回答延迟很容易超过 8 秒。如果用传统 HTTP 请求等接口返回后再一次性渲染用户会看到一个白屏页面长达十秒这在 2025 年的互联网环境下就是劝退体验。SSEServer-Sent Events是一种基于 HTTP 的单向流式推送协议服务器通过一个长连接分块发送文本浏览器用内置的 EventSource 接口逐块接收渲染。对比 WebSocketSSE 的优势在于实现极简单无需额外协议握手原生支持断线重连EventSource 自带自动重连功能。唯一限制是服务端到客户端单向但大模型流式输出正好是单向场景。我选它没有任何犹豫。6.2 FastAPI 服务端 SSE 实现核心思路是前端请求对话接口时传streamtrue后端在 Agent 循环最外层拿到模型流式响应按行转发给浏览器。为了让检索中的中间过程也可视化我在 SSE 事件里设计了两种类型searching事件表示 Agent 正在调工具前端可以展示正在检索书中...的转圈动画content事件则是模型的最终作文内容增量。from fastapi import FastAPI from fastapi.responses import StreamingResponse import json app FastAPI() app.post(/api/chat) async def chat_api(payload: dict): user_input payload[message] history payload.get(history, []) async def event_generator(): # 阶段一Agent 内部推理非流式处理工具调用 # 此处省略完整循环细节跑通 run_agent 后拿到最终消息文本 final_text, _ run_agent(user_input, history) # 阶段二将最终文本以 SSE content 事件流式返回 for chunk in split_text(final_text, size20): yield fevent: content\ndata: {json.dumps({delta: chunk}, ensure_asciiFalse)}\n\n yield event: done\ndata: {}\n\n return StreamingResponse(event_generator(), media_typetext/event-stream)实际项目我会在阶段一和阶段二之间加一层先用 history 拿到 Agent 完整回答文本最后一次性流式输出给用户。严格说这不是真流式因为它内部还是先等 Agent 跑完才输出。如果你想让整个 Agent 决策过程都流式化模型边思考边把文字推给用户那需要把 run_agent 内部的 chat 调用全部改成 streamTrue并把中间思考摘要也转换成 SSE 事件复杂度会上升一截。作为第一个项目先保证对外输出流式体验已经能收获 90% 的体验收益。6.3 前端 EventSource 接入与 abort 机制前端我用了最朴素的单页面 HTML JavaScript没有引框架方便你看懂链路。EventSource 的接入代码非常短const es new EventSource(/api/chat/stream?message encodeURIComponent(msg)); es.addEventListener(content, (e) { const delta JSON.parse(e.data).delta; document.getElementById(answer).textContent delta; }); es.addEventListener(done, () { es.close(); });EventSource 有个限制它只支持 GET 请求而我刚才的接口是 POST。两种解决方式开发时图省事把接口改为 GET参数放 QueryString生产环境则建议用 fetch ReadableStream 手动解析 SSE 格式或者用 POST text/event-stream 的方式让后端把连接改成可读取流。我这个教学项目选择了 GET EventSource代码量最少、最直观。前端还必须处理用户不想等了的情况。EventSource 提供了close()方法能断开连接但断开后服务端生成任务还在跑白白消耗 API 额度。我实测用 AbortController 解决会更好——先把 EventSource 包在一个 AbortSignal 里前端点停止时既关连接又发一个取消请求给后端后端在循环每次迭代前检查取消标志一旦收到取消就立刻中断循环抛异常释放资源。前端关键代码let currentES null; let abortCtrl null; function stopGeneration() { if (abortCtrl) abortCtrl.abort(); if (currentES) currentES.close(); } function sendMessage() { abortCtrl new AbortController(); const msg inputEl.value; currentES new EventSource(/api/chat/stream?message${encodeURIComponent(msg)}, { signal: abortCtrl.signal }); // 事件监听同上 }这套SSE abort 取消的组合是我实际踩过很多坑后沉淀下来的最佳实践。给初学者的建议很直接第一次开发大模型应用先把 streaming 跑通再补 abort前者决定体验后者决定资源浪费和稳定性。7. 完整演示场景与效果说明7.1 场景一模糊查询说人话式提问我拿真实用户需求测了一下。输入我想找一本讲机器学习的书但是我是零基础不要太难的那种。Agent 第一轮思考把零基础不要太难映射成搜索参数keyword机器学习、tags 匹配入门同时把 sort 设为 hot。工具返回了《机器学习实战基于 Scikit-Learn、Keras 和 TensorFlow》和《Python 机器学习基础教程》等书。第二轮模型判断结果合适直接生成推荐语特别强调这本书从安装环境讲起适合没有算法基础的人。整个过程用户没有输入任何筛选条件体验和跟真人图书管理员对话几乎无差别。7.2 场景二带明确限制条件的复杂查询输入2019 年之后出版的 Python 入门书评分 8 分以上最好讲爬虫的。这个句子里有三个硬条件和两个软偏好。Agent 解析后生成的 action 参数为{keyword: Python, category: null, min_rating: 8.0, year_from: 2019, tags: [入门, 爬虫]}。工具层按年份、评分、标签逐一过滤最终返回了 6 本候选。随后 Agent 又注意到用户对爬虫有偏好但数据库里 2019 年以后符合条件的爬虫书评分都在 8 以下于是它做了个很聪明的让步降低爬虫标签的优先级把评分 7.8 的《Python 爬虫开发与项目实战》放在推荐列表第二位并在回答里主动说明这本书评分稍低于 8但它是最贴近爬虫方向的选择。这个主动让步解释取舍的行为正是 Agent 比普通接口加分的地方。7.3 场景三多轮追问第一轮有没有适合做睡前读物的短篇科幻 Agent 检索到《银河边缘》《时间移民》等。第二轮有中国作者写的吗 Agent 根据历史消息中的科幻、短篇、合集条件追加author_country中国的过滤条件把原来的推荐里剔除了外国作家最终在上一轮基础上给出《宇宙尽头的餐馆》和《时间移民》的筛选结果并且回复里说这两本在上一轮推荐的短篇科幻中属于中国作者作品。如果去掉历史传递第二轮的上一轮这个概念根本无法成立。这个场景是检验 Agent 多轮记忆能力的最佳案例。8. 实战踩坑记录这些坑我替你踩过了8.1 SSE 连接被网关切断页面空白第一次跑起来后我发现流式输出偶尔在中间就断了。排查了很久发现根本不是后端代码的问题而是 Nginx 默认对 HTTP 响应有 60 秒超时SSE 长连接超过这个时间就被掐断。解决办法是在 Nginx 配置里给对应的 location 设置proxy_read_timeout 3600s;同时关闭缓冲proxy_buffering off;。如果你在本地直接跑 uvicorn 大概率碰不到这个问题但一部署到服务器就会遇到我提前写出来省得你们再花两天排查。第二个 SSE 相关坑是编码。某些环境下默认media_typetext/event-stream会携带 charset浏览器解析时如果后端返回的中文被截断会出现半个字符乱码。我的解决办法是在 yield 时对 delta 做 UTF-8 明确编码以及每次发送前把字符串切在字符边界上而不是按字节切。8.2 大模型返回的 JSON 不稳定Agent 循环里最脆弱的点是解析模型输出的 ACTION 行。即使我喂了严格的格式示例模型偶尔也会输出多行 JSON、末尾多个逗号、或者在 ACTION 前面加一句好的让我查询一下。正则一开始写的是rACTION: (.*)结果把后面整段文字都吞进了 JSON 解析器报错如家常便饭。我最终的方案是双保险正则限制只抓第一个花括号到最后一个花括号之间的内容同时解析失败时把那轮回复原样追加进 messages 并提示你上次输出的格式不对请重新输出一行纯 JSON 的 ACTION引导模型自我纠正。日志显示模型第二次输出格式的正确率几乎是 99%。8.3 Agent 陷入死循环API 费用哗哗流模型在少部分问题上会不断重复同一个动作虽然设置了 max_steps8 能阻止无限循环但 8 轮模型调用已经是一笔不小的 token 开销。有一次我忘记设置上限用户问了个模糊问题模型连续调了三十多次工具后台账单肉眼可见地涨。解决方案除了降 max_steps 之外我额外加了一个工具调用去重记录最近五次的调用参数如果发现 json.dumps 后的参数字符串重复就在 messages 里追加一条提示你已经用完全相同的参数查询过了请基于已有结果回答不要重复查询。这一步训下来冗余调用减少一半以上速度也快了。8.4 虚拟环境与依赖版本冲突Python 项目最常见的慢性病就是环境和版本。我开发到后期引入新版 openai SDK 时它把对 pydantic 的版本要求升到了 v2直接导致项目中另一个依赖崩溃。后来我养成了一个习惯requirements.txt 里固定精确版本号甚至同时导出一份requirements-lock.txt记录所有传递依赖的版本每次换机器部署先按锁文件恢复环境。对初学者这里只给一条忠告pip freeze requirements.txt是最省事的锁依赖办法别嫌它丑。8.5 上下文窗口撑爆后的静默错误还有个隐蔽问题值得单独提醒当 messages 内容超过模型上下文上限时不同 API 的行为不一样有的是直接 400 报错有的却会静默地丢掉最早的几条消息导致 Agent 明明有历史记忆却突然失忆。我线上就碰到过用户连续聊了 20 多轮后Agent 突然问你之前说过要找什么书吗排查半天才发现是上下文窗口在作怪。解决办法还是那个滑动窗口截断历史主动把早期轮次丢弃。9. 这个项目还能往哪些方向扩展项目跑通一周之后我自己做了三个方向的小扩展都有明显的效果提升。第一个是数据库里加入书籍全文简介并在此基础上用简单的关键词抽取替代标签匹配。原先 tags 字段是人工维护的覆盖不全扩展后允许 Agent 把用户的关键词和 intro 字段做 LIKE 匹配召回率提升显著。更进一步可以引入向量化embedding对 intro 建索引实现语义搜索而非关键词匹配那基本就是一个 Mini RAG 系统了。第二个是多 Agent 协作。我把检索和推荐文案生成拆成了两个独立 Agent检索 Agent 只负责和数据库打交道推荐 Agent 只负责把检索结果写成生动文字。两者通过消息队列传递结构化数据这样一来检索逻辑和表达逻辑解耦任何一个模型的升级替换都不影响另一个。CrewAI 官方框架做的就是这个事亲手拆过一遍之后再回头用框架会很有掌控感。第三个扩展方向是把系统接进微信生态或者飞书机器人。后端已经是 FastAPI加一个 webhook 入口即可前端界面替换成聊天软件的消息收发。我做了个简单的飞书接入团队内部试用后发现让 AI 查书的使用频率比网页版高出整整两倍因为大家不用再专门打开浏览器。最后再分享一个小技巧开发时你一定会反复调试 Agent 行为别急着每次都跑前端页面。我给自己留了一个 CLI 入口直接在终端里输入一句自然语言就能看到 Agent 完整思考过程打印每一步的 thought、action、observation排查问题效率至少翻一倍。等你把 Agent 推理逻辑调顺再接前端、完整体验流式输出整个开发过程会非常顺畅。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Agent of Empires配置完全参考:config.toml与Profile关键设置详解 2026/9/27 2:32:20

Agent of Empires配置完全参考:config.toml与Profile关键设置详解

Agent of Empires配置完全参考:config.toml与Profile关键设置详解 【免费下载链接】agent-of-empires Manage multiple Claude Code, OpenCode agents from either TUI or Web for easy access on mobile. Also supports Mistral Vibe, Codex CLI, Gemini CLI, Pi.d…

阅读更多 →
Python手写机器学习算法:从逻辑回归到多分类实战指南 2026/9/27 2:32:13

Python手写机器学习算法:从逻辑回归到多分类实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
<数据集>煤矸石识别数据集<目标检测> 2026/9/27 2:32:13

<数据集>煤矸石识别数据集<目标检测>

一、数据集简介 YOLO与VOC格式的煤矸石数据集,适用于YOLO系列、Faster Rcnn、SSD等模型训练,类别:Coal、Rock,图片数量3091。 文件中包含图片、txt标签、指定类别信息的yaml文件、xml标签,已将图片和txt标签划分为训练…

阅读更多 →
Skyline渲染引擎实战:从卡顿到稳如老狗的跨端性能优化 2026/9/27 2:31:48

Skyline渲染引擎实战:从卡顿到稳如老狗的跨端性能优化

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
单节锂保IC如何快速定制?CM100E新国标方案解析 2026/9/27 2:31:41

单节锂保IC如何快速定制?CM100E新国标方案解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
帝国企业网站模板速查手册:解决没人访问的选型真相 2026/9/27 2:31:35

帝国企业网站模板速查手册:解决没人访问的选型真相

帝国企业网站模板速查手册:解决没人访问的选型真相 网站做好了没人访问,这是无数设计师转前端时最绝望的时刻。你精心调色的页面,在搜索引擎眼里可能只是一堆乱码。别急,这份 帝国企业网站模板 的 速查手册…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉