在开始深入Harness的架构和原理之前,先亲手跑一个最小可用的Agent。这个Agent虽然简单,但它具备了完整的“感知-思考-行动”循环:接收一个目标,自主决定调用哪个工具,并基于工具结果给出最终回答。
你需要准备:
lets go,让我们开始这段奇妙旅程吧。
创建项目目录,安装两个库:
mkdir my-first-agent
cd my-first-agent
pip install openai rich
openai 用于调用LLM,rich 只是为了打印更美观的运行日志,不装也不影响核心逻辑。
将你的API Key设置为环境变量:
export OPENAI_API_KEY="sk-..."
创建一个文件 agent.py,输入以下代码:
import json
from openai import OpenAI
client = OpenAI()
# ---------- 1. 定义工具 ----------
def get_weather(city: str) -> str:
"""查询指定城市的天气(模拟数据)。"""
mock_data = {
"北京": "晴,26°C,湿度40%",
"上海": "多云,29°C,湿度65%",
"深圳": "雷阵雨,31°C,湿度80%",
}
return mock_data.get(city, f"暂无{city}的天气数据,请确认城市名称")
def calculate(expression: str) -> str:
"""计算数学表达式。"""
try:
result = eval(expression, {"__builtins__": {}}, {})
return f"计算结果:{result}"
except Exception as e:
return f"计算出错:{e}"
# 工具的JSON Schema,告诉LLM如何调用它们
TOOLS = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "查询指定城市的天气",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名称"}
},
"required": ["city"]
}
}
},
{
"type": "function",
"function": {
"name": "calculate",
"description": "计算数学表达式,支持加减乘除和括号",
"parameters": {
"type": "object",
"properties": {
"expression": {"type": "string", "description": "数学表达式,如 '(3+5)*2'"}
},
"required": ["expression"]
}
}
}
]
# 工具名到函数的映射
TOOL_MAP = {
"get_weather": get_weather,
"calculate": calculate,
}
# ---------- 2. Agent核心循环 ----------
def run_agent(user_input: str, max_iterations: int = 5):
messages = [{"role": "user", "content": user_input}]
for turn in range(max_iterations):
# 调用LLM
response = client.chat.completions.create(
model="gpt-4o-mini", # 用便宜快速的模型即可
messages=messages,
tools=TOOLS,
)
message = response.choices[0].message
# 如果LLM要调用工具
if message.tool_calls:
# 把LLM的决策加入对话历史
messages.append(message)
for tool_call in message.tool_calls:
name = tool_call.function.name
args = json.loads(tool_call.function.arguments)
print(f"[Agent调用工具] {name}({args})")
# 执行工具
result = TOOL_MAP[name](**args)
print(f"[工具返回] {result}")
# 将工具结果加入对话历史
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": result
})
else:
# LLM直接给出了最终回答
return message.content
return "达到最大迭代次数,Agent未能完成任务"
# ---------- 3. 运行 ----------
if __name__ == "__main__":
print("=" * 50)
print("你的第一个AI Agent")
print("试试:北京和上海哪个更热?答案乘2是多少?")
print("=" * 50)
while True:
user_input = input("\n你> ").strip()
if user_input.lower() in ("exit", "quit"):
break
if not user_input:
continue
answer = run_agent(user_input)
print(f"\nAgent> {answer}")
python agent.py
输入一个需要多步推理和工具调用的问题:
你> 北京今天多少度?这个数字乘以3再加5等于多少?
你会看到类似这样的输出:
[Agent调用工具] get_weather({'city': '北京'})
[工具返回] 晴,26°C,湿度40%
[Agent调用工具] calculate({'expression': '26 * 3 + 5'})
[工具返回] 计算结果:83
Agent> 北京今天26°C。26 × 3 + 5 = 83。
让我们拆解这段代码背后的核心机制:
第一,工具定义是Agent的“能力边界”。
TOOLS 列表中的JSON Schema告诉LLM:“你有两个工具可用,一个查天气,一个算数学。查天气需要一个city参数,计算需要一个expression参数。”没有这些定义,LLM不知道它能做什么。
第二,Agent循环是“感知-思考-行动”的不断重复。
用户输入 → LLM判断是否需要工具 → 需要 → 执行工具 → 结果反馈给LLM
↑ │
└──────────────┘
循环,直到LLM认为可以回答
每一轮循环中,LLM看到的是完整的对话历史(包括之前的工具调用和结果),这让它能够基于新信息做出下一步决策。
第三,LLM自主决定了调用什么、以什么顺序调用。
用户只说了“北京今天多少度?这个数字乘以3再加5等于多少?”——没有说“先查天气,然后算数学”。Agent自己知道:
这就是Agent与普通程序最本质的区别:你定义工具,它决定如何使用。
运行起来之后,你可以尝试不同的输入,观察Agent的行为:
场景A:Agent不知道答案,会主动调用工具
你> 深圳现在下雨吗?
[Agent调用工具] get_weather({'city': '深圳'})
[工具返回] 雷阵雨,31°C,湿度80%
Agent> 是的,深圳现在有雷阵雨,气温31°C。
场景B:不需要工具时,Agent直接回答
你> 你好,介绍一下你自己
Agent> 你好!我是一个AI助手,可以帮你查询天气和计算数学表达式...
场景C:信息不足时,Agent会尝试弥补
你> 帮我查一下天气
[Agent调用工具] get_weather({'city': '北京'}) # 可能默认查北京
场景D:工具没帮上忙时,Agent会老实说明
你> 广州今天天气如何?
[Agent调用工具] get_weather({'city': '广州'})
[工具返回] 暂无广州的天气数据,请确认城市名称
Agent> 抱歉,我暂时查不到广州的天气数据。
跑通了这个Agent,你就有了一个最小可用的Harness雏形。但它距离“可靠”还有很长的路:
| 缺失的能力 | 可能导致的问题 |
|---|---|
| 记忆管理 | 每个问题都是全新对话,Agent不记得你上一轮说过什么 |
| 错误重试 | 工具调用失败时,Agent不会自动重试或换策略 |
| 执行日志 | 关掉终端就丢了全部运行记录,出问题无法回溯 |
| 评估体系 | 你不知道这个Agent回答得对不对,更不知道改了Prompt后是变好还是变坏 |
| 安全边界 | eval() 执行任意表达式,如果用户输入恶意代码怎么办? |
| 成本控制 | 没有token计数,不知道每次运行花了多少钱 |
这些问题,正是本书后面各章节要逐一解决的。现在,你只需要记住:你已经亲手写了一个Agent,它能在没有你逐步指导的情况下,自主选择工具并完成任务。
Q: 运行报错 AuthenticationError?
确认环境变量设置正确:echo $OPENAI_API_KEY 应该能看到你的key。
Q: Agent反复调用同一个工具,陷入循环?
这是LLM推理不稳定的表现。可以尝试换一个模型(如 gpt-4o),或者简化问题。本书后面第6章会讨论如何用Harness来缓解这类问题。
Q: 想用国产模型或本地模型?
将 client = OpenAI() 改为指向兼容的API端点:
client = OpenAI(
base_url="https://your-model-endpoint.com/v1",
api_key="your-key"
)
只要模型支持Function Calling,代码逻辑完全一样。
在你进入下一章之前,留三个问题给自己:
带着这些问题继续读下去,你会更清楚Harness要解决的究竟是什么。
评论专区
评论加载中...登录后即可发表评论