AI Agent的实现(TODO)

实现一个 AI Agent(AI 代理)的核心本质是:让大语言模型(LLM)具备“感知”、“思考/决策”和“执行工具”的能力,并通过一个循环机制不断纠错,直到完成目标。

不需要依赖复杂框架(如 LangChain 或 LlamaIndex),你可以仅用原生的 Python 和 LLM API(如 OpenAI API)从零搭建一个标准的 ReAct(Reasoning + Acting)机制 Agent。

一、 AI Agent 的核心架构

一个标准 Agent 包含四大要素:

               +--------------------------------------+
               |             系统提示词               |
               | (设定角色、格式约束、工具描述)        |
               +--------------------------------------+
                                  |
                                  v
+------------------+     +------------------+     +------------------+
|   用户目标/输入   | --> |  大语言模型 (LLM) | --> |   思考决策 (Thought) |
+------------------+     +------------------+     +------------------+
                                  ^                        |
                                  |                        v
                         +------------------+     +------------------+
                         | 状态更新/历史记录  | <-- |   执行工具 (Action)  |
                         +------------------+     +------------------+
  1. LLM(大脑):负责推理、分析当前状态并做出下一步决定。

  2. Tools(手脚):LLM 可以调用的函数或 API(例如:计算器、网页搜索、数据库查询)。

  3. Prompt & Protocol(协议):规定 LLM 思考和输出的固定格式(如 Thought -> Action -> Observation)。

  4. Agent Loop(循环运行控制):一个死循环(while loop),负责将工具执行的结果再扔回给 LLM,直到输出终极答案。

二、 手把手代码实现(零框架极简版)

以下是一个用 Python 实现的完整 ReAct 架构 Agent。它能够识别是否需要使用计算器或执行搜索,并在多步思考后给出答案。

1. 定义可用工具 (Tools)

先编写 Agent 可以调用的真实 Python 函数:


Python

import math

def calculate(expression: str) -> str:
    """计算数学表达式"""
    try:
        # 注意:实际生产环境中请勿直接使用 eval,这里仅作 Demo 演示
        result = eval(expression)
        return str(result)
    except Exception as e:
        return f"计算错误: {e}"

def get_word_length(word: str) -> str:
    """获取字符串的长度"""
    return str(len(word))

# 工具注册表
AVAILABLE_TOOLS = {
    "calculate": calculate,
    "get_word_length": get_word_length
}

2. 设计系统提示词 (Prompt Template)

告知模型它的思考流程和工具使用规范:


Python

SYSTEM_PROMPT = """你是一个具备思考和工具使用能力的 AI 助手。

你可以使用以下工具:
1. calculate(expression): 计算数学表达式,例如 calculate("2 + 2")
2. get_word_length(word): 计算单词或文本的长度,例如 get_word_length("hello")

请严格按照以下格式回答问题:

Thought: 你当前对问题的思考和下一步计划。
Action: 要调用的工具名称,必须是 [calculate, get_word_length] 之一。如果不需要调用工具,请输入 None。
Action Input: 传入工具的参数。如果 Action 为 None,这里填 None。
Observation: (此步骤由系统执行并提供给你,你不要自己生成 Observation)

当你有足够的信心得到最终答案时,使用以下格式输出并结束:
Thought: 我已经知道了最终答案。
Final Answer: 最终的解答内容。

开始!
"""

3. 实现 Agent 运行主循环 (Agent Loop)

这是 Agent 的核心控制代码:


Python

import re
from openai import OpenAI

client = OpenAI() # 需要设置 OPENAI_API_KEY 环境变量

def parse_llm_output(text: str):
    """解析 LLM 返回的文本,提取 Thought, Action, Action Input"""
    action_match = re.search(r"Action:\s*(.*)", text)
    input_match = re.search(r"Action Input:\s*(.*)", text)
    
    action = action_match.group(1).strip() if action_match else "None"
    action_input = input_match.group(1).strip() if input_match else "None"
    
    return action, action_input

def run_agent(user_query: str, max_turns: int = 5):
    # 初始化对话历史
    messages = [
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user", "content": f"用户问题: {user_query}"}
    ]
    
    print(f"=== 开始处理任务: {user_query} ===\n")
    
    for turn in range(max_turns):
        # 1. 调用 LLM 获取思考过程
        response = client.chat.completions.create(
            model="gpt-4o",
            messages=messages,
            temperature=0
        )
        llm_output = response.choices[0].message.content
        print(llm_output)
        
        # 将 LLM 的输出加入历史记录
        messages.append({"role": "assistant", "content": llm_output})
        
        # 2. 判断是否已经得到最终答案
        if "Final Answer:" in llm_output:
            print("\n=== 任务完成 ===")
            return
            
        # 3. 解析 Action 和 Action Input
        action, action_input = parse_llm_output(llm_output)
        
        # 4. 执行对应的工具 (Action)
        if action in AVAILABLE_TOOLS:
            tool_func = AVAILABLE_TOOLS[action]
            # 清理字符串引号
            clean_input = action_input.strip("'\"")
            observation = tool_func(clean_input)
        else:
            observation = f"未知工具: {action}"
            
        print(f"Observation: {observation}\n" + "-"*40)
        
        # 5. 将观察到的结果反馈给 LLM(作为下一轮循环的输入)
        messages.append({
            "role": "user", 
            "content": f"Observation: {observation}"
        })

# 运行测试
if __name__ == "__main__":
    query = "请问单词 'supercalifragilisticexpialidocious' 的长度乘以 12.5 是多少?"
    run_agent(query)

三、 进阶:如何让 Agent 更实用?

如果希望构建能用于生产环境的复杂 Agent,可以在极简框架的基础上扩展以下模块:

1. 使用原生 Function Calling / Tool Call

上面的示例通过文本正则匹配解析 Action,容易因为格式错乱出错。主流 LLM API(OpenAI、Anthropic、DeepSeek)都支持原生的 Tool Calling API,可以直接传 JSON Schema 格式的函数定义,由大模型原生地返回结构化的工具调用参数。

2. 增加持久化记忆 (Memory)

  • 短期记忆:维护一个消息队列或滑动窗口,截断过长的历史 Context。

  • 长期记忆:引入向量数据库(如 Chroma、Qdrant),将用户偏好或历史知识嵌入(Embedding)后按需检索(RAG)。

3. 多 Agent 协作 (Multi-Agent Systems)

对于极其复杂的工作流(如自动写软件工程、市场调研报告),单个 Agent 容易迷失。可以采用多 Agent 协作设计模式:

  • Supervisor 模式:由一个 Leader Agent 负责任务拆解分发,子 Agent 各司其职(如:写代码、测试、审查)。

  • Peer-to-Peer 模式:Agent 之间互相传达消息与协同谈判。

四、 常用开源框架生态

自己编写底层代码能助你透彻理解原理。当逻辑变得庞大时,可以借助开源生态快速搭建:

"""
最简单的 AI Agent 示例(基于 OpenRouter 免费模型的工具调用循环)

工作原理(ReAct 风格):
    1. 把用户问题和可用的"工具"一起发给大模型
    2. 大模型决定是直接回答,还是需要调用某个工具
    3. 如果需要工具:我们实际执行工具,把结果回传给模型继续推理
    4. 重复直到模型给出最终答案

本脚本只用 Python 标准库(urllib/json),不依赖任何第三方包。
运行:python agent.py
"""

import os
import sys
import json
import datetime
import urllib.parse
import urllib.request

BASE_URL = "https://openrouter.ai/api/v1"
# 换成 OpenRouter 上任意免费模型 id,例如:
#   meta-llama/llama-3.3-70b-instruct:free
#   deepseek/deepseek-chat:free
MODEL = os.getenv("MODEL", "meta-llama/llama-3.3-70b-instruct:free")


def load_api_key() -> str:
    """优先取环境变量,否则尝试解析同目录 .env 文件。"""
    key = os.getenv("OPENROUTER_API_KEY", "")
    if key:
        return key.strip()
    env_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), ".env")
    if os.path.exists(env_file):
        with open(env_file, "r", encoding="utf-8") as f:
            for line in f:
                line = line.strip()
                if line.startswith("OPENROUTER_API_KEY"):
                    key = line.split("=", 1)[1].strip().strip('"').strip("'")
                    return key
    return ""


API_KEY = load_api_key()

# ---------------------------------------------------------------------------
# 工具定义(给模型看的"说明书")
# ---------------------------------------------------------------------------
TOOLS = [
    {
        "type": "function",
        "function": {
            "name": "get_now",
            "description": "获取当前的日期和时间。",
            "parameters": {"type": "object", "properties": {}},
        },
    },
    {
        "type": "function",
        "function": {
            "name": "calculate",
            "description": "执行两个数字的四则运算,返回具体结果。",
            "parameters": {
                "type": "object",
                "properties": {
                    "a": {"type": "number", "description": "第一个数字"},
                    "b": {"type": "number", "description": "第二个数字"},
                    "op": {
                        "type": "string",
                        "enum": ["+", "-", "*", "/"],
                        "description": "运算符",
                    },
                },
                "required": ["a", "b", "op"],
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "search_wikipedia",
            "description": "在维基百科搜索一个词语,返回第一段简介。",
            "parameters": {
                "type": "object",
                "properties": {
                    "query": {"type": "string", "description": "要搜索的词"}
                },
                "required": ["query"],
            },
        },
    },
]
# ---------------------------------------------------------------------------
# 工具的实际实现
# ---------------------------------------------------------------------------
def get_now() -> str:
    return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")


def calculate(a: float, b: float, op: str) -> str:
    if op == "+":
        return str(a + b)
    if op == "-":
        return str(a - b)
    if op == "*":
        return str(a * b)
    if op == "/":
        if b == 0:
            return "错误:除数不能为 0"
        return str(a / b)
    return f"未知运算符: {op}"


def search_wikipedia(query: str) -> str:
    # 用维基百科的免费开放 API(不需要任何密钥)
    url = (
        "https://zh.wikipedia.org/w/api.php?"
        + urllib.parse.urlencode(
            {
                "action": "query",
                "format": "json",
                "prop": "extracts",
                "exintro": True,
                "explaintext": True,
                "titles": query,
            }
        )
    )
    try:
        req = urllib.request.Request(
            url,
            headers={"User-Agent": "MyAIAgent/1.0 (educational demo; contact me@example.com)"},
        )
        with urllib.request.urlopen(req, timeout=10) as resp:
            data = json.loads(resp.read().decode("utf-8"))
        pages = data.get("query", {}).get("pages", {})
        for _, page in pages.items():
            extract = page.get("extract", "")
            if extract:
                return extract[:500]
        return f"没有找到关于“{query}”的资料。"
    except Exception as e:  # 网络问题等
        return f"维基百科查询失败: {e}"


# 工具名 -> 实际函数 的映射表
TOOL_IMPLEMENTATIONS = {
    "get_now": lambda: json.dumps({"result": get_now()}),
    "calculate": lambda **kw: json.dumps({"result": calculate(kw["a"], kw["b"], kw["op"])}),
    "search_wikipedia": lambda **kw: json.dumps({"result": search_wikipedia(kw["query"])}),
}
# ---------------------------------------------------------------------------
# 调用模型的辅助函数(纯标准库,OpenAI 兼容接口)
# ---------------------------------------------------------------------------
def chat_completion(messages, tools):
    """POST 到 OpenRouter 的 /chat/completions,返回 message 字典。"""
    url = BASE_URL + "/chat/completions"
    body = json.dumps(
        {"model": MODEL, "messages": messages, "tools": tools, "tool_choice": "auto"}
    ).encode("utf-8")

    req = urllib.request.Request(
        url,
        data=body,
        headers={
            "Authorization": f"Bearer {API_KEY}",
            "Content-Type": "application/json",
            "HTTP-Referer": "http://localhost",
            "X-Title": "My First AI Agent",
        },
        method="POST",
    )
    with urllib.request.urlopen(req, timeout=120) as resp:
        data = json.loads(resp.read().decode("utf-8"))
    return data["choices"][0]["message"]


def run_agent(prompt: str, max_steps: int = 5):
    # 消息历史:第一句是系统提示,之后是用户的提问
    messages = [
        {
            "role": "system",
            "content": (
                "你是一个乐于助人的 AI Agent。你可以调用工具来获取真实信息,"
                "然后基于工具返回的结果回答用户。回答请使用中文。"
            ),
        },
        {"role": "user", "content": prompt},
    ]

    print(f"\n=== 用户问题: {prompt} ===")
    for step in range(1, max_steps + 1):
        print(f"\n▶ 第 {step} 步:调用模型 ...")

        msg = chat_completion(messages, TOOLS)
        messages.append(msg)  # 保留模型这一步的发言/工具调用

        # 模型没有要求调用工具 => 说明它给出了最终答案
        tool_calls = msg.get("tool_calls")
        if not tool_calls:
            print("\n✅ 最终回答:")
            print(msg.get("content"))
            return msg.get("content")

        # 模型要求调用工具:逐个执行,把结果塞回对话
        for call in tool_calls:
            fn = call["function"]
            name, args = fn["name"], json.loads(fn["arguments"] or "{}")
            print(f"  🔧 调用工具 [{name}],参数: {args}")
            result = TOOL_IMPLEMENTATIONS[name](**args)
            messages.append(
                {
                    "role": "tool",
                    "tool_call_id": call["id"],
                    "content": result,
                }
            )

    print("\n⚠ 达到最大步骤数,未能完成。")
    return None


# ---------------------------------------------------------------------------
# 入口
# ---------------------------------------------------------------------------
if __name__ == "__main__":
    # 让中文/特殊字符在任何 Windows 终端都能正常打印
    if hasattr(sys.stdout, "reconfigure"):
        sys.stdout.reconfigure(encoding="utf-8", errors="replace")

    if not API_KEY:
        print("缺少 API Key。请先在官网 https://openrouter.ai 注册并获取密钥,")
        print("然后把它写到同目录的 .env 文件里:OPENROUTER_API_KEY=你的密钥")
        print("示例见 .env.example")
        raise SystemExit(1)

    print(f"使用模型: {MODEL}")

    questions = [
        "现在几点钟了?今天是几月几号?",
        "帮我计算 12345 * 678 等于多少?",
        "用中文简单介绍一下“人工智能”。",
    ]
    # 你可以改成任意自己想问的中文问题,例如:
    # questions = ["今天的天气怎么样?", "帮我写一首关于秋天的五行诗"]

    for q in questions:
        run_agent(q)
        print("\n" + "-" * 60)
  • LangGraph / LangChain:适合构建有向无环图(DAG)和复杂状态调度的企业级 Agent。

  • CrewAI:高度面向角色扮演和团队协作(Multi-Agent)的开箱即用框架。

  • AutoGen (Microsoft):微软主导的高扩展性多 Agent 讨论与代码生成框架。

  • Dify / FastGPT:支持可视化 Flow 画布调优的工作流 Agent 平台(适合低代码部署)。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值