导语:本文源自Hollis【实战课程】大模型应用开发实战中的核心项目,经过脱敏后首次公开技术细节。我们将从零搭建一个能够自主执行SQL查询、运行Python代码、调用外部API的ReAct Agent,并最终部署至腾讯云生产环境。全文包含完整的工具链设计、提示工程技巧、流式响应优化及成本控制策略,所有代码均可复现,适合希望将大模型真正落地于业务分析的开发者。
在Hollis课程的第一章节,我们强调一个核心观点:纯粹的对话式LLM只是一个“大脑”,而Agent是赋予它“手脚”的关键。企业中的数据分析需求往往涉及多步推理——例如:“对比上季度华东区和华南区的销售额差异,并生成可视化报告”——这需要模型自主决定:
传统的RAG只能检索静态文本,无法动态执行计算。而Agent通过工具调用(Tool Calling) 与推理-行动循环(ReAct),将复杂任务拆解为可执行的子步骤,这正是大模型应用开发从“玩具”走向“生产力”的关键一跃。
本文将以一个智能销售数据分析助手为例,完整演示如何基于LangChain + OpenAI兼容API(也可替换为开源Qwen模型)构建高可靠性的Agent系统,并部署在腾讯云TKE集群上,支撑日均万次查询。
orders)、产品表(products)、区域表(regions),总计约500万行。+-------------------+ +---------------------+ +----------------------+
| Web UI (React) |---->| API Gateway (Kong) |---->| Agent Orchestrator |
| (Streaming Chat) | | Auth + Rate Limit | | (LangChain) |
+-------------------+ +---------------------+ +----------------------+
|
v
+-------------------+ +---------------------+ +----------------------+
| Tool Executors |<----| Memory (Redis) |<----| LLM Router |
| - SQL Executor | | (Conversation ctx) | | (OpenAI / 国产模型) |
| - Python Sandbox | +---------------------+ +----------------------+
| - Web Search API |
+-------------------+关键选型决策:
create_react_agent,配合 AgentExecutor 实现流式处理。ChatOpenAI 接口接入,便于切换。RestrictedPython 或 docker-exec 沙箱,保证安全;SQL执行器仅赋予只读权限。每个工具必须包含名称、描述、参数schema,模型会根据用户问题决定调用哪个工具、传入什么参数。我们定义三个核心工具:
① SQL查询工具:接收自然语言生成的SQL(由模型自己生成),执行并返回结果。
from langchain.tools import StructuredTool
from pydantic import BaseModel, Field
import pymysql
import json
class SQLInput(BaseModel):
sql_query: str = Field(description="有效的MySQL SELECT语句")
limit: int = Field(default=100, description="返回行数上限")
def execute_sql(sql_query: str, limit: int = 100) -> str:
# 使用连接池,只读事务
conn = pymysql.connect(
host=os.getenv("DB_HOST"), user=os.getenv("DB_USER"),
password=os.getenv("DB_PASS"), database="sales_db",
charset="utf8mb4", cursorclass=pymysql.cursors.DictCursor
)
with conn.cursor() as cur:
# 强制增加LIMIT防止拖库
if "limit" not in sql_query.lower():
sql_query += f" LIMIT {limit}"
cur.execute(sql_query)
rows = cur.fetchall()
conn.close()
# 截断过长的返回结果(防止token爆炸)
return json.dumps(rows[:limit], ensure_ascii=False)[:3000]
sql_tool = StructuredTool.from_function(
func=execute_sql,
name="sql_query",
description="执行只读SQL查询并返回JSON结果,用于获取销售数据、产品信息等。",
args_schema=SQLInput
)② Python数据分析工具:允许模型编写Python代码进行聚合、统计、绘图,并返回图表Base64。
import subprocess, tempfile, base64, os
class PythonInput(BaseModel):
code: str = Field(description="Python3代码,必须将最终结果赋值给变量`result`,如果是图表则保存为`chart.png`")
def execute_python(code: str) -> str:
# 使用临时文件执行,限制资源
with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
# 注入安全限制:禁用危险模块
safe_code = """
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
# 用户代码开始
""" + code + """
# 用户代码结束
# 如果图表存在则编码返回
if os.path.exists('chart.png'):
with open('chart.png', 'rb') as img:
b64 = base64.b64encode(img.read()).decode()
result = f"data:image/png;base64,{b64}"
"""
f.write(safe_code)
try:
proc = subprocess.run(
["python3", f.name], timeout=30,
capture_output=True, text=True, env={"PYTHONPATH": ""}
)
os.unlink(f.name)
if proc.returncode != 0:
return f"执行错误: {proc.stderr}"
# 从输出中提取result变量(实际可用exec捕获,此处简化)
return proc.stdout[-1000:] # 实际应解析
except Exception as e:
return str(e)
python_tool = StructuredTool.from_function(
func=execute_python,
name="python_analysis",
description="执行Python代码用于数据分析和生成图表,代码中可使用pandas/numpy/matplotlib。"
)③ 外部知识搜索工具:当内部数据不足时,可搜索公开信息(如行业平均毛利率),使用腾讯云ES或Bing Search API。
from langchain_community.tools import DuckDuckGoSearchRun
search_tool = DuckDuckGoSearchRun()
# 包装为StructuredToolLangChain的 create_react_agent 使用经典的 Thought/Action/Observation 循环。我们自定义了系统提示模板,引导模型分步推理:
from langchain.agents import create_react_agent, AgentExecutor
from langchain.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
# 使用腾讯云混元(兼容OpenAI接口)
llm = ChatOpenAI(
model="hunyuan-lite",
openai_api_key=os.getenv("HUNYUAN_API_KEY"),
openai_api_base="https://api.hunyuan.cloud.tencent.com/v1",
temperature=0.1,
streaming=True # 开启流式
)
# 自定义ReAct模板(增强指令)
template = """
You are a senior data analyst assistant. You have access to these tools:
{tools}
Use the following format:
Question: {input}
Thought: you should always think about what to do next.
Action: the action to take, must be one of [{tool_names}]
Action Input: the input to the action in JSON format
Observation: the result of the action
... (this Thought/Action/Action Input/Observation can repeat N times)
Thought: I now know the final answer
Final Answer: the final answer to the original question, include chart if generated.
Begin!
Previous conversation history:
{chat_history}
Question: {input}
{agent_scratchpad}
"""
prompt = PromptTemplate.from_template(template)
agent = create_react_agent(llm, tools=[sql_tool, python_tool, search_tool], prompt=prompt)
agent_executor = AgentExecutor(
agent=agent,
tools=tools,
verbose=True,
max_iterations=8, # 防止死循环
early_stopping_method="generate",
return_intermediate_steps=True,
handle_parsing_errors=True
)关键优化点:
AgentExecutor 不支持原生流式,我们通过 astream_events 方法(LangChain 0.3+)实现逐Token推送,大幅提升用户体验。ConversationBufferWindowMemory 保留最近5轮对话,避免上下文超长。from langchain.memory import ConversationBufferWindowMemory
memory = ConversationBufferWindowMemory(k=5, memory_key="chat_history", return_messages=True)
agent_executor.memory = memory为了让前端实时看到Agent的思考过程,我们在FastAPI中实现SSE(Server-Sent Events):
from fastapi import FastAPI, BackgroundTasks
from sse_starlette.sse import EventSourceResponse
import asyncio
app = FastAPI()
@app.post("/agent/stream")
async def agent_stream(query: str, session_id: str):
# 从Redis加载该session的历史
memory.load_memory_variables({"session_id": session_id})
async def event_generator():
async for event in agent_executor.astream_events(
{"input": query},
version="v1"
):
if event["event"] == "on_chain_stream":
# 流式输出token
yield {"data": event["data"]["chunk"], "event": "token"}
elif event["event"] == "on_tool_start":
yield {"data": f"调用工具: {event['name']}", "event": "tool"}
elif event["event"] == "on_chain_end":
yield {"data": "[DONE]", "event": "done"}
# 保存新的对话到Redis
memory.save_context({"input": query}, {"output": final_answer})
return EventSourceResponse(event_generator())编写Dockerfile,基于 python:3.11-slim,安装依赖并设置非root用户。在TKE上部署时,每个Pod分配:
使用HPA(Horizontal Pod Autoscaler)根据CPU利用率(阈值70%)动态伸缩,最少2个副本,最多10个副本。
大模型的调用费用是主要成本。我们引入两层缓存:
redisvl 库对用户问题做embedding,相似度>0.95时直接返回历史答案(无需调用LLM)。命中率约25%。sql_hash 为键存入Redis,避免重复查询数据库。from hashlib import md5
cache_key = md5(sql_query.encode()).hexdigest()
cached = redis_client.get(cache_key)
if cached:
return cached.decode()
# 否则执行并缓存接入腾讯云CLS(日志服务)收集Agent的每一步 Thought 和 Observation,便于排查错误。同时使用Prometheus记录:
设置告警规则:当错误率超过5%或P95延迟 > 8s 时触发钉钉通知。
我们使用Hollis课程内测阶段的200个真实销售分析问题对系统进行压测,结果如下:
指标 | 数值 |
|---|---|
任务完成率(最终给出正确结论) | 86.5% |
平均迭代步数 | 3.2步 |
平均总耗时(含LLM推理) | 6.8s |
工具调用准确率(选择正确工具) | 94% |
典型错误分析:
product_line写成product_line_name),通过在SQL工具中增加错误捕获并返回列不存在信息,模型可在下一步自行修正——这验证了ReAct的自纠错能力。ConversationSummaryMemory 替代。成本测算:每次问答平均消耗约1500个input token和300个output token(混元模型),按腾讯云刊例价约为0.02元/次,远低于雇佣人工数据分析师的成本。
本文基于Hollis【实战课程】的核心理念,完整呈现了一个生产级数据分析Agent的开发全链路。我们证明了通过精心设计的工具+合理的提示工程+流式交互,大模型完全能够承担企业级数据分析任务。同时,腾讯云提供的容器、日志、监控服务为系统的稳定运行提供了坚实底座。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。