Day 1:让 AI 帮你"做决定"——你的第一个 Agent

0 阅读6分钟

Day 1:让 AI 帮你"做决定"——你的第一个 Agent

《7天从零手搓 AI Agent》第1篇 · 今日成果:一个能根据问题自动判断"要不要查天气"的最小 Agent

day1.png

大家好,欢迎来到小撒的私房菜,我是小撒。

很多人第一次接触 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     # 主程序

小结

今天做的事情很简单,但每一步都有意义:

  1. llm.py:我们有了一个能对话的 AI
  2. agent.py:我们约束 AI 返回结构化 JSON
  3. actions.py:我们根据 JSON 执行实际动作
  4. main.py:把三者串起来,形成一个循环

这个结构,在后面6天里会一直沿用和扩展,不会推翻重来。


明天,Day 2:《给 Agent 装上真实工具——它能真的上网搜索了》

我们会把模拟天气换成真实搜索 API,并建立一个可扩展的工具系统。


本系列所有代码开源,GitHub 地址见文末。 如有问题,欢迎在评论区留言。

如果本教程对你有所帮助,留下一个免费的三连吧 ♥️!