Day 1:让 AI 帮你"做决定"——你的第一个 Agent
《7天从零手搓 AI Agent》第1篇 · 今日成果:一个能根据问题自动判断"要不要查天气"的最小 Agent
大家好,欢迎来到小撒的私房菜,我是小撒。
很多人第一次接触 Agent,脑子里是这样一幅图:
一个超级智能的 AI,能自主完成各种复杂任务,还能联网、调用工具、写代码……
这没错。但这幅图的问题是——它太大了,你不知道从哪里开始。
所以今天我们做一个最小的 Agent。
小到什么程度?
只做一件事:你问它问题,它判断要不要查天气,然后查给你看。
就这样。
但这就是所有 Agent 的核心骨架。后面6天,都是在这个骨架上加东西。
Agent 到底是什么
先别看定义,看行为:
你说:今天北京天气怎么样?
Agent 的内部过程:
1. 收到你的话(感知)
2. 想一想:这个问题需要查天气工具(思考)
3. 去调用查天气的函数(行动)
4. 把结果告诉你(输出)
就是这样。
感知 → 思考 → 行动。
不是什么神奇的东西。是一个接收输入、用 AI 决策、执行动作、输出结果的程序。
环境准备(5分钟搞定)
mkdir my_agent
cd my_agent
pip install openai python-dotenv
然后创建一个 .env 文件(就是一个普通文本文件,名字叫 .env):
OPENAI_API_KEY=你的key
如果用 DeepSeek,key 填 DeepSeek 的,后面代码里加一行 base_url 就行,后面会说。
第一个文件:llm.py
这个文件只做一件事:帮我们调用 AI,返回文字。
# llm.py
import os
from openai import OpenAI
from dotenv import load_dotenv
load_dotenv()
client = OpenAI(
api_key=os.environ["OPENAI_API_KEY"],
# 如果用 DeepSeek,取消注释:
# base_url="https://api.deepseek.com",
)
def chat(messages: list[dict]) -> str:
response = client.chat.completions.create(
model="gpt-4o-mini", # DeepSeek 改成 "deepseek-chat"
messages=messages,
temperature=0, # 0 = 输出更稳定,不乱发挥
)
return response.choices[0].message.content
就这几行。先不用全懂,知道它的作用就行:给它一组消息,它返回 AI 的回复。
今天最重要的概念:让 AI 返回 JSON
这是整个 Day 1 最值得停下来想清楚的地方。
问题是这样的:
你直接问 AI"用不用查天气",它会回答"好的,需要查天气……"这种自然语言。
但程序没法理解"好的,需要查天气……"。程序需要的是一个可以解析的结构,比如:
{"action": "get_weather", "city": "北京"}
或者:
{"action": "answer", "content": "1+1等于2"}
怎么做到这一点?
靠 System Prompt。
你在系统提示里告诉 AI:"你只能返回 JSON,格式是这样的,不能说任何其他话。"
AI 会遵守这个约束(大多数时候)。这就是"结构化输出"的朴素实现。
第二个文件:agent.py
# agent.py
from llm import chat
SYSTEM_PROMPT = """你是一个智能助手。
当用户问你问题时,判断:是直接回答,还是需要查天气。
你必须用 JSON 格式回复,不能说任何其他话。
格式如下:
如果可以直接回答:
{"action": "answer", "content": "你的回答内容"}
如果需要查天气:
{"action": "get_weather", "city": "城市名"}
只返回 JSON,不要加任何解释、前缀或代码块标记。"""
def run_agent(user_input: str) -> str:
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_input},
]
response = chat(messages)
print(f"[AI 决策]: {response}") # 调试用,让你看到 AI 在想什么
return response
注意那行 print。
这是整个系列里最重要的调试习惯:永远打印 AI 的原始输出。
出问题了,第一步就是看这行打印,看 AI 到底返回了什么。
第三个文件:actions.py
AI 说"要查天气",谁来查?
今天用模拟数据,明天换真实 API。
# actions.py
import json
import re
def get_weather(city: str) -> str:
"""模拟天气数据,Day2 换真实 API。"""
mock_data = {
"北京": "晴,15°C,东风3级",
"上海": "多云,18°C,南风2级",
"广州": "小雨,22°C,偏东风",
}
return mock_data.get(city, f"{city}:晴,20°C(模拟数据)")
def safe_parse_json(text: str) -> dict:
"""从 AI 回复里提取 JSON,即使 AI 多说了废话也能处理。"""
try:
return json.loads(text)
except json.JSONDecodeError:
pass
# AI 可能在 JSON 前后加了废话,尝试提取 {...} 块
match = re.search(r'\{.*\}', text, re.DOTALL)
if match:
try:
return json.loads(match.group())
except json.JSONDecodeError:
pass
# 兜底:把原文当作普通回答
return {"action": "answer", "content": text}
def execute_action(ai_response: str) -> str:
"""解析 AI 返回的 JSON,执行对应动作。"""
decision = safe_parse_json(ai_response)
action = decision.get("action")
if action == "answer":
return decision.get("content", "(AI 没有提供内容)")
elif action == "get_weather":
city = decision.get("city", "未知城市")
weather = get_weather(city)
return f"{city}的天气:{weather}"
else:
return f"(未知动作 {action!r})"
注意 safe_parse_json 函数。
AI 有时候会不老实,在 JSON 前面加"好的,我来回答你:"之类的废话。这个函数会用正则表达式把 JSON 块提取出来,不会因为 AI 的小聪明导致程序崩溃。
这是一个很实用的防御性设计。
主程序:main.py
# main.py
from agent import run_agent
from actions import execute_action
def main() -> None:
print("=== 我的第一个 Agent ===")
print("输入 quit 退出\n")
while True:
user_input = input("你:").strip()
if not user_input:
continue
if user_input.lower() in ("quit", "exit"):
print("再见!")
break
ai_response = run_agent(user_input) # Step 1:让 AI 决策
result = execute_action(ai_response) # Step 2:执行 AI 的决定
print(f"Agent:{result}\n")
if __name__ == "__main__":
main()
跑起来
python main.py
应该看到:
=== 我的第一个 Agent ===
输入 quit 退出
你:北京今天天气怎么样?
[AI 决策]: {"action": "get_weather", "city": "北京"}
Agent:北京的天气:晴,15°C,东风3级
你:1+1等于多少?
[AI 决策]: {"action": "answer", "content": "1+1等于2。"}
Agent:1+1等于2。
你刚才做了什么:
- AI 判断了要不要用工具 ✓
- AI 把决定用 JSON 告诉了程序 ✓
- 程序解析 JSON 并执行了动作 ✓
这就是 Agent 的核心骨架。
最常见的两个问题
问题1:json.decoder.JSONDecodeError
AI 返回了奇怪的格式。
先加一行打印看看:
print(f"[DEBUG]: {repr(response)}")
然后检查 System Prompt——把"只返回 JSON,不要加任何解释"这句话放在最显眼的位置。
safe_parse_json 函数大多数情况下已经能兜住,但偶尔 AI 会返回完全无法解析的内容,这时打印原始值是第一步。
问题2:KeyError: 'OPENAI_API_KEY'
.env 文件没创建,或者文件名写成了 env.txt。
注意:.env 是以点开头的隐藏文件,在 macOS/Linux 用 ls -a 才能看到。
今天的项目结构
my_agent/
├── .env # API Key(不要上传 GitHub)
├── llm.py # AI 调用封装
├── agent.py # System Prompt + 调用 AI
├── actions.py # 解析决策 + 执行动作
└── main.py # 主程序
小结
今天做的事情很简单,但每一步都有意义:
llm.py:我们有了一个能对话的 AIagent.py:我们约束 AI 返回结构化 JSONactions.py:我们根据 JSON 执行实际动作main.py:把三者串起来,形成一个循环
这个结构,在后面6天里会一直沿用和扩展,不会推翻重来。
明天,Day 2:《给 Agent 装上真实工具——它能真的上网搜索了》
我们会把模拟天气换成真实搜索 API,并建立一个可扩展的工具系统。
本系列所有代码开源,GitHub 地址见文末。 如有问题,欢迎在评论区留言。
如果本教程对你有所帮助,留下一个免费的三连吧 ♥️!