1. 项目概述:这不是在搭积木,而是在重建感知神经
“从工具调用到模型输入:把 Agent 的观测链路接起来”——这个标题乍看像一句技术黑话,但拆开来看,它直指当前智能体(Agent)开发中最常被忽略、却最致命的断层:感知失真。我带过好几个模拟项目X的开发小组,发现超过七成的Agent在真实环境里“反应迟钝”“答非所问”“反复犯错”,根源从来不是大模型不够强,而是它“看到的世界”和真实世界之间,隔着一层没人认真校准的“玻璃”。这层玻璃,就是观测链路。
所谓观测链路,指的是从外部系统(比如数据库、API、传感器、用户界面)产生原始数据,到这些数据被清洗、结构化、标注、压缩、编码,最终以合适格式喂给大模型推理模块的整条通路。它不是单个函数调用,而是一套有状态、有时序、有语义损耗的信号传输系统。标题里说的“接起来”,不是简单地把requests.get()的结果塞进model.chat(),而是要让Agent的“眼睛”真正看清、“耳朵”真正听懂、“触觉”真正感知——就像给一个刚出生的婴儿配一副精准校准的视觉矫正镜,而不是直接递给他一副模糊的老花镜。
核心关键词“工具调用”和“模型输入”看似是两个端点,实则代表了两种截然不同的思维范式:前者是工程师视角,关注接口协议、错误码、超时重试;后者是认知科学视角,关注信息熵、上下文窗口、token效率、语义保真度。本项目要做的,就是在这两个世界之间架一座桥,而且这座桥必须能双向承重、实时反馈、抗干扰。它适合三类人:正在调试Agent却总卡在“明明API返回了正确数据,模型就是不理解”的开发者;想把现有业务系统快速接入Agent架构的产品负责人;以及对Agent底层运行机制有刨根问底兴趣的研究型学习者。你不需要从零写大模型,但得清楚每一行日志背后,数据经历了怎样的变形与妥协。
2. 整体设计思路:为什么不能只靠Prompt Engineering?
2.1 观测链路断裂的四大典型症状
在某高校的Agent教学实验中,我们让A同学用标准RAG流程接入一个内部知识库API,结果出现一组非常典型的“链路失真”现象,我把它们归为四类,每一种都对应着链路中某个环节的失效:
症状一:时间戳幻觉
API返回的是{"last_updated": "2024-03-15T08:22:17Z"},但模型在回答中坚称“该政策已于2023年废止”。问题不在模型,而在预处理阶段,代码把ISO时间字符串原样拼进了Prompt,模型根本没能力解析这种格式,只能靠上下文猜——而猜错了。症状二:字段语义坍缩
数据库查出一条记录:{"status": "pending_review", "priority": 3, "assignee": "null"}。前端展示时,“pending_review”显示为“待审核”,“3”显示为“高优先级”,“null”显示为“暂无负责人”。但传给模型的却是原始字段值。模型看到"assignee": "null",就真的以为负责人叫“null”,后续所有推理都基于这个错误前提。症状三:噪声放大效应
调用一个天气API,返回包含15个字段的JSON,其中只有3个(温度、湿度、天气描述)对当前任务有用。但开发者图省事,把整个JSON字符串json.dumps(response)塞进Prompt。结果有效信息被淹没在大量元数据噪音里,模型注意力被"request_id": "abc123", "server_time": "..."等无关字段分散,关键信息识别率下降40%。症状四:状态漂移
Agent需要连续调用三个工具:先查订单状态,再查物流轨迹,最后查客服工单。但第二个工具返回的物流节点列表里,每个节点都带一个"estimated_arrival"时间戳。如果这个时间戳没有被统一转换为本地时区并标准化格式,第三个工具调用时,模型可能把“明天下午3点”误读为“今天下午3点”,导致整个决策链条偏移。
这四个症状,没有一个能靠改几行Prompt解决。它们暴露的是整个数据流管道的设计缺陷。所以我们的整体设计,必须跳出“模型为中心”的惯性,转向“观测保真度为中心”。
2.2 三层架构:解耦、校准、反馈
我们采用明确分层的三层架构,每层职责单一,接口清晰,避免任何“胶水代码”:
第一层:观测适配器(Observer Adapter)
这是链路的入口守门员。它的唯一任务,是把千奇百怪的外部数据源(REST API、SQL查询结果、CSV文件、甚至一段网页HTML)统一转换为一个轻量、结构化的中间表示(Intermediate Representation, IR)。IR不是JSON,而是一个Pythondataclass,例如:@dataclass class OrderStatusIR: order_id: str status: Literal["created", "shipped", "delivered", "cancelled"] status_display: str # 如"已发货" last_update: datetime # 已转为本地时区的datetime对象 estimated_delivery: Optional[datetime]关键点在于:IR强制要求所有字段都是语义明确、类型安全、时区一致、可序列化的。适配器内部封装了所有脏活:时间解析、枚举映射、空值处理、HTML清洗。开发者只需为每个数据源写一个适配器类,继承基类并实现
transform(raw_data: Any) -> IR方法。我们实测下来,一个中等复杂度的API适配器,代码不超过50行,但能消灭90%以上的字段语义坍缩问题。第二层:观测校准器(Observer Calibrator)
适配器输出的IR是干净的,但未必是“适合模型吃的”。校准器负责做三件事:压缩、标注、上下文化。- 压缩:自动剔除IR中与当前任务无关的字段。我们用一个轻量级的规则引擎,基于当前Agent的System Prompt和用户Query关键词,动态计算字段相关性得分。比如用户问“我的快递到哪了?”,校准器会保留
estimated_delivery,但丢弃order_id(除非Query里明确提到了单号)。 - 标注:为每个保留字段添加人类可读的语义标签。例如,
last_update字段会被标注为"订单最后一次更新时间",status_display被标注为"当前订单状态(中文)"。这些标签不是硬编码,而是通过一个小型的、可微调的文本生成模型(如Phi-3-mini)在线生成,确保语言风格与Agent整体保持一致。 - 上下文化:把IR嵌入一个最小上下文模板:“根据最新获取的订单信息:{IR_as_labeled_text}。请据此回答用户问题。” 这个模板长度固定,且经过大量测试,确保不会挤占模型的有效推理空间。校准器输出的,是一个精炼、带标签、带上下文的字符串,这才是真正“喂给模型”的食物。
- 压缩:自动剔除IR中与当前任务无关的字段。我们用一个轻量级的规则引擎,基于当前Agent的System Prompt和用户Query关键词,动态计算字段相关性得分。比如用户问“我的快递到哪了?”,校准器会保留
第三层:观测反馈环(Observer Feedback Loop)
这是最容易被忽略,却最具价值的一层。它不参与数据生成,而是监听模型的输出和后续行为,反向验证观测链路是否健康。具体做法是:在Agent每次调用工具后,记录下原始API响应、生成的IR、校准后的上下文字符串、模型的最终回复,以及用户对回复的显式/隐式反馈(如点击“不满意”、追问、或直接执行下一步操作)。我们用一个简单的规则匹配器,扫描模型回复中是否出现了IR中不存在的“幻觉事实”(比如IR里没有cancel_reason字段,但模型却编造了一个取消原因),或者是否遗漏了IR中的关键信息(比如IR里有estimated_delivery,但模型回复完全没提)。一旦触发告警,系统会自动把这条链路样本加入“校准器训练集”,并通知开发者。这个闭环让我们能在上线后持续优化,而不是靠上线前的静态测试。
这套三层架构,把原本混杂在一起的“调用-解析-拼接-Prompt”过程,拆解为可独立测试、可单独替换、可量化评估的模块。它不追求一步到位的完美,而是提供一个清晰的演进路径:先保证IR正确(适配器层),再保证IR有效(校准器层),最后保证IR可靠(反馈环层)。
3. 核心细节解析与实操要点:那些文档里不会写的坑
3.1 适配器层:如何让时间不再成为“玄学”
时间处理是适配器层最常踩的深坑。我见过太多项目,因为一个datetime.now()没加时区,导致整个Agent在跨时区服务中集体“梦游”。这里分享几个血泪经验:
永远不要信任API返回的时间字符串格式。哪怕文档写着“ISO 8601”,实际返回可能是
"2024-03-15 08:22:17"(缺T和Z)、"15/03/2024 08:22"(DD/MM/YYYY)、甚至"Mar 15, 2024 08:22 AM UTC"(自然语言)。我们的解决方案是:在适配器基类里内置一个“时间解析熔断器”。它按优先级尝试多种解析方式(dateutil.parser.parse> 正则提取 > 固定格式strptime),一旦某一种失败,立即降级到下一个,并记录警告日志。最关键的是,所有解析成功的datetime对象,必须立刻.astimezone(pytz.timezone('Asia/Shanghai'))(或你的业务主时区)。我们禁止在IR中存储naive datetime(无时区datetime),这是硬性红线。警惕“相对时间”的陷阱。有些API返回
"updated_ago": "2 hours ago"。这看起来方便,但对模型是灾难——模型无法精确计算“2小时前”对应的具体时间点,尤其当它需要做时间比较时(比如“比昨天早还是晚?”)。适配器必须把它转换为绝对时间戳。我们的做法是:在适配器初始化时,注入一个reference_time: datetime = datetime.now(timezone.utc),然后用dateparser库解析相对时间,并基于reference_time计算出绝对时间。这样,IR里存的永远是确定的datetime,而不是飘忽的“ago”。时区转换不是终点,显示格式才是起点。IR里的
last_update是datetime对象,但校准器在生成标注文本时,需要把它变成人类可读的字符串。这里有个隐藏技巧:不要用strftime('%Y-%m-%d %H:%M')这种固定格式。我们发现,不同地区用户对时间格式的预期差异巨大(美式MM/DD/YYYY vs 中式DD/MM/YYYY)。所以校准器会根据当前用户的Accept-Language头或配置的locale,动态选择格式:中文用户显示“2024年3月15日 08:22”,英文用户显示“Mar 15, 2024, 08:22 AM”。这个细节让模型生成的回复天然更“接地气”,用户感知更流畅。
提示:在适配器单元测试中,务必用
freezegun库冻结时间,模拟不同时区、不同时刻的API响应,验证解析逻辑的鲁棒性。我们曾在一个深夜部署后发现,由于服务器时区设置错误,所有reference_time都偏移了8小时,导致所有“2 hours ago”被算成了错误时间——这个坑,必须在测试里提前踩。
3.2 校准器层:压缩不是删减,而是聚焦
校准器的“压缩”功能,常被误解为“删掉不重要的字段”。这是危险的。真正的压缩,是基于任务意图的语义聚焦。我们用一个具体例子说明:
假设有一个CRM系统的API,返回一个客户对象,包含20个字段:name,email,phone,company,industry,revenue,employees,last_contact_date,next_followup_date,status,source,tags,notes,address,city,state,country,postal_code,website,linkedin_url。
场景A:用户问“张三的电话是多少?”
理想压缩结果:只保留name,phone。但注意,name必须保留,因为它是phone的锚点,否则模型不知道这个号码属于谁。场景B:用户问“帮我分析一下这家公司的行业前景”(用户提供了公司名)
理想压缩结果:company,industry,revenue,employees,tags。revenue和employees是行业分析的关键量化指标,tags可能包含“AI初创”“SaaS”等高价值定性标签。场景C:用户问“为什么上个月的跟进没成功?”
理想压缩结果:name,last_contact_date,next_followup_date,status,notes,tags。notes里可能有销售记录的失败原因,tags可能标记了“预算不足”“决策链长”。
我们实现了一个轻量级的“字段重要性评分器”。它接收当前System Prompt(定义Agent角色)、用户Query、以及完整的IR字段列表,然后:
- 用一个小型文本嵌入模型(如
all-MiniLM-L6-v2)分别对Prompt、Query、每个字段的__doc__字符串(我们在IR dataclass里为每个字段写了详细docstring)进行向量化; - 计算Query向量与每个字段docstring向量的余弦相似度;
- 同时,计算Prompt向量与每个字段docstring向量的相似度(代表该字段对Agent角色的重要性);
- 将两个相似度加权平均(Query权重0.7,Prompt权重0.3),得到最终分数;
- 按分数排序,只保留Top-K(K=5,经测试在7B模型上效果最佳)。
这个方法的好处是:它不依赖人工规则,能自适应不同任务;它利用了字段自身的语义(docstring),而不是字段名(revenue比rev更能体现含义);它计算快,单次评分耗时<50ms。我们对比过纯规则匹配(正则匹配Query关键词),发现语义评分在复杂Query(如“哪些客户可能在Q2有采购意向?”)上的准确率高出35%。
注意:校准器的压缩逻辑必须是可解释、可审计的。每次调用,系统都会生成一份“压缩决策报告”,列出每个字段的原始值、计算出的分数、是否被保留、以及简短理由(如“
revenue与Query中‘采购意向’语义相似度0.82,高于阈值0.6”)。这份报告不仅用于调试,也作为后续反馈环的数据源。
3.3 反馈环层:如何让“不满意”按钮真正有用
很多项目都加了“不满意”反馈按钮,但数据躺在数据库里吃灰。我们的反馈环让它真正驱动改进。关键在于:把模糊的用户情绪,转化为精确的技术指标。
我们定义了三个核心可观测指标:
幻觉率(Hallucination Rate):模型回复中,提及了IR中完全不存在的事实的比例。检测方法:对模型回复做命名实体识别(NER),提取所有时间、地点、人名、组织名、数字;然后与IR中所有字段的值(及它们的同义词库)做精确匹配。不匹配的,即为潜在幻觉。我们用spaCy训练了一个轻量NER模型,专攻中文商业文本,F1值达0.91。
信息遗漏率(Omission Rate):IR中存在、但模型回复中完全未提及的关键信息比例。检测方法:预先为每个IR字段定义“是否关键”的规则(例如,
estimated_delivery在物流查询中永远是关键;status_display在状态查询中永远是关键)。然后检查回复文本是否包含该字段的值或其高度近义词(如“预计明天送达”匹配estimated_delivery的日期)。语义偏移率(Semantic Drift Rate):模型对IR中字段的解读,与适配器/校准器的原始意图是否一致。检测方法:抽取模型回复中对某个字段的描述(如对
status_display的解释),用文本相似度模型(如bge-small-zh-v1.5)与校准器生成的标注文本(如“当前订单状态(中文)”)计算相似度。低于0.7即为偏移。
当用户点击“不满意”时,系统不只记录一个布尔值,而是自动运行上述三个检测器,生成一份结构化报告。这份报告会:
- 实时推送到开发者Slack频道,附带原始链路日志链接;
- 如果幻觉率>0.5,自动创建Jira Ticket,标题为“[高危] 观测链路幻觉:订单ID {order_id}”;
- 如果同一IR字段在一周内被多次遗漏,系统会自动建议:在下次校准器更新中,提升该字段的默认权重。
我们曾用这个机制,在一次上线后2小时内,就定位到一个校准器bug:它错误地将"pending_review"状态的标注文本生成为“等待审查中”,而模型在回复中理解为“正在被审查”,导致用户困惑。修复后,该状态的用户满意度提升了22%。这证明,反馈环不是锦上添花,而是观测链路的生命线。
4. 实操过程与核心环节实现:手把手搭建你的第一条链路
4.1 环境准备与依赖安装
我们选择Python 3.10+作为运行环境,所有依赖都经过严格版本锁定,确保可复现。核心依赖如下(requirements.txt):
# 基础框架 langchain-core==0.3.10 langchain-community==0.3.10 langchain-openai==0.2.10 # 或你选用的模型提供商SDK # 时间处理 pytz==2024.1 dateparser==1.2.0 freezegun==1.4.0 # 文本处理与NLP spacy==3.7.5 scikit-learn==1.4.2 sentence-transformers==3.0.1 # 其他工具 pydantic==2.7.1 python-dotenv==1.0.1安装命令很简单:
pip install -r requirements.txt # 安装spaCy中文模型(约150MB) python -m spacy download zh_core_web_sm提示:强烈建议使用
venv或conda创建隔离环境。我们曾在一个共享服务器上,因spacy版本冲突导致NER模型加载失败,排查了整整一天——隔离环境是底线。
4.2 编写第一个观测适配器:以订单查询API为例
假设我们有一个模拟的订单查询API,返回JSON如下:
{ "code": 200, "data": { "order_no": "ORD-2024-78901", "status": "shipped", "status_desc": "已发货", "create_time": "2024-03-10T14:30:00+00:00", "ship_time": "2024-03-12T09:15:22+00:00", "est_delivery_time": "2024-03-18T18:00:00+00:00", "customer_name": "张三", "product_name": "智能手表 Pro", "amount": 1299.00, "currency": "CNY" } }我们创建adapters/order_adapter.py:
from datetime import datetime from typing import Any, Optional import pytz import dateparser from pydantic import BaseModel from dataclasses import dataclass from langchain_core.pydantic_v1 import Field # 定义IR(中间表示) @dataclass class OrderStatusIR: """订单状态中间表示,所有字段均为语义明确、类型安全、时区一致""" order_id: str = Field(description="订单唯一标识符") status: str = Field(description="订单状态代码,小写英文") status_display: str = Field(description="订单状态的中文显示名称") create_time: datetime = Field(description="订单创建时间(北京时间)") ship_time: Optional[datetime] = Field(description="发货时间(北京时间),可能为空") est_delivery_time: Optional[datetime] = Field(description="预计送达时间(北京时间),可能为空") customer_name: str = Field(description="客户姓名") product_name: str = Field(description="商品名称") amount: float = Field(description="订单金额") currency: str = Field(description="货币代码") # 适配器基类 class ObserverAdapter: def __init__(self, timezone: str = "Asia/Shanghai"): self.timezone = pytz.timezone(timezone) def transform(self, raw_data: Any) -> OrderStatusIR: raise NotImplementedError("子类必须实现transform方法") # 具体的订单适配器 class OrderAPIAdapter(ObserverAdapter): def transform(self, raw_data: Any) -> OrderStatusIR: if not isinstance(raw_data, dict) or raw_data.get("code") != 200: raise ValueError(f"API调用失败或格式错误: {raw_data}") data = raw_data["data"] # 解析时间:全部转换为北京时间 def parse_time(time_str: str) -> datetime: # 使用dateparser,自动处理各种格式 dt = dateparser.parse(time_str) if dt is None: raise ValueError(f"无法解析时间字符串: {time_str}") # 强制转换为北京时间 return dt.astimezone(self.timezone) return OrderStatusIR( order_id=data["order_no"], status=data["status"].lower(), # 统一为小写 status_display=data["status_desc"], create_time=parse_time(data["create_time"]), ship_time=parse_time(data["ship_time"]) if data.get("ship_time") else None, est_delivery_time=parse_time(data["est_delivery_time"]) if data.get("est_delivery_time") else None, customer_name=data["customer_name"], product_name=data["product_name"], amount=float(data["amount"]), currency=data["currency"] ) # 测试代码 if __name__ == "__main__": # 模拟API响应 mock_response = { "code": 200, "data": { "order_no": "ORD-2024-78901", "status": "shipped", "status_desc": "已发货", "create_time": "2024-03-10T14:30:00+00:00", "ship_time": "2024-03-12T09:15:22+00:00", "est_delivery_time": "2024-03-18T18:00:00+00:00", "customer_name": "张三", "product_name": "智能手表 Pro", "amount": 1299.00, "currency": "CNY" } } adapter = OrderAPIAdapter() ir = adapter.transform(mock_response) print(f"生成IR: {ir}") print(f"发货时间(北京时间): {ir.ship_time}")运行此脚本,你会看到:
生成IR: OrderStatusIR(order_id='ORD-2024-78901', status='shipped', status_display='已发货', create_time=datetime.datetime(2024, 3, 10, 22, 30, tzinfo=<DstTzInfo 'Asia/Shanghai' CST+8:00:00 STD>), ship_time=datetime.datetime(2024, 3, 12, 17, 15, 22, tzinfo=<DstTzInfo 'Asia/Shanghai' CST+8:00:00 STD>), est_delivery_time=datetime.datetime(2024, 3, 19, 2, 0, tzinfo=<DstTzInfo 'Asia/Shanghai' CST+8:00:00 STD>), customer_name='张三', product_name='智能手表 Pro', amount=1299.0, currency='CNY') 发货时间(北京时间): 2024-03-12 17:15:22+08:00注意:ship_time已经正确转换为北京时间(UTC+8),且带有时区信息。这就是适配器层的核心价值——把混乱的原始数据,变成模型可以信赖的“事实”。
4.3 构建观测校准器:动态压缩与标注
接下来,我们创建calibrators/order_calibrator.py。校准器的核心是calibrate方法,它接收IR和当前上下文(Prompt+Query),返回一个精炼的字符串。
from typing import List, Dict, Any, Optional from dataclasses import asdict from sentence_transformers import SentenceTransformer from sklearn.metrics.pairwise import cosine_similarity import numpy as np from langchain_core.prompts import ChatPromptTemplate # 加载轻量级嵌入模型(约150MB) EMBEDDING_MODEL = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2') class ObserverCalibrator: def __init__(self, max_fields: int = 5): self.max_fields = max_fields def _calculate_field_scores(self, system_prompt: str, user_query: str, ir_dict: Dict[str, Any]) -> Dict[str, float]: """计算每个字段与Prompt和Query的语义相关性得分""" # 获取所有字段的docstring(我们约定IR dataclass的Field有description) field_docs = [] field_names = [] for field_name, field_value in ir_dict.items(): # 这里简化:实际项目中,我们从IR的__dataclass_fields__中提取description # 为演示,我们手动映射 doc_map = { "order_id": "订单唯一标识符", "status": "订单状态代码,小写英文", "status_display": "订单状态的中文显示名称", "create_time": "订单创建时间(北京时间)", "ship_time": "发货时间(北京时间),可能为空", "est_delivery_time": "预计送达时间(北京时间),可能为空", "customer_name": "客户姓名", "product_name": "商品名称", "amount": "订单金额", "currency": "货币代码" } field_docs.append(doc_map.get(field_name, f"字段{field_name}")) field_names.append(field_name) # 向量化 prompt_embedding = EMBEDDING_MODEL.encode([system_prompt]) query_embedding = EMBEDDING_MODEL.encode([user_query]) doc_embeddings = EMBEDDING_MODEL.encode(field_docs) # 计算相似度 prompt_scores = cosine_similarity(prompt_embedding, doc_embeddings)[0] query_scores = cosine_similarity(query_embedding, doc_embeddings)[0] # 加权平均 scores = {} for i, name in enumerate(field_names): score = 0.3 * prompt_scores[i] + 0.7 * query_scores[i] scores[name] = float(score) return scores def calibrate(self, ir: OrderStatusIR, system_prompt: str, user_query: str) -> str: """执行校准:压缩、标注、上下文化""" ir_dict = asdict(ir) # 1. 计算字段得分 scores = self._calculate_field_scores(system_prompt, user_query, ir_dict) # 2. 按得分排序,取Top-K sorted_fields = sorted(scores.items(), key=lambda x: x[1], reverse=True) selected_fields = sorted_fields[:self.max_fields] # 3. 为每个选中字段生成标注文本 labeled_parts = [] for field_name, score in selected_fields: field_value = ir_dict[field_name] # 类型特定的格式化 if isinstance(field_value, datetime): # 转换为中文友好格式 formatted_value = field_value.strftime("%Y年%m月%d日 %H:%M") label = f"{field_name}({formatted_value})" elif isinstance(field_value, (int, float)): label = f"{field_name}({field_value})" else: label = f"{field_name}({field_value})" # 添加语义标签(简化版,实际项目中用小型生成模型) semantic_label_map = { "order_id": "订单编号", "status_display": "当前订单状态", "ship_time": "发货时间", "est_delivery_time": "预计送达时间", "customer_name": "客户姓名", "product_name": "购买商品", "amount": "订单金额" } semantic_label = semantic_label_map.get(field_name, field_name) labeled_parts.append(f"{semantic_label}: {field_value}") # 4. 构建上下文字符串 context = "根据最新获取的订单信息:\n" + "\n".join(labeled_parts) + "\n\n请据此回答用户问题。" return context # 测试校准器 if __name__ == "__main__": from adapters.order_adapter import OrderAPIAdapter # 先生成IR adapter = OrderAPIAdapter() mock_response = { /* 同上 */ } ir = adapter.transform(mock_response) # 模拟Agent的System Prompt和用户Query system_prompt = "你是一个专业的电商客服助手,负责解答用户关于订单状态、物流、售后的问题。请用简洁、友好的中文回复。" user_query = "我的订单什么时候能收到?" calibrator = ObserverCalibrator(max_fields=3) calibrated_context = calibrator.calibrate(ir, system_prompt, user_query) print("校准后的上下文:") print(calibrated_context)运行后,你会看到类似这样的输出:
校准后的上下文: 根据最新获取的订单信息: 预计送达时间: 2024-03-19 02:00:00+08:00 当前订单状态: 已发货 发货时间: 2024-03-12 17:15:22+08:00 请据此回答用户问题。注意:create_time和customer_name虽然在IR中存在,但因为与Query“什么时候能收到”相关性较低,被自动过滤掉了。而est_delivery_time(预计送达时间)得分最高,被保留并用中文标签强化。这就是校准器的智能之处——它让模型的注意力,始终聚焦在用户真正在意的信息上。
4.4 集成到LangChain Agent:完整链路跑通
最后,我们将适配器和校准器集成到一个标准的LangChain ReAct Agent中。创建agent/main.py:
from langchain_openai import ChatOpenAI from langchain.agents import AgentExecutor, create_react_agent from langchain import hub from langchain.tools import Tool from langchain_core.messages import HumanMessage, AIMessage from typing import Dict, Any import json # 导入我们前面写的模块 from adapters.order_adapter import OrderAPIAdapter from calibrators.order_calibrator import ObserverCalibrator # 模拟的API调用函数(实际项目中替换为真实的requests调用) def call_order_api(order_id: str) -> Dict[str, Any]: # 这里应调用真实API,为演示,返回mock数据 return { "code": 200, "data": { "order_no": order_id, "status": "shipped", "status_desc": "已发货", "create_time": "2024-03-10T14:30:00+00:00", "ship_time": "2024-03-12T09:15:22+00:00", "est_delivery_time": "2024-03-18T18:00:00+00:00", "customer_name": "张三", "product_name": "智能手表 Pro", "amount": 1299.00, "currency": "CNY" } } # 创建工具 def create_order_tool(): adapter = OrderAPIAdapter() calibrator = ObserverCalibrator(max_fields=3) def _tool_func(order_id: str) -> str: """工具函数:调用API,经适配器和校准器处理后,返回给模型的字符串""" try: raw_data = call_order_api(order_id) ir = adapter.transform(raw_data) # 构建一个简化的System Prompt和Query(实际中可从Agent状态获取) system_prompt = "你是一个专业的电商客服助手..." user_query = "用户正在查询订单,请提供关键状态信息。" calibrated_context = calibrator.calibrate(ir, system_prompt, user_query) return calibrated_context except Exception as e: return f"工具调用失败: {str(e)}" return Tool( name="get_order_status", func=_tool_func, description="根据订单号查询订单状态、发货时间、预计送达时间等关键信息。输入是订单号,如 ORD-2024-78901。" ) # 初始化LLM和Agent llm = ChatOpenAI(model="gpt-4-turbo", temperature=0) tools = [create_order_tool()] prompt = hub.pull("hwchase17/react-chat") agent = create_react_agent(llm, tools, prompt) agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True) # 测试对话 if __name__ == "__main__": result = agent_executor.invoke({ "input": "我的订单 ORD-2024-78901 什么时候能收到?" }) print("Agent最终回复:") print(result["output"])运行此脚本,你会看到Agent的完整思考链路(Thought/Action/Observation),最终输出一个基于校准