mirror of
http://47.106.207.27:3000/Jeremy_liu/AI0814_jiaoan_public.git
synced 2026-09-27 06:14:14 +08:00
202 lines
6.9 KiB
Python
202 lines
6.9 KiB
Python
"""
|
||||
|
|
ReAct Agent(基于 DeepSeek Function Calling 实现)
|
|||
|
|
|
|||
|
|
ReAct = Reasoning + Acting:模型先"想"(决定是否调用工具、调用哪个),
|
|||
|
|
再"做"(执行工具),拿到"观察结果"后继续下一轮思考,直到给出最终答案。
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import os
|
|||
|
|
import json
|
|||
|
|
from openai import OpenAI
|
|||
|
|
from dotenv import load_dotenv
|
|||
|
|
|
|||
|
|
load_dotenv()
|
|||
|
|
|
|||
|
|
client = OpenAI(
|
|||
|
|
api_key=os.environ.get("DEEPSEEK_API_KEY"),
|
|||
|
|
base_url="https://api.deepseek.com",
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
MODEL = "deepseek-chat" # 换成你实际可用的模型名
|
|||
|
|
MAX_STEPS = 8 # 防止死循环的最大推理步数
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ============================================================
|
|||
|
|
# 1. 工具实现层:每个工具就是一个普通的 Python 函数
|
|||
|
|
# ============================================================
|
|||
|
|
def get_weather(location: str) -> str:
|
|||
|
|
"""模拟天气查询接口"""
|
|||
|
|
fake_db = {
|
|||
|
|
"杭州": "30℃,晴,东南风 2 级",
|
|||
|
|
"北京": "25℃,多云",
|
|||
|
|
"上海": "28℃,小雨",
|
|||
|
|
}
|
|||
|
|
return fake_db.get(location, f"{location}:暂无该城市天气数据")
|
|||
|
|
|
|||
|
|
|
|||
|
|
def calculator(expression: str) -> str:
|
|||
|
|
"""一个安全的四则运算计算器"""
|
|||
|
|
allowed = set("0123456789+-*/(). ")
|
|||
|
|
if not set(expression) <= allowed:
|
|||
|
|
return "错误:表达式包含非法字符"
|
|||
|
|
try:
|
|||
|
|
return str(eval(expression, {"__builtins__": {}}, {}))
|
|||
|
|
except Exception as e:
|
|||
|
|
return f"计算失败:{e}"
|
|||
|
|
|
|||
|
|
|
|||
|
|
# 工具名 -> 函数 的注册表,Agent 靠它做分发
|
|||
|
|
TOOL_REGISTRY = {
|
|||
|
|
"get_weather": get_weather,
|
|||
|
|
"calculator": calculator,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ============================================================
|
|||
|
|
# 2. 工具描述层:告诉模型有哪些工具可用(JSON Schema)
|
|||
|
|
# ============================================================
|
|||
|
|
TOOLS_SCHEMA = [
|
|||
|
|
{
|
|||
|
|
"type": "function",
|
|||
|
|
"function": {
|
|||
|
|
"name": "get_weather",
|
|||
|
|
"description": "获取指定城市的当前天气",
|
|||
|
|
"parameters": {
|
|||
|
|
"type": "object",
|
|||
|
|
"properties": {
|
|||
|
|
"location": {"type": "string", "description": "城市名,如杭州"},
|
|||
|
|
},
|
|||
|
|
"required": ["location"],
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"type": "function",
|
|||
|
|
"function": {
|
|||
|
|
"name": "calculator",
|
|||
|
|
"description": "计算一个数学表达式,例如 '30 - 25'",
|
|||
|
|
"parameters": {
|
|||
|
|
"type": "object",
|
|||
|
|
"properties": {
|
|||
|
|
"expression": {"type": "string", "description": "要计算的数学表达式"},
|
|||
|
|
},
|
|||
|
|
"required": ["expression"],
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ============================================================
|
|||
|
|
# 3. 系统提示词:定义 Agent 的行为方式
|
|||
|
|
# ============================================================
|
|||
|
|
SYSTEM_PROMPT = """你是一个可以使用工具的智能助手。
|
|||
|
|
|
|||
|
|
工作要求:
|
|||
|
|
1. 先思考问题需要哪些信息,判断是否需要调用工具。
|
|||
|
|
2. 需要外部信息(天气、计算等)时,必须调用工具,不要凭空编造。
|
|||
|
|
3. 可以连续多次调用工具;每一步只做当前最必要的事。
|
|||
|
|
4. 拿到工具结果后,用简洁的中文给出最终答案。
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ============================================================
|
|||
|
|
# 4. 核心:ReAct 循环
|
|||
|
|
# ============================================================
|
|||
|
|
def run_react_agent(user_query: str, max_steps: int = MAX_STEPS, verbose: bool = True):
|
|||
|
|
"""
|
|||
|
|
执行 ReAct 循环:
|
|||
|
|
思考(Thought) -> 行动(Action) -> 观察(Observation) -> 再思考 ... -> 最终答案
|
|||
|
|
返回 (最终答案, 完整消息历史)
|
|||
|
|
"""
|
|||
|
|
messages = [
|
|||
|
|
{"role": "system", "content": SYSTEM_PROMPT},
|
|||
|
|
{"role": "user", "content": user_query},
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
for step in range(1, max_steps + 1):
|
|||
|
|
if verbose:
|
|||
|
|
print(f"\n{'=' * 20} Step {step} {'=' * 20}")
|
|||
|
|
|
|||
|
|
# ---------- Thought:让模型决定下一步做什么 ----------
|
|||
|
|
response = client.chat.completions.create(
|
|||
|
|
model=MODEL,
|
|||
|
|
messages=messages,
|
|||
|
|
tools=TOOLS_SCHEMA,
|
|||
|
|
tool_choice="auto",
|
|||
|
|
)
|
|||
|
|
assistant_msg = response.choices[0].message
|
|||
|
|
|
|||
|
|
# 把模型的回复写入历史(必须手动构造,content 为 None 时补成 "")
|
|||
|
|
msg_dict = {"role": "assistant", "content": assistant_msg.content or ""}
|
|||
|
|
if assistant_msg.tool_calls:
|
|||
|
|
msg_dict["tool_calls"] = [tc.model_dump() for tc in assistant_msg.tool_calls]
|
|||
|
|
messages.append(msg_dict)
|
|||
|
|
|
|||
|
|
if verbose and assistant_msg.content:
|
|||
|
|
print(f"[Thought] {assistant_msg.content}")
|
|||
|
|
|
|||
|
|
# ---------- 终止条件:模型不再请求工具,直接给出答案 ----------
|
|||
|
|
if not assistant_msg.tool_calls:
|
|||
|
|
final_answer = assistant_msg.content or ""
|
|||
|
|
if verbose:
|
|||
|
|
print(f"[Final] {final_answer}")
|
|||
|
|
return final_answer, messages
|
|||
|
|
|
|||
|
|
# ---------- Action + Observation:执行所有工具调用 ----------
|
|||
|
|
for tool_call in assistant_msg.tool_calls:
|
|||
|
|
name = tool_call.function.name
|
|||
|
|
|
|||
|
|
# 注意:arguments 是 JSON 字符串,必须解析成 dict 再传参
|
|||
|
|
try:
|
|||
|
|
args = json.loads(tool_call.function.arguments or "{}")
|
|||
|
|
except json.JSONDecodeError:
|
|||
|
|
args = {}
|
|||
|
|
|
|||
|
|
if verbose:
|
|||
|
|
print(f"[Action] {name}({json.dumps(args, ensure_ascii=False)})")
|
|||
|
|
|
|||
|
|
func = TOOL_REGISTRY.get(name)
|
|||
|
|
if func is None:
|
|||
|
|
result = f"错误:未知工具 {name}"
|
|||
|
|
else:
|
|||
|
|
try:
|
|||
|
|
result = func(**args)
|
|||
|
|
except Exception as e:
|
|||
|
|
result = f"工具执行异常:{e}"
|
|||
|
|
|
|||
|
|
result = str(result)
|
|||
|
|
if verbose:
|
|||
|
|
print(f"[Observation] {result}")
|
|||
|
|
|
|||
|
|
# 工具结果必须带上 tool_call_id,一一对应
|
|||
|
|
messages.append({
|
|||
|
|
"role": "tool",
|
|||
|
|
"tool_call_id": tool_call.id,
|
|||
|
|
"content": result,
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
# 超过最大步数仍未收敛
|
|||
|
|
fallback = "抱歉,我在限定步骤内没能得出结论。"
|
|||
|
|
if verbose:
|
|||
|
|
print(f"[Final] {fallback}")
|
|||
|
|
return fallback, messages
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ============================================================
|
|||
|
|
# 5. 运行入口
|
|||
|
|
# ============================================================
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
# 单轮测试
|
|||
|
|
for q in ["杭州天气怎么样?", "杭州和北京温差多少度?"]:
|
|||
|
|
print(f"\n\n########## 用户:{q} ##########")
|
|||
|
|
answer, history = run_react_agent(q)
|
|||
|
|
print(f"回答:{answer}")
|
|||
|
|
|
|||
|
|
# 如果想做成多轮对话的交互式 Agent,可以这样:
|
|||
|
|
# while True:
|
|||
|
|
# q = input("\n你:")
|
|||
|
|
# if q in ("exit", "quit"):
|
|||
|
|
# break
|
|||
|
|
# answer, _ = run_react_agent(q)
|
|||
|
|
# print("助手:", answer)
|