之前探索了LLM与程序协作来去重清洗文本格式数据
https://blog.csdn.net/liliang199/article/details/159865538
这里进一步探索LLM如何以ReAct Agent的方式,统计分析融合去重后的数据。
LLM不实际运行统计分析,而是借助一专业统计工具完成,以确保分析过程的准确性。
所用示例参考和修改自网络资料。
1 Agent机制
1.1 LLM统计存在的问题
统计任务sum/count/avg/group by要求绝对精确。
LLM虽然也具备统计能力,直接让LLM处理统计任务,由可能存在多次运行不一致的问题。
而且,随着统计数据规模的扩大,由注意力漂移导致的误差累积会越来越多。
LLM的不稳定性和幻觉会影响到统计结果的准确性。
1.2 Agent ReAct机制
既然LLM不善于处理统计任务,更合理的方案是,准备各种统计工具,由LLM决定调用哪些工具。
具体为
1)LLM理解自然语言,生成工具调用如 SQL
2)由Agent调用后端同工具获得精确结果
3)再由LLM 组织自然语言回答。
Agent ReAct模式允许多轮工具调用,能处理复杂查询。
比如"对比今年和去年的销售额"需要两次调用工具。
2 Agent 运行
这里以代码方式示例LLM如何以ReAct Agent方式统计分析去重后数据。
2.1 环境设置
参考之前文档,LLM环境设置示例代码 如下。
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
from openai import OpenAI
client = OpenAI()
这里采用上一节"LLM与程序协作来去重清洗文本格式数据"去重处理后的数据进行测试。
name company date description ext_person ext_place ext_amount ext_date original_ids index id block_key new_id
0 张三 ABC科技 2023-05-02 张三昨天在北京签了合同,金额50000元 | 张总与北京分公司达成5万交易 张三 北京 50000 元 2024-05-21 [1, 2] NaN NaN NaN 1
1 李四 XYZ有限公司 2023-06-01 李四在上海支付了1200元 李四 上海 1200 元 None NaN 2.0 3.0 XYZ有 2
数据表名称为records_dedup,参考链接如下
https://blog.csdn.net/liliang199/article/details/159865538
2.2 工具定义
这里定义数据库查询统计工具execute_sql,统计逻辑由sql定义实现。
1)工具代码
# ================= 阶段3:查询统计 + ReAct Agent =================
# 定义工具:执行 SQL 查询(只读)
def execute_sql(query: str) -> str:
"""执行 SQL SELECT 语句,返回 JSON 字符串"""
try:
print(query)
conn = sqlite3.connect("example.db")
df_result = pd.read_sql_query(query, conn)
conn.close()
return df_result.to_json(orient="records")
except Exception as e:
return f"SQL错误: {e}"
query = "select * from records_dedup"
json_data = execute_sql(query)
for result in json.loads(json_data):
print(result)
输出示例如下,可见该工具能实际运行sql。
select * from records_dedup
{'name': '张三', 'company': 'ABC科技', 'date': '2023-05-02', 'description': '张三昨天在北京签了合同,金额50000元 | 张总与北京分公司达成5万交易', 'ext_person': '张三', 'ext_place': '北京', 'ext_amount': '50000 元', 'ext_date': '2024-05-21', 'original_ids': '[1, 2]', 'index': None, 'id': None, 'block_key': None, 'new_id': 1}
{'name': '李四', 'company': 'XYZ有限公司', 'date': '2023-06-01', 'description': '李四在上海支付了1200元', 'ext_person': '李四', 'ext_place': '上海', 'ext_amount': '1200 元', 'ext_date': None, 'original_ids': None, 'index': 2.0, 'id': 3.0, 'block_key': 'XYZ有', 'new_id': 2}
2)工具描述
为方便LLM理解工具,这里定义工具描述,示例如下。
# 定义工具描述(供 LLM 理解)
tools = [
{
"type": "function",
"function": {
"name": "execute_sql",
"description": "在 SQLite 数据库上执行只读 SQL 查询。数据库表 'records_dedup' 包含字段:id, name, company, date, description, ext_person, ext_place, ext_amount, ext_date",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "有效的 SELECT SQL 语句"}
},
"required": ["query"]
}
}
}
]
2.3 Agent运行
这里运行基于 ReAct Agent 的自然语言查询,即统计类任务精确执行 SQL。
由LLM决定是否调用工具,在需要时由Agent运行工具程序,拿到结果后生成最终答案。
代码如下所示。
def react_agent(user_question: str, max_steps=3):
"""ReAct 循环:LLM 决定是否调用工具,拿到结果后给出最终答案"""
messages = [
{"role": "system", "content": "你是一个数据库查询助手。你需要根据用户问题,调用 execute_sql 工具来获取精确数据。不允许自己编造数字。若需要多步查询,请逐步调用工具。最终用自然语言回答用户。"},
{"role": "user", "content": user_question}
]
for _ in range(max_steps):
response = client.chat.completions.create(
model=model_name,
messages=messages,
tools=tools,
tool_choice="auto",
temperature=0
)
msg = response.choices[0].message
messages.append(msg) # 将助手消息加入历史
# 如果没有工具调用,说明已经得出最终答案
if not msg.tool_calls:
return msg.content
# 执行工具调用
for tool_call in msg.tool_calls:
func_name = tool_call.function.name
args = json.loads(tool_call.function.arguments)
if func_name == "execute_sql":
result = execute_sql(args["query"])
print(result)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": result
})
return "达到最大步骤限制,未完成查询。"
# 测试查询
print("\n=== ReAct Agent 查询示例 ===")
question = "统计每个公司的总金额(ext_amount),按金额从高到低排序"
answer = react_agent(question)
print(f"用户问题:{question}")
print(f"最终答案:{answer}")
# 另一个需要多步的查询(演示)
question2 = "先查出张三的 ext_amount,再查出李四的,比较谁大"
answer2 = react_agent(question2)
print(f"\n用户问题:{question2}")
print(f"最终答案:{answer2}")
# 关闭连接
conn.close()
输入如下所示。
=== ReAct Agent 查询示例 ===
SELECT company, SUM(ext_amount) as total_amount FROM records_dedup GROUP BY company ORDER BY total_amount DESC
{"company":"ABC\\u79d1\\u6280","total_amount":50000.0},{"company":"XYZ\\u6709\\u9650\\u516c\\u53f8","total_amount":1200.0}
用户问题:统计每个公司的总金额(ext_amount),按金额从高到低排序
最终答案:
根据查询结果,每个公司的总金额统计如下(按金额从高到低排序):
| 公司 | 总金额 |
|------|--------|
| ABC 科技 | 50,000.00 |
| XYZ 有限公司 | 1,200.00 |
其中,ABC 科技的总金额最高,为 50,000.00 元;XYZ 有限公司的总金额为 1,200.00 元。
SELECT ext_amount FROM records_dedup WHERE name = '张三'
{"ext_amount":"50000 \\u5143"}
SELECT ext_amount FROM records_dedup WHERE name = '李四'
{"ext_amount":"1200 \\u5143"}
用户问题:先查出张三的 ext_amount,再查出李四的,比较谁大
最终答案:
根据查询结果:
**张三**的 ext_amount 为 **50000 元**
**李四**的 ext_amount 为 **1200 元**
比较结果:**张三的 ext_amount 更大**,比李四多 48800 元。
相比直接运行程序,Agent可以更有效地调用工具,而且避免了LLM不稳定和存在幻觉的问题。
3 优化注意点
Agent相比直接运行程序更高效,由于Agent依赖LLM决策,依然可能存在不稳定性问题。
这里罗列一些可能存在的问题,以及可能的优化方案。
1)LLM生成SQL可能存在语法或逻辑错误,如join缺失,需增加SQL语法校验等安全限制。
2)多轮调用可能产生冗长的中间输出,所以需要设计清晰的工具描述和终止条件。
3)提供数据库schema的详细描述(表名、字段、类型、示例值)作为 system prompt。
4)对于对常见查询类型可预置模板减少推理开销。
reference
LLM与程序协作来去重清洗文本格式数据
https://blog.csdn.net/liliang199/article/details/159865538
LLM如何与程序协作来结构化文本财报数据
https://blog.csdn.net/liliang199/article/details/159834630
如何使用向量库faiss和LLM判断问题是否被记录
https://blog.csdn.net/liliang199/article/details/159476341
LLM如何基于tools对同一数据不同问题进行查询