LLM如何基于tools对同一数据不同问题进行查询

张开发
2026/4/5 23:42:56 15 分钟阅读

分享文章

LLM如何基于tools对同一数据不同问题进行查询
之前探索了如何基于OpenAI进行Function Calling调用https://blog.csdn.net/liliang199/article/details/159734250这里进一步针对更复杂的问题比如针对同一数据的不同问题进行灵活查询。所用示例参考和修改自网络资料。1 问题描述1.1 场景说明这是一个针对“同一数据不同问题”的灵活查询场景。场景 你有一张包含500列宽表的Excel或者一份杂乱的报告。问题A“去年销售额是多少”问题B“华东区销售排名前三是谁”1.2 问题描述针对该场景可以采用纯程序的方案也可以采用纯LLM的方案。1纯程序需要为每一个新问题写SQL或Python脚本。如果业务人员增加新的问题开发人员需要排期开发。不灵活维护成本高。2纯大模型让大模型根据问题生成查询代码比如Text2SQL、Text2Code。对于数值统计比如求和、平均数大模型生成的代码可能逻辑错误。比如漏了关键WHERE条件或者直接把算错的数字返回给用户。2 基于tools的融合方案2.1 方案描述这里尝试基于LLM和工具调用尝试采用程序和模型融合的方案来解决问题。1工具定义每个工具包含名称、描述、参数schemaJSON格式以及对应的执行函数。2路由决策LLM根据用户问题和工具描述自动选择工具若没有匹配则拒绝回答。3参数提取大模型填充参数schema执行层做类型校验和默认值补全。4执行层隔离执行函数必须是纯确定的可单元测试可审计。5容错参数缺失或格式错误时执行层可抛出异常由大模型向用户追问。具体运行过程如下所示step1 路由用户问“去年销售额”LLM不需要自己去数据里找它只需要理解意图将其路由到SalesAnalysis这个工具Function Calling。step2 参数提取LLM从问题提取参数 { “date_range”: “2025-01-01 to 2025-12-31”, “metric”: “sum_sales” }。step3 执行程序接收到参数调用封装好的、经过单元测试的Python或SQL代码去数据库或DataFrame中执行精确计算。返回结果1,234,567 元。step4 呈现LLM拿到这个精确的数字根据用户是想要“口语回答”还是“生成图表”组织最终的语言。这样做的好处是计算由确定的程序完成不会出现大模型算错数的问题。而且新增一种查询类型只需要新增一个工具定义和对应程序处理逻辑不需要重写整个流程。2.2 代码示例假设数据是一张销售宽表sales_data.xlsx包含以下列日期,区域,产品,销售额,销售员,客户ID…共500列但只用其中几列这里将实现两个工具get_total_sales计算指定时间范围内的总销售额。get_top_regions返回销售额排名前N的区域带具体数值。1准备环境和数据在代码中设置LLM的 API Key、Base Url。import os model_name gpt_model_name # LLM名称,比如deepseek-r1, qwen3.5-8b os.environ[OPENAI_API_KEY] gpt_api_key # LLM供应商提供的api key os.environ[OPENAI_BASE_URL] gpt_api_url # LLM供应商提供llm访问api的url生成模拟数据代码示例如下。import numpy as np import pandas as pd import json from openai import OpenAI # 模拟加载数据实际可从Excel读取 df pd.DataFrame({ 日期: pd.date_range(2025-01-01, periods100, freqD), 区域: [华东, 华南, 华北, 西南] * 25, 销售额: np.random.randint(1000, 10000, 100), 销售员: [张三, 李四, 王五, 赵六] * 25 }) # 实际中可读取df pd.read_excel(sales_data.xlsx)2 定义工具和执行函数这里创建get_total_sales和get_top_regions这两个工具以及对应的tools定义。# ---------- 工具1总销售额 ---------- def get_total_sales(start_date: str, end_date: str) - float: 精确计算指定日期范围内的总销售额 mask (df[日期] start_date) (df[日期] end_date) total df.loc[mask, 销售额].sum() return float(total) # 工具1的OpenAI function schema tool_total_sales { type: function, function: { name: get_total_sales, description: 获取指定日期范围内的总销售额, parameters: { type: object, properties: { start_date: {type: string, description: 开始日期格式YYYY-MM-DD}, end_date: {type: string, description: 结束日期格式YYYY-MM-DD} }, required: [start_date, end_date] } } } # ---------- 工具2区域排名 ---------- def get_top_regions(top_n: int, start_date: str None, end_date: str None) - list: 返回销售额排名前N的区域及其销售额 if start_date and end_date: mask (df[日期] start_date) (df[日期] end_date) sub_df df.loc[mask] else: sub_df df sales_by_region sub_df.groupby(区域)[销售额].sum().sort_values(ascendingFalse) top sales_by_region.head(top_n).reset_index() return [{region: row[区域], sales: float(row[销售额])} for _, row in top.iterrows()] tool_top_regions { type: function, function: { name: get_top_regions, description: 获取销售额排名前N的区域可指定日期范围, parameters: { type: object, properties: { top_n: {type: integer, description: 排名数量, default: 3}, start_date: {type: string, description: 开始日期可选}, end_date: {type: string, description: 结束日期可选} }, required: [top_n] } } } # 将工具映射到执行函数 tool_functions { get_total_sales: get_total_sales, get_top_regions: get_top_regions, } available_tools [tool_total_sales, tool_top_regions]3核心对话逻辑构建核心对话逻辑具体为调用LLM决定是否调用工具运行工具调用将结果呈现给用户。client OpenAI() # 或使用其他兼容接口 def answer_question(user_question: str) - str: # 第一步调用大模型带工具定义路由参数提取 messages [{role: user, content: user_question}] response client.chat.completions.create( modelmodel_name, # 或支持function calling的模型 messagesmessages, toolsavailable_tools, tool_choiceauto # 让模型自己决定是否调用工具 ) assistant_msg response.choices[0].message # 如果没有调用工具直接返回模型回答可能拒绝或闲聊 if not assistant_msg.tool_calls: return assistant_msg.content # 第二步执行工具调用可能有多个本例只处理第一个 tool_call assistant_msg.tool_calls[0] tool_name tool_call.function.name tool_args json.loads(tool_call.function.arguments) # 执行对应的Python函数 if tool_name in tool_functions: try: result tool_functions[tool_name](**tool_args) except Exception as e: result f执行错误{str(e)} else: result 未知工具 # 第三步将执行结果返回给大模型生成最终自然语言回答 messages.append(assistant_msg) # 助手之前的tool_calls消息 messages.append({ role: tool, tool_call_id: tool_call.id, content: json.dumps(result, ensure_asciiFalse) }) final_response client.chat.completions.create( modelmodel_name, messagesmessages ) return final_response.choices[0].message.content4测试示例然后进一步给出测试示例print(answer_question(去年年销售额是多少)) print(\n\n) print(answer_question(华东区销售排名前三是谁))输出如下所示虽然第2个问题正常回答然而第一个问题因为问的是去年年销售额是多少。如果没有外部输入校正模型可能以为1现在是2025年那去年就是2024年。2现在是2026年那去年就是2025年这时是第一种情况模型给工具提供了错误的参数导致没有从工具调用获得期望的数据。所以要解决这个问题还需要一共时间校准的工具来辅助模型构建参数。根据系统查询结果2024 年全年的销售额为 **0 元**。这可能是因为- 系统刚上线销售数据尚未录入- 去年确实没有产生销售记录- 数据还在同步中如果您需要查询其他时间段的销售数据或者想了解具体的销售明细请告诉我我可以帮您进一步查询。根据查询结果销售额排名前三的区域如下| 排名 | 区域 | 销售额元 ||------|------|-------------|| 1 | 西南地区 | 160,829 || 2 | 华东地区 | 152,390 || 3 | 华北地区 | 143,997 |华东区在所有区域中排名第 **2** 位销售额为 **152,390 元**。---⚠️ **说明**如果您想查询的是**华东区内部的销售人员/团队排名前三**当前系统暂不支持按区域细分查询人员排名。如需该数据请联系相关部门获取详细报表。3 新增工具方案为了处理“去年销售额是多少”这类相对时间查询需要增加一个获取当前日期的工具。LLM可以先用它获得真实日期再计算出“去年”的起止范围最后调用销售统计工具。3.1 方案说明这里保留原有工具get_total_sales 和 get_top_salespersons 不变确保确定性计算。增加了1get_current_date工具get_current_date返回当前系统日期如 2026-04-02。2多步工具调用循环while True 循环处理模型连续的工具调用请求直到模型输出最终自然语言回答。3.2 代码示例示例代码如下import pandas as pd import numpy as np import json from datetime import datetime, timedelta from openai import OpenAI # 模拟数据实际可从Excel读取 df pd.DataFrame({ 日期: pd.date_range(2025-01-01, periods100, freqD), 区域: [华东, 华南, 华北, 西南] * 25, 销售额: np.random.randint(1000, 10000, 100), 销售员: [张三, 李四, 王五, 赵六] * 25 }) # 1. 定义工具函数 def get_current_date() - str: 获取当前系统日期用于解析相对时间如“去年”、“上个月” return datetime.now().strftime(%Y-%m-%d) def get_total_sales(start_date: str, end_date: str) - float: 精确计算指定日期范围内的总销售额 mask (df[日期] start_date) (df[日期] end_date) total df.loc[mask, 销售额].sum() return float(total) def get_top_salespersons(region: str, top_n: int 3, start_date: str None, end_date: str None) - list: 获取指定区域内销售额排名前N的销售员 mask (df[区域] region) if start_date and end_date: mask (df[日期] start_date) (df[日期] end_date) sub_df df.loc[mask] sales_by_person sub_df.groupby(销售员)[销售额].sum().sort_values(ascendingFalse) top sales_by_person.head(top_n).reset_index() return [{salesperson: row[销售员], sales: float(row[销售额])} for _, row in top.iterrows()] # 2. 工具描述Function Schema tool_current_date { type: function, function: { name: get_current_date, description: 获取当前系统日期用于计算相对时间例如“去年”需要先知道今年是哪一年, parameters: {type: object, properties: {}, required: []} } } tool_total_sales { type: function, function: { name: get_total_sales, description: 获取指定日期范围内的总销售额, parameters: { type: object, properties: { start_date: {type: string, description: 开始日期格式YYYY-MM-DD}, end_date: {type: string, description: 结束日期格式YYYY-MM-DD} }, required: [start_date, end_date] } } } tool_top_salespersons { type: function, function: { name: get_top_salespersons, description: 获取指定区域内销售额排名前N的销售员可限定日期范围, parameters: { type: object, properties: { region: {type: string, description: 区域名称如华东}, top_n: {type: integer, description: 排名数量, default: 3}, start_date: {type: string, description: 开始日期可选}, end_date: {type: string, description: 结束日期可选} }, required: [region] } } } # 工具映射表 tool_functions { get_current_date: get_current_date, get_total_sales: get_total_sales, get_top_salespersons: get_top_salespersons, } available_tools [tool_current_date, tool_total_sales, tool_top_salespersons] # 3. 核心对话引擎支持多步工具调用 client OpenAI() def answer_question(user_question: str) - str: messages [{role: user, content: user_question}] # 循环处理工具调用直到模型不再请求调用工具 while True: response client.chat.completions.create( modelmodel_name, # 支持function calling messagesmessages, toolsavailable_tools, tool_choiceauto ) assistant_msg response.choices[0].message messages.append(assistant_msg) # 如果没有工具调用请求说明最终答案已生成 if not assistant_msg.tool_calls: return assistant_msg.content # 执行所有工具调用按顺序 for tool_call in assistant_msg.tool_calls: tool_name tool_call.function.name tool_args json.loads(tool_call.function.arguments) # 执行对应函数 if tool_name in tool_functions: try: result tool_functions[tool_name](**tool_args) except Exception as e: result f执行错误{str(e)} else: result f未知工具{tool_name} # 将工具执行结果追加到消息中 messages.append({ role: tool, tool_call_id: tool_call.id, content: json.dumps(result, ensure_asciiFalse) }) # 循环继续模型会根据工具结果决定下一步可能调用新工具或生成最终答案 # 4. 测试 if __name__ __main__: # 问题“去年销售额是多少” print(answer_question(去年销售额是多少)) # 预期流程 # 1. 模型调用 get_current_date()假设返回 2026-04-02 # 2. 模型根据当前日期计算出去年范围2025-01-01 至 2025-12-31 # 3. 模型调用 get_total_sales(start_date2025-01-01, end_date2025-12-31) # 4. 模型根据返回的数字生成“去年总销售额为 1,234,567 元。”输出如下所示此时LLM为用户呈现了正确的数据。去年2025 年的总销售额是 556,960.00 元。3.3 如何完成去年推理模型收到问题“去年销售额是多少”后意识到需要当前日期才能确定“去年”。1调用get_current_date得到2026-04-02。2根据返回的日期计算出去年的范围应为2025-01-01到2025-12-31。3调用get_total_sales(start_date2025-01-01, end_date2025-12-31)。4最后用返回的数值生成自然语言答案。需要注意的是大模型计算相对时间时如润年、边界日期可能存在幻觉风险。reference---如何基于OpenAI进行Function Calling调用https://blog.csdn.net/liliang199/article/details/159734250

更多文章