构建自主决策的AI助手
大模型的工作原理:根据前面的文字,推理下一个词最可能是什么。
就像接龙游戏,看到"今天天气",它会推测下一个词可能是"很好"、"不错"等。
这导致很多事情它做不了:
核心思路:给大模型配备工具,让它知道什么时候该用什么工具!
同样的问题,有了工具就能解决:
获取时间工具 → 返回"2024年11月23日 20:30"计算器工具 → 返回精确结果天气API工具 → 返回实时天气邮件发送工具 → 真的发送邮件AI Agent = 大模型(大脑)+ 工具集(手脚)+ 自主决策能力
它能自己判断什么时候需要用工具,自己选择用哪个工具,自己调用工具完成任务!
在 Agent 工程里,除了“工具(Tool)”之外,常见还有一个更高一层的概念:Skill(技能)。 你可以把 Skill 理解成:面向某类任务的可复用解决方案,它把“怎么做”沉淀成一套稳定流程。
工具更像是一个原子能力:一次调用完成一件事。
例如:get_weather(city)、sql_query(query)、send_email(to, subject, body)。
技能更像是一套可复用的“完成任务的方法论/流程”,通常会编排多个工具,并附带规则和输出格式。 例如“写周报技能”= 读取任务列表 + 总结进展 + 输出固定模板。
get_weather(city)(只负责返回天气数据)get_weather → 提取温度/降雨/风力 → 生成穿衣/出行建议 → 输出为固定结构人类处理任务的过程:信息输入 → 大脑处理 → 采取行动
感知环境 → LLM大脑规划 → 调用工具执行 → 观察结果反馈
当你的 Agent 需要接入越来越多的外部能力(数据库、内部系统、文件、浏览器、代码执行器……),工程上最痛的一点是: 每个工具都要写一套“对接胶水”,不同框架还不通用。 MCP(Model Context Protocol)的目标就是:提供一个标准化方式,让模型/Agent 能“发现并调用工具”,并能读取结构化资源。
sql_query / send_email / get_weather。file://...、某个知识库条目等。# 一个典型的 MCP 工作流(概念级)
1) Agent 连接 MCP Server
2) 列举可用 Tools / Resources
3) 模型输出下一步动作:调用 tool(name, args)
4) Agent 执行 tool 调用 -> 得到结果
5) 将结果作为 Observation 追加到上下文 -> 继续 Think/Act/Observe一个完整的AI Agent系统包含4个核心模块:
LLM是Agent的"大脑",负责理解任务、规划步骤、做出决策。
Agent需要记住历史信息,才能做出更好的决策。
作用:记住当前对话的上下文
实现:Prompt中的历史消息
限制:受模型上下文长度限制(如128k tokens)
作用:存储大量历史知识和经验
实现:向量数据库(如Chroma)
优势:可以存储海量信息,随时检索
工具让Agent突破LLM的限制,能做更多事情!
Google搜索、网页爬虫
计算器、代码执行器
数据库查询、API调用
图片生成、视频编辑
Agent根据规划,执行具体的操作。
AI Agent = LLM大脑 + 记忆 + 工具 + 行动能力
ReAct(Reasoning + Acting)是AI Agent最重要的工作机制,让Agent能够边思考边行动,像人一样解决问题!
❌ 问题:LLM虽然强大,但有局限性:
✅ 解决:ReAct让LLM能够:
ReAct的核心是思考(Thought)→ 行动(Action)→ 观察(Observation)的循环:
LLM分析问题,决定下一步该做什么
例如:"用户想知道周杰伦演唱会信息,我需要搜索最新消息"
执行具体操作,调用工具
例如:调用Google搜索API,搜索"2024周杰伦演唱会"
查看工具返回的结果
例如:搜索返回了多个网页链接和摘要
如果信息足够 → 生成最终答案
如果信息不够 → 继续思考、行动、观察
问题:"2024年周杰伦最新演唱会的时间和地点是什么?"
用户想知道2024年周杰伦最新演唱会信息,需要搜索最新资讯
调用Google搜索API,搜索"2024周杰伦演唱会"
搜索返回了6个相关网页链接
需要查看网页内容获取详细信息
访问第一个网页,提取内容
网页显示:2024年12月上海演唱会
已获取足够信息,可以回答用户
2024年周杰伦最新演唱会将于12月在上海举行
💡 提示:这个例子里“工具”只是概念上的
这里用的是“调用工具”的思路来理解 ReAct(多步推进任务)。如果你关心“工具规模变大后的复用与治理”,可以回看本章前面的 🧩 MCP 小节。
现在让我们用通义千问API实现一个带工具调用的Agent。
pip3 install dashscopeimport dashscope
from dashscope import Generation
import json
import os
# 设置API Key
dashscope.api_key = os.getenv('DASHSCOPE_API_KEY')
class SimpleAgent:
def __init__(self):
"""初始化Agent"""
self.messages = []
self.tools = {
"calculator": self.calculator,
"get_weather": self.get_weather
}
def calculator(self, expression):
"""计算器工具"""
try:
result = eval(expression)
return f"计算结果:{result}"
except Exception as e:
return f"计算错误:{str(e)}"
def get_weather(self, city):
"""获取天气工具(模拟)"""
# 实际应用中应该调用真实的天气API
weather_data = {
"北京": "晴天,15-25℃",
"上海": "多云,18-26℃",
"深圳": "小雨,22-28℃"
}
return weather_data.get(city, f"{city}的天气信息暂时无法获取")通义千问需要知道有哪些工具可用,以及如何使用它们:
def get_tool_definitions(self):
"""定义可用工具的描述"""
return [
{
"type": "function",
"function": {
"name": "calculator",
"description": "执行数学计算,支持加减乘除等基本运算",
"parameters": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "要计算的数学表达式,如:'3.14 * 2 + 5'"
}
},
"required": ["expression"]
}
}
},
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取指定城市的天气信息",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,如:北京、上海"
}
},
"required": ["city"]
}
}
}
] def run(self, user_input):
"""运行Agent - ReAct循环"""
print(f"🙋 用户:{user_input}\n")
# 添加用户消息
self.messages.append({
"role": "user",
"content": user_input
})
# ReAct循环:最多执行5轮
for i in range(5):
print(f"--- 第{i+1}轮思考 ---")
# 调用LLM进行推理
response = Generation.call(
model='qwen-plus',
messages=self.messages,
tools=self.get_tool_definitions(),
result_format='message'
)
# 基础错误检查
if response is None:
print("❌ 错误:API响应为空")
return "抱歉,无法获取响应"
if response.status_code != 200:
print(f"❌ 错误:API请求失败,状态码:{response.status_code}")
if hasattr(response, 'message') and response.message:
print(f"错误信息:{response.message}")
return "抱歉,API请求失败"
if response.output is None:
print("❌ 错误:API响应output为空")
return "抱歉,无法获取响应内容"
assistant_message = response.output.choices[0].message
# 如果assistant_message是对象,转换为字典
if not isinstance(assistant_message, dict):
assistant_message = assistant_message.__dict__
# 检查是否需要调用工具
tool_calls = assistant_message.get('tool_calls')
if tool_calls:
# 有工具调用
tool_call = tool_calls[0]
# tool_call 可能是 dict,也可能是对象
if isinstance(tool_call, dict):
function_name = tool_call['function']['name']
arguments = json.loads(tool_call['function']['arguments'])
else:
function_name = tool_call.function.name
arguments = json.loads(tool_call.function.arguments)
print(f"💭 思考:需要使用工具 {function_name}")
print(f"🎬 行动:调用 {function_name},参数:{arguments}")
# 执行工具
if function_name in self.tools:
result = self.tools[function_name](**arguments)
print(f"👀 观察:{result}\n")
# 将工具调用和结果添加到消息历史
self.messages.append({
"role": "assistant",
"content": "",
"tool_calls": [tool_call]
})
self.messages.append({
"role": "tool",
"content": result,
"name": function_name
})
else:
print(f"❌ 错误:工具 {function_name} 不存在\n")
break
else:
# 没有工具调用,返回最终答案
final_answer = assistant_message.get('content', '无法获取回答')
print(f"✅ 最终答案:{final_answer}\n")
return final_answer
return "抱歉,无法完成任务"# 创建Agent
agent = SimpleAgent()
# 测试1:需要使用计算器
print("=" * 50)
print("测试1:计算任务")
print("=" * 50)
agent.run("请帮我计算 123.456 * 789 + 456 的结果")
# 测试2:需要查询天气
print("\n" + "=" * 50)
print("测试2:天气查询")
print("=" * 50)
agent = SimpleAgent() # 重新创建,清空历史
agent.run("北京今天天气怎么样?")
# 测试3:不需要工具
print("\n" + "=" * 50)
print("测试3:普通对话")
print("=" * 50)
agent = SimpleAgent()
agent.run("请介绍一下Python编程语言")==================================================
测试1:计算任务
==================================================
🙋 用户:请帮我计算 123.456 * 789 + 456 的结果
--- 第1轮思考 ---
💭 思考:需要使用工具 calculator
🎬 行动:调用 calculator,参数:{'expression': '123.456 * 789 + 456'}
👀 观察:计算结果:97846.784
--- 第2轮思考 ---
✅ 最终答案:计算结果是 97846.784
==================================================
测试2:天气查询
==================================================
🙋 用户:北京今天天气怎么样?
--- 第1轮思考 ---
💭 思考:需要使用工具 get_weather
🎬 行动:调用 get_weather,参数:{'city': '北京'}
👀 观察:晴天,15-25℃
--- 第2轮思考 ---
✅ 最终答案:北京今天是晴天,气温在15-25℃之间。这一章的核心是 Agent:模型不只是“一次性回答”,而是会在多轮中 思考 → 选择工具 → 观察结果 → 更新记忆 → 再行动。 下面用一个更贴合 Agent 的案例: “帮我做一个周末出行计划:查天气、估算预算、输出最终清单”。
初学者做 Agent 常见误区:只写“一个大函数”,把所有逻辑塞进 prompt。
更好的方式是让 Agent 每轮都产出一个结构化动作(要不要调用工具、调用哪个、参数是什么),并把结果写入 memory。
import json
import os
from typing import Any, Dict, List
import dashscope
from dashscope import Generation
dashscope.api_key = os.getenv("DASHSCOPE_API_KEY")
def get_weather(city: str) -> str:
# 示例:真实项目请调用天气 API
return "晴 15-25℃"
def calculator(expression: str) -> float:
# 注意:这里只是教学示例,不要在生产环境 eval
return eval(expression)
def export_markdown(content: str) -> str:
# 教学版:真实项目可以写文件、上传对象存储、生成下载链接等
return "已导出 markdown(示例)"
class SimpleReActAgent:
def __init__(self):
# memory:用于保存“可检验的中间状态/工具执行轨迹”。
# - 这不是为了让模型“凭空更聪明”,而是为了让多轮任务更稳定、可复盘。
# - 建议结构化存储(step/tool/args/observation),方便调试与复现。
# - 若要做“长期记忆”,应把摘要写入数据库/向量库,而不是一直塞在内存列表里。
self.memory: List[Dict[str, Any]] = []
self.messages: List[Dict[str, Any]] = []
self.tools = {
"get_weather": get_weather,
"calculator": calculator,
"export_markdown": export_markdown,
}
def get_tool_definitions(self):
return [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取指定城市的天气信息",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名称,如:北京、上海"}
},
"required": ["city"],
},
},
},
{
"type": "function",
"function": {
"name": "calculator",
"description": "执行数学计算,支持加减乘除等基本运算",
"parameters": {
"type": "object",
"properties": {
"expression": {"type": "string", "description": "要计算的数学表达式"}
},
"required": ["expression"],
},
},
},
{
"type": "function",
"function": {
"name": "export_markdown",
"description": "导出 markdown 内容(教学示例)",
"parameters": {
"type": "object",
"properties": {
"content": {"type": "string", "description": "要导出的 markdown 正文"}
},
"required": ["content"],
},
},
},
]
def run(self, user_goal: str, max_steps: int = 8):
if not dashscope.api_key:
raise RuntimeError("请先设置环境变量 DASHSCOPE_API_KEY")
# messages:短期记忆(Conversation Memory)
# - 本次对话的上下文在这里滚动累积,并随每轮请求发给模型。
# - 真正工程化时会做:滑动窗口、总结摘要、敏感信息过滤、工具结果裁剪。
self.messages = [
{
"role": "system",
"content": """你是一个严谨的规划型 Agent。请按照以下规则工作:
1. 如果用户没有提供必要信息(如城市、预算等),请先询问这些信息
2. 只有获得必要信息后,才开始调用工具
3. 每轮你可以选择调用一个工具来推进任务
4. 最后导出完整的出行计划清单
必要信息:
- 出行城市(必须)
- 预算范围(可选,如果没有会按中等预算估算)
- 出行人数(可选,默认1人)""",
},
{"role": "user", "content": user_goal},
]
for step in range(max_steps):
resp = Generation.call(
model="qwen-plus",
messages=self.messages,
tools=self.get_tool_definitions(),
result_format="message",
)
assistant_message = resp.output.choices[0].message
if not isinstance(assistant_message, dict):
assistant_message = assistant_message.__dict__
tool_calls = assistant_message.get("tool_calls")
if not tool_calls:
# 如果没有工具调用,检查是否是询问信息的回复
content = assistant_message.get("content", "")
self.messages.append({
"role": "assistant",
"content": content
})
# 如果是询问信息,我们需要模拟用户提供信息来继续
if "城市" in content or "预算" in content or "人数" in content:
# 模拟用户提供完整信息
simulated_response = "北京,预算2000元,2人"
self.messages.append({
"role": "user",
"content": simulated_response
})
print(f"👤 模拟用户回复:{simulated_response}")
continue # 继续下一轮
else:
return content
tool_call = tool_calls[0]
if isinstance(tool_call, dict):
tool_name = tool_call["function"]["name"]
tool_args = json.loads(tool_call["function"]["arguments"])
else:
tool_name = tool_call.function.name
tool_args = json.loads(tool_call.function.arguments)
if tool_name not in self.tools:
return f"工具不允许:{tool_name}"
# 过滤参数,只传递函数实际需要的参数
import inspect
tool_func = self.tools[tool_name]
func_params = inspect.signature(tool_func).parameters
filtered_args = {k: v for k, v in tool_args.items() if k in func_params}
observation = tool_func(**filtered_args)
print(f"🔧 调用工具:{tool_name}({filtered_args}) → {observation}")
# 把"本轮动作与结果"写入 memory(State/Scratchpad)
# - 这里存的是工具调用轨迹,方便后续:复盘、调试、或做摘要写入长期记忆。
# - 注意:不要把敏感原文(Token/手机号等)直接写入长期存储;必要时先脱敏。
self.memory.append({
"step": step + 1,
"tool": tool_name,
"args": filtered_args,
"args": tool_args,
"observation": observation,
})
self.messages.append({
"role": "assistant",
"content": "",
"tool_calls": [tool_call],
})
self.messages.append({
"role": "tool",
"content": str(observation),
"name": tool_name,
})
if tool_name == "export_markdown":
return observation
return "未在步数内完成(示例)"
agent = SimpleReActAgent()
result = agent.run("帮我做一个周末出行计划:查天气、估算预算、输出清单")
print(result)
print("memory=", agent.memory)decide_action 换成 LLM,就得到真正的智能 Agent。请完成这些练习,巩固 Agent 的核心能力(ReAct / 工具路由 / 记忆 / 终止条件 / 安全)
⚠️ 提示:Agent 面试高频点一般集中在:工具安全边界、终止/防死循环、可观测性(日志)、评测与回放
1. 什么是 ReAct?为什么它比“一次性回答”更像 Agent?
请用自己的话说明:Think/Act/Observe 各自负责什么,以及它们如何形成闭环。
2. Tool Router(工具路由)解决了什么问题?
为什么要让每轮输出结构化动作(tool + args),而不是把逻辑写死在 prompt 里?
3. Memory(记忆/状态)应该存什么?不应该存什么?
从“可复盘/可控/隐私安全”的角度回答。
4. 为什么要有终止条件(stop condition)?举 2 个例子。
比如完成任务、或无法完成时退出。你会如何设计 max_steps?
5. 为什么示例里的 eval 有风险?如何替换成“安全计算工具”?
请写出 2 个安全策略(白名单语法/沙箱/解析器等)。
6. 工具输出为什么要“结构化”?你会用什么格式?
请举例说明:工具返回 JSON 对 Agent 调试与可观测性有什么帮助。
7. 如何防止 Agent 反复调用同一个工具(死循环)?
请给出 2 个检测/打断策略(重复模式、无进展检测、成本预算等)。
8.【系统设计】如何设计一个“可审计”的 Agent?
回答时请覆盖:动作日志、工具输入输出、错误与耗时、回放能力。
9.【安全题】工具调用场景下,提示注入(Prompt Injection)通常怎么发生?怎么防?
请至少给出 2 个防护点:工具白名单、参数校验、上下文隔离等。
10.【评测题】如何评测一个 Agent 是否“真的更好”?
请给出 3 个指标:成功率、成本、步数、工具错误率、用户体验等。
掌握了AI Agent的基础后,下一章我们将学习Prompt Engineering(提示词工程)的高级技巧, 让你能够更好地控制和优化Agent的行为!