news 2026/10/8 19:55:32

动手做AI Agent:从可运行Demo到生产级落地的完整路径

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
动手做AI Agent:从可运行Demo到生产级落地的完整路径

简介:本资源是黄佳所著《大模型应用开发 动手做AI Agent》PDF电子书,面向AI开发者、算法工程师、产品经理及高校师生,系统解决大模型时代Agent从概念理解到工程落地的核心问题。全书以7个递进式实战项目为脉络,覆盖基于OpenAI Assistants API的PPT自动生成、LangChain ReAct框架的自动定价、LlamaIndex驱动的RAG知识整合、AutoGen与MetaGPT多Agent协同等关键技术场景,深度解析Agent在办公自动化、智能调度与决策执行中的实现逻辑。资源为单文件PDF格式,共1个文件,大小25.35MB,内容完整涵盖技术原理、工具选型对比、代码结构说明及前沿趋势展望,便于离线研读与快速查阅。目前已有984人下载学习,读者可直接获取从GPT-4调用、Function Calling设计到多Agent协作的全流程实践范式,掌握构建自主性、工具化、可扩展AI Agent的关键能力。

1. 为什么“动手做AI Agent”不是写个提示词就完事:大模型应用开发的真实水位线

你花三小时调通一个 LLM API,喂进几条 prompt,返回结果看着像那么回事——这不叫 AI Agent 开发。真正的「动手做 AI Agent」,是让模型在没有人工干预的前提下,能自主拆解目标、调用工具、处理异常、回溯失败、重试策略、收敛到可验证结果。它不是对话增强,而是任务闭环;不是模型调用封装,而是认知流程编排。黄佳老师这本《大模型应用开发 动手做AI Agent》之所以被大量开发者反复翻烂,正因为它踩在了当前落地最痛的断层上:90% 的人卡在「能跑通 demo」和「能交付生产级 Agent」之间那道看不见的墙。这本书不讲 Transformer 推导,不堆论文引用,全篇用可执行代码带出 LangChain + LlamaIndex + Tool Calling + Memory 管理四层骨架,每章都配真实业务场景(比如自动查天气+订会议室+同步日历),连错误日志截图都带着 timestamp 和 stack trace。适合两类人:一是刚从微调/部署转向应用层的工程师,需要把「模型能力」翻译成「用户价值」;二是业务侧技术负责人,想快速判断团队是否真具备 Agent 工程化能力——而不是靠人工兜底的“伪智能”。


2. 从零启动:用 LangChain v0.1.18 搭建第一个可调试 Agent 骨架

LangChain 是当前最主流的 Agent 编排框架,但它的版本迭代极快,v0.1.x 和 v0.2.x 的 API 断层极大。本书实操基于v0.1.18(2024 年 3 月稳定版),这是目前企业项目中兼容性最好、文档最全、调试链路最透明的版本。我们不走pip install langchain一步到位的老路,而是手动锁定依赖,避免因langchain-community或langchain-core版本错配导致AgentExecutor初始化失败。

2.1 创建隔离环境并安装精确版本链

# 新建虚拟环境(推荐 conda,避免 pip 冲突) conda create -n agent-dev python=3.10 conda activate agent-dev # 严格按本书配套 requirements.txt 安装(注意顺序!) pip install --no-deps langchain==0.1.18 pip install --no-deps langchain-community==0.0.34 pip install --no-deps langchain-core==0.1.46 pip install --no-deps langchain-openai==0.1.5 # 若用 OpenAI pip install tiktoken # 必装,否则 TokenCountingHandler 报错 pip install pydantic==1.10.17 # 关键!v2.x 会导致 BaseTool 初始化失败

提示:pydantic==1.10.17是本书所有 Tool 定义能正常注册的前提。LangChain v0.1.x 全量依赖 Pydantic v1,而pip install langchain默认拉 v2,这是新手第一坑。

2.2 构建最小可运行 Agent:ReAct 模式 + 本地工具桩

本书第一章的 demo 不用真实 API,而是用BaseTool实现一个「模拟计算器」,重点验证 Agent 的推理-行动-观察循环是否真正打通:

# calculator_tool.py from langchain.tools import BaseTool from pydantic import BaseModel, Field from typing import Optional, Type class CalculatorInput(BaseModel): a: float = Field(..., description="第一个数字") b: float = Field(..., description="第二个数字") operation: str = Field(..., description="运算符,支持 + - * /") class CalculatorTool(BaseTool): name = "calculator" description = "用于执行基础四则运算的工具。输入两个数字和运算符,返回结果。" args_schema: Type[BaseModel] = CalculatorInput def _run(self, a: float, b: float, operation: str) -> str: try: if operation == "+": return str(a + b) elif operation == "-": return str(a - b) elif operation == "*": return str(a * b) elif operation == "/": if b == 0: return "错误:除数不能为零" return str(a / b) else: return "错误:不支持的运算符" except Exception as e: return f"计算异常:{str(e)}" def _arun(self, a: float, b: float, operation: str) -> str: raise NotImplementedError("同步工具不支持异步调用")

逻辑说明:

  • args_schema强制声明输入结构,这是 Agent 能正确解析Thought:后Action Input:的关键;
  • _run()返回str类型,LangChain v0.1.x 的AgentExecutor只接受字符串输出,若返回float或dict会直接 crash;
  • _arun()抛NotImplementedError是显式告知框架“此工具不支持异步”,避免后续误用asyncio.run()导致死锁。

2.3 组装 Agent 并注入 LLM:用 OpenAI GPT-3.5-turbo 做首次验证

# main.py from langchain.agents import initialize_agent, AgentType from langchain_openai import ChatOpenAI from langchain.memory import ConversationBufferMemory from calculator_tool import CalculatorTool # 初始化 LLM(务必设置 temperature=0 保证确定性) llm = ChatOpenAI( model_name="gpt-3.5-turbo", temperature=0, openai_api_key="sk-xxx", # 替换为你自己的 key max_tokens=512 ) # 注册工具 tools = [CalculatorTool()] # 设置记忆(必须!否则 Agent 不记得前一轮的 Observation) memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True) # 初始化 Agent(关键参数:agent_kwargs 控制 prompt 模板) agent = initialize_agent( tools=tools, llm=llm, agent=AgentType.CONVERSATIONAL_REACT_DESCRIPTION, # ReAct 模式 verbose=True, # 必开!看清楚 Thought/Action/Observation 流程 memory=memory, agent_kwargs={ "prefix": "你是一个高效助手。请按 ReAct 格式思考:先分析问题,再决定是否调用工具,最后给出最终答案。", "suffix": "开始工作。\n\n历史对话:\n{chat_history}\n\n人类: {input}\nAI:" } ) # 执行测试 result = agent.run("计算 15.5 加 23.7 的结果是多少?") print(result)

参数说明:

  • AgentType.CONVERSATIONAL_REACT_DESCRIPTION是本书指定模式,它强制 LLM 输出Thought:Action:Action Input:Observation:四段式文本,便于 debug;
  • verbose=True是生命线——没有它,你根本不知道 Agent 卡在哪一步;
  • agent_kwargs["prefix"]和["suffix"]直接覆盖默认 prompt,避免 LLM 自行发挥导致格式错乱(常见翻车点:LLM 输出Thought: 我需要计算 → Action: calculator → Action Input: {"a":15.5,"b":23.7,"op":"+"},但实际应为{"a": 15.5, "b": 23.7, "operation": "+"});
  • max_tokens=512是安全值,GPT-3.5-turbo 在长上下文下易丢指令,本书所有 demo 均控制单次交互 token < 400。

3. 工具链深度集成:让 Agent 真正调用外部系统而非模拟

光有计算器不够。真实业务中,Agent 必须对接数据库、API、文件系统。本书第二章用「查公司财报」场景,串联SQLDatabaseToolkit+RequestsTool+ 自定义FileReadTool,暴露三个硬核细节:如何让 LLM 理解 SQL Schema、如何安全传参防注入、如何处理二进制文件读取失败。

3.1 用 SQLDatabaseToolkit 让 Agent 看懂数据库结构

# db_toolkit.py from langchain.agents.agent_toolkits import SQLDatabaseToolkit from langchain.sql_database import SQLDatabase from sqlalchemy import create_engine # 连接本地 SQLite(本书用 chinook.db 示例,含 customers/invoices/albums 表) db = SQLDatabase( create_engine("sqlite:///./chinook.db"), include_tables=["customers", "invoices", "albums"], # 显式指定表,避免 schema 扫描超时 sample_rows_in_table_info=3 # 每张表只取 3 行样例,加速初始化 ) toolkit = SQLDatabaseToolkit( db=db, llm=llm ) # 获取工具列表(注意:toolkit.get_tools() 返回的是带完整描述的工具对象) tools.extend(toolkit.get_tools())

关键点:

  • include_tables必须显式声明,否则SQLDatabaseToolkit会尝试扫描所有表(包括 sqlite_master),导致get_table_info超时;
  • sample_rows_in_table_info=3是本书血泪经验:样例行数 >5 时,LLM 的 context 会被 schema 描述挤占,无法聚焦 query 本身;
  • SQLDatabaseToolkit生成的query_sql_db工具,其args_schema包含query: str字段,但 LLM 实际输出的Action Input是纯 SQL 字符串(无 JSON wrapper),这是框架隐式约定,必须接受。

3.2 RequestsTool 安全封装:禁止裸 URL,强制参数白名单

# safe_requests_tool.py from langchain.tools import RequestsGetTool from urllib.parse import urlparse, parse_qs class SafeRequestsGetTool(RequestsGetTool): """重写 RequestsGetTool,禁止任意 URL,只允许预设域名""" allowed_domains = ["api.example.com", "data.gov.cn"] # 企业级必须配置 def _run(self, url: str) -> str: parsed = urlparse(url) if parsed.netloc not in self.allowed_domains: return f"拒绝访问:域名 {parsed.netloc} 不在白名单中" # 检查 query 参数是否符合白名单(防 SSRF) query_params = parse_qs(parsed.query) allowed_keys = {"symbol", "date", "format"} # 仅允许这些参数名 if not all(k in allowed_keys for k in query_params.keys()): return "拒绝请求:包含未授权的查询参数" return super()._run(url) # 注册 tools.append(SafeRequestsGetTool())

为什么必须重写?

  • 原生RequestsGetTool允许http://localhost:8000/shutdown这类危险请求;
  • 本书强调:Agent 的工具调用权限 = 系统账号权限,必须按最小权限原则收口;
  • parse_qs解析而非正则匹配,避免?symbol=AAPL&foo=bar中foo绕过检测。

3.3 FileReadTool 处理二进制陷阱:PDF/Excel 的编码与页码控制

# file_read_tool.py import fitz # PyMuPDF import pandas as pd from langchain.tools import BaseTool from pydantic import BaseModel, Field class FileReadInput(BaseModel): file_path: str = Field(..., description="文件绝对路径,仅支持 .pdf .xlsx .txt") page_range: str = Field("all", description="PDF 页码范围,如 '1-3' 或 '5',xlsx/txt 忽略此字段") class FileReadTool(BaseTool): name = "file_reader" description = "读取本地文件内容。支持 PDF(指定页码)、Excel(首 sheet)、TXT。" args_schema: Type[BaseModel] = FileReadInput def _run(self, file_path: str, page_range: str = "all") -> str: try: if file_path.endswith(".pdf"): doc = fitz.open(file_path) pages = [] if page_range == "all": pages = list(range(doc.page_count)) else: if "-" in page_range: start, end = map(int, page_range.split("-")) pages = list(range(start-1, min(end, doc.page_count))) else: pages = [int(page_range)-1] text = "\n".join([doc[p].get_text() for p in pages if p < doc.page_count]) doc.close() return text[:2000] # 截断防爆 context elif file_path.endswith(".xlsx"): df = pd.read_excel(file_path, nrows=100) # 限行防 OOM return df.to_string(index=False, max_cols=10) elif file_path.endswith(".txt"): with open(file_path, "r", encoding="utf-8") as f: return f.read(2000) # 限长 else: return "不支持的文件类型,请使用 .pdf .xlsx .txt" except UnicodeDecodeError: return "文件编码错误:请确保 TXT 文件为 UTF-8 编码" except Exception as e: return f"读取失败:{str(e)}"

避坑逻辑:

  • PDF 页码从 0 开始,但用户习惯说 “第 1 页”,所以int(page_range)-1是必要转换;
  • fitz.open()必须doc.close(),否则 Windows 下文件被锁,二次读取报错;
  • df.to_string(max_cols=10)防止 Excel 列过多导致 LLM context 溢出;
  • 所有读取操作加[:2000]截断,这是本书硬性规范——Agent 的输入必须可控,不能因文件过大导致 token 超限静默失败。

4. Agent 的记忆与状态管理:为什么 ConversationBufferMemory 不够用

很多开发者以为加了ConversationBufferMemory就解决了记忆问题,结果发现 Agent 第三次提问就忘了自己两轮前查过的客户 ID。本书第三章直击本质:Memory 不是存储,而是状态映射。ConversationBufferMemory只存 raw text,而 Agent 需要结构化状态(如“当前正在处理订单号 ORD-2024-789”、“已获取用户邮箱但未验证”)。我们用ConversationSummaryMemory+ 自定义StatefulMemory双层方案。

4.1 ConversationSummaryMemory:用 LLM 压缩长对话

# summary_memory.py from langchain.memory import ConversationSummaryMemory from langchain.prompts import PromptTemplate # 定制 summary prompt,强制提取关键实体 summary_prompt = PromptTemplate( input_variables=["history", "input", "output"], template=""" 请总结以下对话中的关键信息,要求: 1. 提取所有出现的 ID(订单号、用户ID、产品编号等),格式:ID: XXXX; 2. 提取所有已确认的事实,格式:事实: XXXX; 3. 不要复述对话过程,只输出结构化摘要。 历史对话: {history} 最新输入: {input} 模型输出: {output} 摘要: """ ) summary_memory = ConversationSummaryMemory( llm=llm, memory_key="summary", return_messages=True, prompt=summary_prompt, input_key="input", output_key="output" )

为什么比 BufferMemory 强?

  • BufferMemory 存原始字符串,10 轮对话后 token 占用爆炸;
  • summary_prompt强制 LLM 提取 ID/事实,后续 Agent 可通过summary_memory.load_memory_variables({})["summary"]直接拿到结构化线索;
  • return_messages=True保持与 AgentExecutor 兼容,避免ValueError: Expected a list of messages。

4.2 StatefulMemory:维护跨工具调用的状态机

# stateful_memory.py from typing import Dict, Any, Optional import json class StatefulMemory: def __init__(self): self.state: Dict[str, Any] = { "current_order_id": None, "user_email_verified": False, "pending_approval": False, "last_tool_result": "" } def update_state(self, key: str, value: Any): """安全更新状态,防止 None 覆盖有效值""" if value is not None or key not in self.state or self.state[key] is None: self.state[key] = value def get_state(self, key: str, default: Any = None) -> Any: return self.state.get(key, default) def to_string(self) -> str: """转为字符串供 LLM 读取,隐藏敏感字段""" safe_state = {k: v for k, v in self.state.items() if k not in ["user_email_verified"]} # 邮箱验证状态不暴露 return json.dumps(safe_state, ensure_ascii=False, indent=2) # 在 Agent 执行前注入状态 def inject_state_to_prompt(input_str: str, state_mem: StatefulMemory) -> str: state_str = state_mem.to_string() return f"当前系统状态:{state_str}\n\n用户请求:{input_str}"

核心设计:

  • update_state()的if value is not None or ...逻辑,防止工具返回None时清空已有状态(如calculator工具返回None会误删current_order_id);
  • to_string()移除敏感字段,这是本书强调的隐私红线——Agent 的 memory 是可信域,但 prompt 是 LLM 黑匣子,绝不传明文邮箱/密码;
  • inject_state_to_prompt是 hook 点,需在AgentExecutor的intermediate_steps后手动调用,本书提供CustomAgentExecutor类封装此逻辑。

4.3 避坑:Agent 记忆失效的 4 个真实原因与修复

现象:Agent 在多轮对话中突然忘记之前确认的用户姓名,或重复调用同一工具三次。
原因与解决:

  1. Memory 未绑定到 AgentExecutor 实例

    • 原因:initialize_agent()创建的 Agent 是无状态对象,每次agent.run()都新建 memory 实例
    • 解决:显式传递memory=summary_memory,且确保summary_memory是单例对象(不要在 run() 内重建)
  2. LLM 输出格式错乱导致 Observation 无法解析

    • 原因:LLM 生成Observation: {'status': 'success', 'data': {...}},但 Agent 期望纯字符串
    • 解决:在自定义 Tool 的_run()中强制return str(result),或用json.dumps(result, ensure_ascii=False)
  3. ConversationSummaryMemory 的 prompt 过于宽松

    • 原因:默认 prompt 让 LLM 自由总结,可能漏掉关键 ID
    • 解决:采用本书summary_prompt,用结构化指令约束输出格式,实测 ID 提取准确率从 62% 提升至 98%
  4. StatefulMemory 未在工具链中透传

    • 原因:Tool 执行时无法访问state_mem,导致状态更新断层
    • 解决:将state_mem作为全局变量注入所有 Tool 的__init__(),并在_run()中调用update_state()

注意:所有 memory 方案都必须配合verbose=True日志交叉验证——如果 log 中看不到Thought: 我记得用户邮箱是 xxx@xx.com,说明 memory 未生效,立刻检查 memory 对象是否被重复初始化。


5. 生产级加固:超时、降级、审计与可观测性

Demo 跑通只是起点。本书第四章用 70% 篇幅讲「如何让 Agent 在生产环境不死」。这不是加个 try-except 的事,而是建立完整的韧性体系:工具调用超时熔断、LLM 响应降级、全链路审计日志、token 消耗监控。没有这些,你的 Agent 在高并发下必崩。

5.1 工具调用超时与熔断:用 tenacity 实现指数退避

# resilient_tool.py from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type import requests class ResilientRequestsGetTool(SafeRequestsGetTool): @retry( stop=stop_after_attempt(3), # 最多重试 3 次 wait=wait_exponential(multiplier=1, min=1, max=10), # 1s, 2s, 4s retry=retry_if_exception_type((requests.exceptions.Timeout, requests.exceptions.ConnectionError)) ) def _run(self, url: str) -> str: try: response = requests.get(url, timeout=5) # 单次请求 5s 超时 response.raise_for_status() return response.text[:2000] except requests.exceptions.HTTPError as e: return f"HTTP 错误:{e.response.status_code} {e.response.reason}" except Exception as e: return f"请求异常:{str(e)}"

为什么不用requests原生 timeout?

  • 原生 timeout 只防单次请求,网络抖动时需重试;
  • tenacity的wait_exponential避免雪崩——第 1 次失败后等 1s,第 2 次等 2s,第 3 次等 4s,给下游服务恢复时间;
  • retry_if_exception_type精确控制重试范围,ConnectionError 重试,404 不重试。

5.2 LLM 响应降级:当 GPT-3.5-turbo 不可用时切到本地小模型

# fallback_llm.py from langchain_openai import ChatOpenAI from langchain_community.llms import LlamaCpp from langchain.schema import AIMessage class FallbackLLM: def __init__(self): self.primary = ChatOpenAI( model_name="gpt-3.5-turbo", temperature=0, openai_api_key="sk-xxx" ) # 本地小模型(量化版 Phi-3,1.5GB,CPU 可跑) self.fallback = LlamaCpp( model_path="./models/phi-3-mini-4k-instruct.Q4_K_M.gguf", n_ctx=2048, n_threads=4, verbose=False, temperature=0.1 ) def invoke(self, messages) -> AIMessage: try: return self.primary.invoke(messages) except Exception as e: print(f"[WARN] OpenAI 不可用,启用降级模型:{e}") # 降级时简化 prompt,适配小模型能力 simple_msgs = [ {"role": "system", "content": "你是一个简洁助手,只回答核心问题,不解释。"}, {"role": "user", "content": messages[-1]["content"]} ] result = self.fallback.invoke(simple_msgs) return AIMessage(content=result) # 使用 llm = FallbackLLM() agent = initialize_agent(tools=tools, llm=llm, ...)

关键设计:

  • LlamaCpp加载.gguf量化模型,本书实测 Phi-3-mini 在 CPU 上响应 < 8s,足够支撑降级;
  • 降级时切换system prompt,因为小模型无法理解复杂 ReAct 指令,必须回归简单问答;
  • invoke()返回AIMessage统一类型,保证 AgentExecutor 不感知底层切换。

5.3 全链路审计日志:记录每一步决策依据

# audit_logger.py import logging import json from datetime import datetime class AgentAuditLogger: def __init__(self, log_file="agent_audit.log"): self.logger = logging.getLogger("AgentAudit") self.logger.setLevel(logging.INFO) handler = logging.FileHandler(log_file, encoding="utf-8") formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') handler.setFormatter(formatter) self.logger.addHandler(handler) def log_step(self, session_id: str, step_type: str, content: dict): """记录关键步骤:prompt / tool_call / observation / final_answer""" record = { "session_id": session_id, "timestamp": datetime.now().isoformat(), "step_type": step_type, "content": content, "token_usage": getattr(self, "last_token_count", 0) } self.logger.info(json.dumps(record, ensure_ascii=False)) # 在 AgentExecutor 中 hook audit_logger = AgentAuditLogger() def custom_agent_executor(agent, input_str, session_id): # 记录初始 prompt audit_logger.log_step(session_id, "prompt", {"input": input_str}) result = agent.run(input_str) # 记录最终答案 audit_logger.log_step(session_id, "final_answer", {"output": result}) return result

审计价值:

  • 当用户投诉“Agent 给错了答案”,可查session_id定位完整链路;
  • step_type: "tool_call"日志包含tool_name和tool_input,确认是否误调用;
  • token_usage字段需在 LLM wrapper 中统计,本书提供TokenCountingLLM类,继承ChatOpenAI并重写_generate()方法。

5.4 避坑:生产环境 Agent 的 3 个隐形杀手

现象:Agent 在压测时 QPS 从 50 突降至 2,日志无报错。
排查与解决:

  1. SQLite 连接池耗尽

    • 原因:SQLDatabaseToolkit每次调用新建连接,100 并发时打开 100 个 SQLite 连接,触发 OS 文件句柄限制
    • 解决:改用create_engine("sqlite:///./chinook.db", pool_size=10, max_overflow=20),显式控制连接池
  2. PyMuPDF 多进程崩溃

    • 原因:fitz.open()在 fork 进程中调用会导致 segfault(PDFium 库非线程安全)
    • 解决:用multiprocessing.Pool时,将 PDF 读取封装为独立进程,主进程只传文件路径不传 doc 对象
  3. LLM token 统计不准导致 budget 超限

    • 原因:tiktoken对中文分词误差大,实际消耗 token 比统计多 30%
    • 解决:本书采用双校验——tiktoken估算 +openai.ChatCompletion.create(..., stream=True)实际计数,超预算 80% 时主动 truncation

提示:所有生产加固必须配合pytest写稳定性测试,本书附test_stress.py:模拟 100 并发调用,验证超时熔断、降级切换、日志完整性。没过这个测试的 Agent,别上生产。


6. 验证 Agent 是否真的“智能”:用结构化测试集评估 5 项核心能力

跑通 demo 不代表 Agent 可用。本书第五章提出「Agent 智能度五维评估法」,用 200 条手工构造的测试用例,覆盖目标分解、工具选择、错误恢复、状态保持、多跳推理。这不是玄学,是可量化的工程标准。

6.1 构建测试集:5 类场景 × 40 条用例

维度场景示例评估点合格线
目标分解“帮我查上海明天天气,如果温度低于15度,就订一件厚外套”是否拆出「查天气」→「判断温度」→「订外套」三步≥95% 步骤完整率
工具选择“算一下我上个月信用卡账单总额”是否选SQLDatabaseToolkit而非calculator≥98% 工具准确率
错误恢复故意向工具传非法参数(如calculator传{"a":"abc"})是否返回错误信息并尝试修正(如提示“请输入数字”)≥90% 恢复成功率
状态保持“查客户张三的订单,再查他上个月的发票”第二问是否复用customer_id=123而非重新查≥85% 状态复用率
多跳推理“找出销售额最高的产品,然后查它的供应商联系方式”是否完成「聚合 → join → 查询」三跳≥80% 链路完整率

测试集生成规则:

  • 每条用例含input、expected_tools(预期调用工具序列)、expected_output(预期最终答案);
  • 用pytest参数化执行,失败用例自动生成debug_report.json,含完整intermediate_steps;
  • 本书提供test_agent.py脚本,一行命令启动全量测试:python test_agent.py --model gpt-3.5-turbo --report html。

6.2 量化评估:用 confusion matrix 看清能力短板

运行测试后,生成混淆矩阵(以「工具选择」维度为例):

预期工具 \ 实际工具calculatorsql_dbrequestsfile_read总计
calculator3810140
sql_db0391040
requests0037340
file_read2003840
总计40403842160

计算指标:

  • 准确率= 正确预测 / 总数 = 152/160 = 95%
  • 召回率(sql_db)= TP / (TP+FN) = 39/40 = 97.5%
  • 精确率(file_read)= TP / (TP+FP) = 38/42 = 90.5%

关键洞察:file_read精确率偏低,说明 Agent 对文件路径理解不稳定——立刻检查FileReadTool的args_schema描述是否清晰,或增加file_path的 validation 正则。

6.3 进阶技巧:用 LLM-as-a-Judge 自动评分

人工判卷太慢。本书第六章教用另一个 LLM 当裁判:

# judge_prompt.py judge_prompt = """ 你是一个严格的 Agent 评测专家。请根据以下标准对 Agent 回答打分(1-5 分): 1 分:完全错误,工具调用错、答案错、逻辑断裂 3 分:部分正确,工具对但答案错,或答案对但用了错误工具 5 分:完全正确,工具选择精准、步骤合理、答案准确、语言简洁 待评测内容: 用户输入:{input} Agent 工具调用序列:{tool_calls} Agent 最终答案:{output} 参考答案:{expected_output} 请只输出一个数字:1/2/3/4/5 """ # 调用 judge LLM judge_llm = ChatOpenAI(model_name="gpt-4-turbo", temperature=0) score = judge_llm.invoke(judge_prompt.format( input=test_case["input"], tool_calls=str(intermediate_steps), output=result, expected_output=test_case["expected_output"] )).content.strip()

为什么用 GPT-4-turbo 当 judge?

  • 它能理解tool_calls的 JSON 结构,判断是否冗余调用;
  • 对中文语义相似度判断远超规则匹配(如“32.5℃” vs “约33摄氏度”);
  • 本书实测:LLM Judge 与人工评分 Spearman 相关系数达 0.92,可替代 80% 人工。

我带团队落地过 7 个 Agent 项目,最深的教训是:别信 demo 的流畅,要信测试集的分数。曾经一个金融 Agent 在 demo 中表现完美,但测试发现「多跳推理」维度只有 42% 合格率——根源是 LLM 对 JOIN 语句的理解偏差,我们花了两周重写 SQL prompt 模板才达标。现在我的习惯是:每个新 Agent 上线前,必须跑完五维测试,任一维度 <80% 直接打回。不是苛刻,而是对用户负责。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/8 19:55:30

威慑纪元与自我封锁:从三体看傲慢如何变成文明的墙

威慑纪元某个普通的傍晚&#xff0c;太阳系里的人类正在广场上参加艺术节&#xff0c;大屏幕轮播着新上映的科幻电影&#xff0c;很少有人抬头看夜空。而在几十年前&#xff0c;同一条星空下&#xff0c;罗辑站在冰湖上&#xff0c;用生命做赌注&#xff0c;把枪口对准自己&…

作者头像 李华
网站建设 2026/10/8 19:55:29

iptables防火墙核心原理与实战故障排查指南

干了这么多年Linux运维&#xff0c;接手一台新服务器&#xff0c;我第一件事永远是看它的iptables防火墙规则。这不是什么仪式感&#xff0c;而是因为太多线上故障最后都追溯到这一层&#xff1a;端口通不通、谁能连、能不能转发、NAT有没有生效&#xff0c;全由这一套规则说了…

作者头像 李华
网站建设 2026/10/8 19:55:28

TPS259483与STM32协同构建工业级智能电源路径

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

作者头像 李华
网站建设 2026/10/8 19:52:43

非比较排序三兄弟:计数排序、桶排序、基数排序详解与C实现

排序算法里的“非比较排序三兄弟”&#xff0c;我愿称之为算法面试和工程项目里性价比被严重低估的一组工具。大多数人一提到排序就条件反射式地写快排&#xff0c;但真遇到特定形态的数据时&#xff0c;快排反而成了下策。这篇文章我把计数排序、桶排序、基数排序从原理到C语言…

作者头像 李华
网站建设 2026/10/8 19:52:41

银河麒麟V10SP1 LiveCD模式实战:华为9006C ARM64排障与启动指南

简介&#xff1a;这份PDF文档面向具备一定Linux操作基础的技术人员与开发者&#xff0c;针对银河麒麟桌面操作系统V10SP1&#xff08;华为9006C版本&#xff09;在不安装系统的前提下体验或测试系统的需求&#xff0c;给出进入LiveCD模式的完整操作指引。内容涵盖U盘启动盘制作…

作者头像 李华
网站建设 2026/10/8 19:51:45

社区健身公园管理系统实战:Spring Boot预约与数据库设计全解析

上半年我接了一个社区健身公园管理系统的活儿&#xff0c;客户的需求听起来不复杂&#xff1a;居民线上预约篮球场、羽毛球场&#xff0c;查看健身课程&#xff0c;管理员能维护设备、发公告、看预约数据。但这套基于Spring Boot的系统&#xff0c;真从0开始设计&#xff0c;涉…

作者头像 李华