个人工作:基于Function Calling实现数据摘要Agent交互式问答功能升
一、一些前面的问题
本次毕业设计围绕电商评论多智能体分析系统展开,前期已完成数据摘要Agent模块开发,该模块可自动聚合评论数据并生成标准化文本摘要。但经过功能自测后发现,原有Agent仅支持单次静态摘要生成,不具备自然语言交互问答能力,无法满足精细化数据查询与多轮追问的业务需求。
因此本次针对数据摘要Agent进行迭代升级,引入大模型Function Calling函数调用机制,构建标准化工具调用循环,使大模型能够自主判断查询需求、调用后端数据工具获取真实指标,实现数据集的交互式智能问答。本次迭代采用增量开发模式,全程复用系统已有业务服务与数据库资源,不改动原有成熟业务代码,保障系统整体稳定性。
二、原有模块缺陷与迭代约束
2.1 旧版数据摘要Agent运行机制
旧版数据摘要Agent采用全量预聚合的静态运行方案,整体执行流程如下:
-
系统启动后并行调用情感分析、LDA主题聚类、差评归因、用户聚类等全部子智能体,完成全维度评论数据计算;
-
将所有分析指标统一拼接为完整文本上下文,一次性传入大语言模型;
-
大模型基于固定上下文,单次生成简短摘要、完整摘要、风险预警三类结果;
-
通过SSE流式接口向前端返回最终摘要内容。
2.2 原有模块存在的核心缺陷
结合系统自测与实际使用场景,旧版静态摘要Agent存在四点无法规避的设计缺陷:
-
无多轮交互能力:所有分析数据提前固化,上下文无法动态更新,不支持用户针对数据细节进行二次追问,智能化交互能力缺失;
-
上下文截断引发数据失真:受大模型输入Token限制,拼接后的上下文被强制截断至2000-3000字符,细分维度指标、原始评论样本等关键数据丢失,导致模型输出内容存在幻觉、数据前后不一致问题;
-
大模型客户端能力缺失:原有LLM客户端仅封装基础对话接口,未适配Function Calling协议,无法传递工具参数、解析工具调用返回结果,不具备智能体自主规划能力;
-
系统算力资源浪费:模块采用全量任务预执行模式,无论用户查询需求如何,均需要运行全部分析任务,无效计算量大,接口响应效率偏低。
2.3 本次迭代开发约束
为保证系统原有业务链路不受影响,本次功能升级遵循最小改动原则,具体开发约束如下:
-
完全保留原有一键摘要生成接口
/agent/summary/generate,存量业务逻辑无任何修改; -
复用系统已封装的情感分析服务、BHI口碑指数计算、RFM用户分群、LDA主题分析等底层能力,不重复开发数据统计逻辑;
-
仅做后端代码增量开发,前端页面无需改动,保证前后端联调兼容性;
-
工具执行优先读取数据库已持久化的分析结果,无存量数据时再实时计算,降低接口响应耗时。
三、整体迭代方案设计
3.1 Function Calling协议选型依据
本次放弃自研自定义工具解析逻辑,选用行业通用的OpenAI标准Function Calling协议,选型原因如下:
-
协议通用性强,可兼容本地部署的各类开源大模型,无需针对不同模型做额外适配改造;
-
依托标准化协议格式,无需重构Agent核心代码,仅需新增客户端方法与工具调度逻辑,开发成本更低;
-
模型返回结果结构统一,降低自定义文本解析带来的异常风险,提升系统运行稳定性。
3.2 模型兼容性降级方案设计
考虑到部分开源大模型不完全支持Function Calling调用协议,为保障服务全场景可用,设计双层运行链路与自动降级机制:
-
正常链路:大模型支持工具调用,启动多轮函数调用循环,模型自主按需调用工具、动态获取数据并生成回答;
-
降级链路:模型接口异常或不支持工具调用,系统自动预执行三项核心高频工具,将关键指标注入上下文,退回普通单轮对话模式,保证服务不中断。
3.3 交互式问答整体执行流程
-
后端接收前端传入的数据集编号、用户提问文本以及历史对话上下文;
-
拼接系统提示词与工具描述列表,向大模型发起带工具参数的对话请求;
-
解析模型返回结果,判断是否需要调用外部数据工具;
-
若存在工具调用请求,解析工具名称与入参,调度对应业务工具查询数据库数据;
-
将工具返回的结构化数据回填至对话上下文,进入下一轮模型对话;
-
设置最大5轮函数调用上限,规避无限循环调用导致的接口阻塞问题;
-
模型判定数据充足后,整合全部工具返回结果,生成最终标准化回答,并记录工具调用日志与迭代次数。
四、后端代码增量改造内容
4.1 优化LLM客户端:新增工具调用对话接口
在原有llm_client.py文件中增量新增achat_tools异步方法,完整透传工具调用相关参数,复用原有接口重试、Token用量统计、异常日志记录逻辑,保证底层客户端能力统一,原有普通对话接口保持不变。
async def achat_tools(
self,
messages: list[dict],
tools: list[dict],
tool_choice: str = "auto",
temperature: float = 0.3,
max_tokens: int = 1500
) -> dict:
try:
# 传入工具调用参数,发起大模型对话请求
response = await self.client.chat.completions.create(
model=self.model_name,
messages=messages,
tools=tools,
tool_choice=tool_choice,
temperature=temperature,
max_tokens=max_tokens
)
# 复用原有Token消耗统计逻辑
self._record_usage(response.usage)
# 统一返回字典格式,适配上层代码调用规范
return response.choices[0].message.dict()
except Exception as e:
self.logger.error(f"工具调用对话请求异常: {e}")
raise e
4.2 新建工具调度模块:业务工具封装与注册表设计
新建summary_tools.py文件,拆分6项贴合评论分析业务的专用数据工具,同时搭建统一工具注册表,实现工具的统一注册、分发与异常管理,避免冗余条件判断代码。所有工具均复用系统存量业务接口,无需重复实现数据分析算法。
本次封装的六项业务工具
-
get_overview_metrics:数据集整体指标统计,包含评论总量、平均评分、正负向评论占比、BHI口碑指数、水军评论占比及数据时间跨度;
-
get_sentiment_breakdown:情感维度拆解分析,输出整体情感分布、五维细分情感评分、情感时序变化及短期情感异常检测结果;
-
get_top_complaints:TOP差评根因挖掘,优先读取历史归因分析结果,无存量数据时自动聚合低分评论提炼负面问题;
-
get_topic_keywords:评论主题与关键词提取,调用LDA主题聚类模型,兜底支持文本高频词统计;
-
get_user_segments:用户分群画像分析,结合UserCluster聚类表与RFM分层算法,输出用户群体特征;
-
search_reviews:关键词原文检索,匹配原始评论内容,返回命中数量与评论样本,为模型回答提供事实依据。
函数调用循环核心代码实现
基于工具注册表实现自动化工具调度与多轮对话循环,同时全局捕获异常,联动降级方案保障服务稳定性,核心代码如下:
async def ask(self, dataset_id: str, question: str, db: Session, history: list = None) -> dict:
# 初始化系统提示词,约束模型基于真实数据作答
messages = [{"role": "system", "content": "你是专业的评论数据分析助手,需调用工具获取真实数据库指标后作答,禁止编造数据,数据缺失需如实说明。"}]
if history:
messages.extend(history)
messages.append({"role": "user", "content": question})
tools_used = []
iterations = 0
fallback = False
try:
# 限制最大迭代轮次,防止死循环阻塞接口
while iterations < self.max_iter:
iterations += 1
resp = await self.llm_client.achat_tools(messages, self.registry.schemas)
# 无需调用工具,直接返回回答结果
if not resp.get("tool_calls"):
return {
"answer": resp["content"],
"tools_used": tools_used,
"iterations": iterations,
"fallback": False
}
# 批量执行模型发起的工具调用请求
for tool_call in resp["tool_calls"]:
t_name = tool_call["function"]["name"]
t_args = eval(tool_call["function"]["arguments"])
tool_res = await self.registry.dispatch(t_name, t_args, db, dataset_id)
# 将工具执行结果回填至对话上下文
messages.append({
"role": "tool",
"tool_call_id": tool_call["id"],
"content": str(tool_res)
})
tools_used.append(t_name)
# 达到最大迭代次数,整合上下文生成最终回答
final_text = await self.llm_client.achat(messages)
return {"answer": final_text, "tools_used": tools_used, "iterations": iterations, "fallback": False}
# 全局异常捕获,触发自动降级策略
except Exception:
fallback = True
# 预执行三项核心基础工具
overview = await get_overview_metrics(db, dataset_id)
sentiment = await get_sentiment_breakdown(db, dataset_id)
complaints = await get_top_complaints(db, dataset_id, top_n=3)
# 拼接基础指标至提示词,退回普通对话模式
prompt_ctx = f"数据集基础指标:{overview},情感分布:{sentiment},TOP3差评:{complaints}"
messages.append({"role": "system", "content": prompt_ctx})
fallback_ans = await self.llm_client.achat(messages)
return {"answer": fallback_ans, "tools_used": ["fallback_pre_run"], "iterations": 1, "fallback": True}
关键问题优化:初期代码未设计全局异常捕获,模型接口异常会直接导致后端服务崩溃,增加try-except异常捕获模块后,服务容错能力得到有效提升。
4.3 新增后端路由接口
在原有路由文件中增量新增两个接口,不改动原有路由规则,满足问答交互与工具查询需求:
-
POST /agent/summary/ask:交互式问答核心接口,接收数据集编号、用户问题与对话历史,返回回答文本、调用工具清单、迭代轮次与降级状态;
-
GET /agent/summary/tools:工具列表查询接口,返回系统全部可用工具信息,用于接口调试与后续前端面板适配。
4.4 新增请求参数结构体
在原有请求参数模块中,新增交互式问答专用请求结构体,规范入参格式,不修改原有摘要请求参数结构,保证接口兼容性。
五、系统测试与结果验证
本次从单元测试、链路测试、降级测试、回归测试四个维度完成全流程验证,保障新增功能可用、存量功能无影响:
-
工具单元测试:调用项目真实数据集编号,逐一测试六项业务工具,验证数据库读写正常、无存量数据时兜底计算逻辑有效,返回数据格式统一规范;
-
函数调用链路测试:模拟大模型工具调用请求,验证工具自动分发、结果回填、多轮对话循环流程运行正常;
-
降级策略测试:手动屏蔽模型Function Calling能力,验证系统可自动触发降级逻辑,正常输出回答,服务无崩溃现象;
-
业务回归测试:复测原有一键摘要生成接口,确认增量代码未对存量业务功能造成负面影响,系统整体运行稳定。
六、开发工作量分析
本次迭代代码工作量统计
本次迭代全程采用增量开发模式,无存量代码修改,新增代码总量约780行,具体分布如下:
-
LLM客户端工具调用接口:约60行
-
六项业务工具及工具注册表:约350行
-
问答循环逻辑与自动降级代码:约220行
-
新增路由接口与参数结构体:约60行
-
单元测试与联调测试代码:约120行
七、开发总结
本次迭代完成了静态数据摘要Agent向交互式问答智能体的升级改造,依托标准化Function Calling协议,搭建了完整的工具调用循环与服务降级机制,解决了原有模块无法追问、数据截断失真、算力浪费等核心问题。
本次开发遵循软件工程最小改动原则,最大化复用系统现有业务资源,兼顾了功能扩展性与系统稳定性。通过本次开发,深入理解了大模型智能体工具调用的运行原理:大模型负责语义理解与调用决策,后端程序负责工具执行与数据查询,二者协同完成智能化数据分析问答。
更多推荐
所有评论(0)