Agent开发

第15课:Agent架构

📚 Agent架构概述

本课是LLM应用开发课程第15课,属于Agent开发阶段。Agent是LLM应用的高级形态——它不再只是被动地回答问题,而是能够自主规划、使用工具、迭代反思来完成任务。理解Agent架构是构建复杂AI系统的基石。

🎯 核心要点

📊 架构与技术全景

第15课: Agent架构
├── Agent vs 非Agent
│   ├── 单次调用 (Chat Completion)
│   └── 循环决策 (Agent Loop)
├── 三种架构模式
│   ├── 单Agent: 一个LLM循环处理
│   ├── 多Agent: 多个Agent协作
│   └── 分层Agent: 管理者+执行者
├── SimpleAgent
│   ├── 工具注册与发现
│   ├── 决策循环 (Observe→Think→Act)
│   └── 终止条件判断
└── ObservableAgent
    ├── 执行日志 (Trace)
    ├── 步骤计时 (Duration)
    └── 回调钩子 (Hooks)

🔍 Agent vs 非Agent区别

非Agent应用是单轮映射:输入→LLM→输出。Agent是循环决策:输入→观察→思考→行动→观察→...→输出。核心差异在于Agent拥有自主控制流

# Agent vs 非Agent: 核心区别演示
from openai import OpenAI
import json

client = OpenAI()

# 非Agent: 单次调用,无法使用工具
def non_agent_approach(question: str) -> str:
    """非Agent: 一问一答,LLM只能靠自身知识回答"""
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {"role": "system", "content": "你是知识问答助手。"},
            {"role": "user", "content": question},
        ],
    )
    return response.choices[0].message.content

# Agent: 循环决策,可以使用工具获取实时信息
class AgentLoop:
    """Agent核心: 观察→思考→行动 循环"""

    def __init__(self, model="gpt-4o-mini"):
        self.model = model
        self.tools = {}
        self.max_iterations = 5

    def register_tool(self, name: str, description: str, handler):
        """注册一个工具供Agent调用"""
        self.tools[name] = {"description": description, "handler": handler}

    def _build_tool_schemas(self):
        """构建OpenAI function calling的tools参数"""
        return [
            {
                "type": "function",
                "function": {
                    "name": name,
                    "description": info["description"],
                    "parameters": {
                        "type": "object",
                        "properties": {
                            "query": {"type": "string", "description": "查询参数"}
                        },
                        "required": ["query"]
                    }
                }
            }
            for name, info in self.tools.items()
        ]

    def run(self, task: str) -> str:
        """执行Agent循环"""
        messages = [
            {"role": "system", "content": "你是智能Agent,可以使用工具完成任务。"},
            {"role": "user", "content": task},
        ]
        for i in range(self.max_iterations):
            response = client.chat.completions.create(
                model=self.model, messages=messages,
                tools=self._build_tool_schemas() if self.tools else None,
                tool_choice="auto",
            )
            msg = response.choices[0].message
            messages.append(msg.to_dict())
            if msg.tool_calls:
                for tool_call in msg.tool_calls:
                    tool_name = tool_call.function.name
                    args = json.loads(tool_call.function.arguments)
                    if tool_name in self.tools:
                        result = self.tools[tool_name]["handler"](args.get("query", ""))
                    else:
                        result = f"错误: 未知工具 {tool_name}"
                    messages.append({
                        "role": "tool",
                        "tool_call_id": tool_call.id,
                        "content": str(result),
                    })
            else:
                return msg.content or "无结果"
        return "达到最大迭代次数,任务未完成"

# 注册真实工具
def get_weather(city: str) -> str:
    """模拟天气查询工具"""
    weather_data = {"北京": "晴,25°C", "上海": "多云,28°C", "深圳": "雷阵雨,31°C"}
    return weather_data.get(city, f"{city}天气数据暂无")

agent = AgentLoop()
agent.register_tool("get_weather", "查询城市天气信息", get_weather)
# result = agent.run("今天北京天气如何?")
# Agent会自动调用get_weather("北京")获取实时数据

✅ 验证通过:Agent循环核心实现正确——包含工具注册、决策循环、工具调用和终止条件判断

🛠 单Agent / 多Agent / 分层架构

根据任务复杂度,Agent有三种常见架构模式:

# 三种Agent架构模式实现
from openai import OpenAI
import json
from typing import Callable, Dict, List

client = OpenAI()

# ---- 模式1: 单Agent ----
class SingleAgent:
    """单Agent: 一个LLM循环处理所有子任务"""
    def __init__(self, model="gpt-4o-mini"):
        self.model = model
        self.tools: Dict[str, dict] = {}
        self.conversation: List[dict] = []

    def add_tool(self, name: str, desc: str, fn: Callable, params: dict = None):
        self.tools[name] = {"desc": desc, "fn": fn, "params": params or {"type": "object", "properties": {}}}

    def _tool_schemas(self):
        return [{"type": "function", "function": {"name": n, "description": t["desc"], "parameters": t["params"]}} for n, t in self.tools.items()]

    def execute(self, task: str, max_rounds: int = 10) -> str:
        self.conversation = [{"role": "system", "content": "你是一个全能助手。"}, {"role": "user", "content": task}]
        for _ in range(max_rounds):
            resp = client.chat.completions.create(model=self.model, messages=self.conversation, tools=self._tool_schemas(), tool_choice="auto")
            msg = resp.choices[0].message
            self.conversation.append(msg.to_dict())
            if not msg.tool_calls:
                return msg.content or ""
            for tc in msg.tool_calls:
                result = self.tools[tc.function.name]["fn"](**json.loads(tc.function.arguments))
                self.conversation.append({"role": "tool", "tool_call_id": tc.id, "content": str(result)})
        return "单Agent: 达到最大轮次"

# ---- 模式2: 多Agent协作 ----
class MultiAgentOrchestrator:
    """多Agent: 编排多个专业Agent协作"""
    def __init__(self, model="gpt-4o-mini"):
        self.model = model
        self.agents: Dict[str, dict] = {}

    def register_agent(self, name: str, system_prompt: str):
        self.agents[name] = {"system_prompt": system_prompt}

    def run_agent(self, agent_name: str, task: str) -> str:
        agent = self.agents[agent_name]
        resp = client.chat.completions.create(model=self.model, messages=[{"role": "system", "content": agent["system_prompt"]}, {"role": "user", "content": task}])
        return resp.choices[0].message.content or ""

    def run_pipeline(self, task: str, agent_order: List[str]) -> str:
        """串行执行多个Agent,前一个输出是后一个输入"""
        result = task
        for name in agent_order:
            result = self.run_agent(name, result)
        return result

    def run_parallel(self, task: str, agent_names: List[str]) -> Dict[str, str]:
        """并行执行多个Agent,收集各自输出"""
        return {name: self.run_agent(name, task) for name in agent_names}

# ---- 模式3: 分层Agent ----
class HierarchicalAgent:
    """分层Agent: 管理者分解任务,执行者完成子任务"""
    def __init__(self, model="gpt-4o-mini"):
        self.model = model
        self.workers: Dict[str, str] = {}

    def register_worker(self, name: str, system_prompt: str):
        self.workers[name] = system_prompt

    def decompose(self, task: str) -> List[dict]:
        worker_list = ", ".join(self.workers.keys())
        resp = client.chat.completions.create(model=self.model,
            messages=[{"role": "system", "content": f"你是任务管理者。可用执行者: {worker_list}。\n将任务分解为子任务,输出JSON。"},
                      {"role": "user", "content": task}],
            response_format={"type": "json_object"})
        try:
            data = json.loads(resp.choices[0].message.content)
            return data.get("subtasks", data.get("tasks", []))
        except json.JSONDecodeError:
            return []

    def execute_worker(self, worker_name: str, subtask: str) -> str:
        if worker_name not in self.workers:
            return f"未知执行者: {worker_name}"
        resp = client.chat.completions.create(model=self.model, messages=[{"role": "system", "content": self.workers[worker_name]}, {"role": "user", "content": subtask}])
        return resp.choices[0].message.content or ""

    def run(self, task: str) -> str:
        subtasks = self.decompose(task)
        results = []
        for st in subtasks:
            worker = st.get("worker", list(self.workers.keys())[0])
            result = self.execute_worker(worker, st.get("task", ""))
            results.append(f"[{worker}] {result}")
        return "\n\n".join(results)

✅ 验证通过:三种架构模式均有独立的类实现,包含完整的工具注册、任务分解和执行逻辑

🔧 SimpleAgent实现

SimpleAgent是最简可用的Agent实现,包含工具注册决策循环结果提取三个核心组件。

# SimpleAgent: 最简可用的Agent实现
from openai import OpenAI
import json
from typing import Any, Callable, Dict, List

client = OpenAI()

class SimpleAgent:
    """最简Agent实现 - register()/run()/自动工具调用循环"""

    def __init__(self, name: str = "SimpleAgent", model: str = "gpt-4o-mini"):
        self.name = name
        self.model = model
        self._tools: Dict[str, dict] = {}
        self._history: List[dict] = []
        self._step_count: int = 0

    def register(self, name: str, description: str, parameters: dict, handler: Callable[..., Any]) -> "SimpleAgent":
        """注册工具,支持链式调用"""
        self._tools[name] = {
            "schema": {"type": "function", "function": {"name": name, "description": description, "parameters": parameters}},
            "handler": handler,
        }
        return self

    def _get_tool_schemas(self) -> List[dict]:
        return [t["schema"] for t in self._tools.values()]

    def _execute_tool(self, tool_name: str, arguments: str) -> str:
        """安全执行工具,捕获异常"""
        if tool_name not in self._tools:
            return json.dumps({"error": f"未知工具: {tool_name}"})
        try:
            args = json.loads(arguments)
            result = self._tools[tool_name]["handler"](**args)
            return json.dumps(result, ensure_ascii=False) if not isinstance(result, str) else result
        except Exception as e:
            return json.dumps({"error": str(e)})

    def run(self, task: str, system_prompt: str = "你是一个智能助手,可以使用工具完成任务。",
            max_steps: int = 8, verbose: bool = False) -> str:
        """执行Agent任务"""
        self._history = [{"role": "system", "content": system_prompt}, {"role": "user", "content": task}]
        self._step_count = 0
        for step in range(max_steps):
            self._step_count = step + 1
            if verbose: print(f"\n--- Step {step + 1} ---")
            response = client.chat.completions.create(
                model=self.model, messages=self._history,
                tools=self._get_tool_schemas() if self._tools else None,
                tool_choice="auto",
            )
            msg = response.choices[0].message
            self._history.append(msg.to_dict())
            if not msg.tool_calls:
                return msg.content or ""
            for tc in msg.tool_calls:
                if verbose: print(f"调用工具: {tc.function.name}({tc.function.arguments})")
                result = self._execute_tool(tc.function.name, tc.function.arguments)
                if verbose: print(f"工具结果: {result[:200]}")
                self._history.append({"role": "tool", "tool_call_id": tc.id, "content": result})
        return "达到最大步数限制"

    @property
    def step_count(self) -> int:
        return self._step_count

# 使用示例
def search_web(query: str) -> str:
    mock = {"Python": "Python 3.12 released", "OpenAI": "GPT-4o available"}
    for key, val in mock.items():
        if key.lower() in query.lower(): return val
    return f"搜索 '{query}' 的结果: 暂无匹配"

agent = SimpleAgent(name="ResearchAgent")
agent.register("search_web", "搜索互联网获取最新信息",
    {"type": "object", "properties": {"query": {"type": "string"}}, "required": ["query"]},
    search_web)
# result = agent.run("搜索Python最新版本", verbose=True)

✅ 验证通过:SimpleAgent实现了完整的工具注册(链式调用)、决策循环、安全工具执行和verbose调试模式

📐 可观察Agent (ObservableAgent)

生产环境中的Agent必须具备可观测性——每次执行都要记录完整的决策过程、工具调用和耗时。这对调试、审计和优化至关重要。

# ObservableAgent: 带完整可观测性的Agent
from openai import OpenAI
import json
import time
from typing import Callable, Dict, List, Optional
from dataclasses import dataclass, field

client = OpenAI()

@dataclass
class ToolCallRecord:
    """单次工具调用记录"""
    tool_name: str
    arguments: str
    result: str
    duration_ms: float
    success: bool
    error: Optional[str] = None

@dataclass
class StepRecord:
    """单步决策记录"""
    step_number: int
    thought: str
    tool_calls: List[ToolCallRecord] = field(default_factory=list)
    duration_ms: float = 0.0
    total_tokens: int = 0

@dataclass
class AgentTrace:
    """完整执行轨迹"""
    task: str
    steps: List[StepRecord] = field(default_factory=list)
    total_duration_ms: float = 0.0
    total_tokens: int = 0
    final_answer: str = ""
    success: bool = False

    def summary(self) -> str:
        lines = [f"任务: {self.task}", f"总步数: {len(self.steps)}",
                 f"总耗时: {self.total_duration_ms:.0f}ms", f"总Token: {self.total_tokens}"]
        for step in self.steps:
            lines.append(f"  Step {step.step_number} ({step.duration_ms:.0f}ms): {step.thought[:80]}...")
            for tc in step.tool_calls:
                status = "OK" if tc.success else "FAIL"
                lines.append(f"    [{status}] {tc.tool_name} ({tc.duration_ms:.0f}ms)")
        lines.append(f"最终答案: {self.final_answer[:200]}")
        return "\n".join(lines)

class ObservableAgent:
    """可观察Agent - 每步记录thought/tool_calls/duration/tokens"""
    def __init__(self, name: str = "ObservableAgent", model: str = "gpt-4o-mini"):
        self.name = name
        self.model = model
        self._tools: Dict[str, dict] = {}
        self._hooks: Dict[str, List[Callable]] = {"before_step": [], "after_step": [], "after_tool": []}

    def register(self, name: str, description: str, parameters: dict, handler: Callable):
        self._tools[name] = {"schema": {"type": "function", "function": {"name": name, "description": description, "parameters": parameters}}, "handler": handler}

    def add_hook(self, event: str, callback: Callable):
        if event in self._hooks: self._hooks[event].append(callback)

    def _fire_hooks(self, event: str, **kwargs):
        for cb in self._hooks.get(event, []):
            try: cb(**kwargs)
            except: pass

    def run(self, task: str, max_steps: int = 8) -> AgentTrace:
        trace = AgentTrace(task=task)
        start_time = time.time()
        messages = [{"role": "system", "content": f"你是{self.name},智能可观察Agent。"}, {"role": "user", "content": task}]
        for step_num in range(1, max_steps + 1):
            self._fire_hooks("before_step", step=step_num, trace=trace)
            step_start = time.time()
            response = client.chat.completions.create(model=self.model, messages=messages, tools=[t["schema"] for t in self._tools.values()] if self._tools else None, tool_choice="auto")
            msg = response.choices[0].message
            step_tokens = response.usage.total_tokens if response.usage else 0
            step_record = StepRecord(step_number=step_num, thought=msg.content or "(工具调用)", total_tokens=step_tokens)
            trace.total_tokens += step_tokens
            messages.append(msg.to_dict())
            if not msg.tool_calls:
                step_record.duration_ms = (time.time() - step_start) * 1000
                trace.steps.append(step_record)
                trace.final_answer = msg.content or ""
                trace.success = True
                break
            for tc in msg.tool_calls:
                tool_start = time.time()
                try:
                    args = json.loads(tc.function.arguments)
                    result = self._tools[tc.function.name]["handler"](**args) if tc.function.name in self._tools else "未知工具"
                    record = ToolCallRecord(tool_name=tc.function.name, arguments=tc.function.arguments, result=str(result)[:500], duration_ms=(time.time()-tool_start)*1000, success=True)
                except Exception as e:
                    record = ToolCallRecord(tool_name=tc.function.name, arguments=tc.function.arguments, result="", duration_ms=(time.time()-tool_start)*1000, success=False, error=str(e))
                step_record.tool_calls.append(record)
                self._fire_hooks("after_tool", record=record, step=step_num)
                messages.append({"role": "tool", "tool_call_id": tc.id, "content": record.result if record.success else f"错误: {record.error}"})
            step_record.duration_ms = (time.time() - step_start) * 1000
            trace.steps.append(step_record)
        trace.total_duration_ms = (time.time() - start_time) * 1000
        if not trace.success: trace.final_answer = "任务未完成"
        return trace

# 使用: 添加日志钩子
agent = ObservableAgent(name="DebugAgent")
agent.add_hook("before_step", lambda step, **kw: print(f"Step {step}"))
agent.add_hook("after_tool", lambda record, **kw: print(f"  [{'OK' if record.success else 'FAIL'}] {record.tool_name}"))
# trace = agent.run("分析某公司股票走势")
# print(trace.summary())

✅ 验证通过:ObservableAgent实现了完整的数据类记录(StepRecord/ToolCallRecord/AgentTrace)、事件钩子系统和执行轨迹摘要

📊 技术对比与选型

架构模式优势劣势适用场景
单Agent实现简单,延迟低工具过多时决策困难简单任务,工具≤10
多Agent协作专业分工,可并行通信开销,协调复杂多领域复合任务
分层Agent任务分解清晰管理Agent是瓶颈大型复杂项目

💡 最佳实践

🏗️ 架构设计

┌──────────────────────────────────────────┐
│          Agent架构全景
├──────────────────────────────────────────┤
│  用户层: 任务输入 / 结果输出
├──────────────────────────────────────────┤
│  Agent层: 决策循环 (Observe→Think→Act)
│  ├── SimpleAgent: 最简循环
│  ├── MultiAgent: 多Agent协作编排
│  └── HierarchicalAgent: 管理者+执行者
├──────────────────────────────────────────┤
│  工具层: 搜索/计算/API/代码执行
├──────────────────────────────────────────┤
│  可观测层: Trace / Hook / Metrics
├──────────────────────────────────────────┤
│  基础层: LLM (GPT-4o-mini / GPT-4o)
└──────────────────────────────────────────┘

⚠️ 常见陷阱

⚡ 性能考量

指标目标值优化手段
首步延迟(TTFT)< 1s模型选择/提示词精简/流式
工具执行< 500ms超时控制/异步执行/缓存
总完成时间< 30s限制步数/并行工具调用
Token消耗可预测压缩历史/总结中间结果

🔗 与其他课程的关系

构建Agent架构综合系统

# Agent架构综合练习
# 挑战: 构建一个支持架构切换的通用Agent框架
from openai import OpenAI
import json
from typing import Callable, Dict, List
from dataclasses import dataclass

client = OpenAI()

@dataclass
class AgentConfig:
    mode: str = "single"           # single / multi / hierarchical
    model: str = "gpt-4o-mini"
    max_steps: int = 8
    agent_roles: Dict[str, str] = None
    manager_prompt: str = "你是任务管理者。"
    worker_roles: Dict[str, str] = None

class UniversalAgent:
    """通用Agent框架: 支持架构切换"""
    def __init__(self, config: AgentConfig):
        self.config = config
        self._tools: Dict[str, dict] = {}

    def register_tool(self, name, desc, params, fn):
        self._tools[name] = {"desc": desc, "params": params, "fn": fn}

    def _tool_schemas(self):
        return [{"type": "function", "function": {"name": n, "description": t["desc"], "parameters": t["params"]}} for n, t in self._tools.items()]

    def _safe_call(self, name, args_json):
        if name not in self._tools: return f"未知工具: {name}"
        try:
            args = json.loads(args_json)
            return str(self._tools[name]["fn"](**args))
        except Exception as e:
            return f"错误: {e}"

    def run(self, task: str) -> str:
        if self.config.mode == "single": return self._run_single(task)
        elif self.config.mode == "multi": return self._run_multi(task)
        elif self.config.mode == "hierarchical": return self._run_hier(task)
        raise ValueError(f"未知模式: {self.config.mode}")

    def _run_single(self, task):
        msgs = [{"role": "system", "content": "你是智能Agent。"}, {"role": "user", "content": task}]
        for _ in range(self.config.max_steps):
            resp = client.chat.completions.create(model=self.config.model, messages=msgs, tools=self._tool_schemas() or None, tool_choice="auto")
            msg = resp.choices[0].message
            msgs.append(msg.to_dict())
            if not msg.tool_calls: return msg.content or ""
            for tc in msg.tool_calls:
                msgs.append({"role": "tool", "tool_call_id": tc.id, "content": self._safe_call(tc.function.name, tc.function.arguments)})
        return "达到最大步数"

    def _run_multi(self, task):
        roles = self.config.agent_roles or {"agent1": "你是助手。"}
        result = task
        for name, prompt in roles.items():
            resp = client.chat.completions.create(model=self.config.model, messages=[{"role": "system", "content": prompt}, {"role": "user", "content": result}])
            result = resp.choices[0].message.content or ""
        return result

    def _run_hier(self, task):
        workers = self.config.worker_roles or {"worker": "你是执行者。"}
        resp = client.chat.completions.create(model=self.config.model, messages=[{"role": "system", "content": self.config.manager_prompt}, {"role": "user", "content": f"分解任务: {task}"}], response_format={"type": "json_object"})
        try:
            subtasks = json.loads(resp.choices[0].message.content)
            results = []
            for st in subtasks.get("subtasks", subtasks.get("tasks", [])):
                w = st.get("worker", list(workers.keys())[0])
                r = client.chat.completions.create(model=self.config.model, messages=[{"role": "system", "content": workers.get(w, "你是执行者。")}, {"role": "user", "content": st.get("task", "")}])
                results.append(r.choices[0].message.content or "")
            return "\n".join(results)
        except json.JSONDecodeError:
            return "管理者分解失败"

config = AgentConfig(mode="single")
agent = UniversalAgent(config)
# result = agent.run("帮我分析AI行业趋势")

进阶挑战

尝试在UniversalAgent中添加自动模式选择——让Agent根据任务复杂度自动决定使用单Agent、多Agent还是分层架构。

🏅🏅 Agent架构实践者