AI Agent运行时架构:从Function Calling到多层级ReAct的工程实践

2024年,AI Agent从概念验证走向生产部署。Claude、GPT-4、Gemini等大模型通过Function Calling暴露工具调用能力,但如何围绕这一能力构建可靠的运行时架构,仍是工程实践中的核心挑战。本文深入解析AI Agent的运行时设计,涵盖从单循环ReAct到多Agent协作的完整工程化路径。

一、Agent核心循环:不只是"思考-行动"

最简Agent架构是一个循环:接收目标→LLM推理→执行工具→观察结果→再次推理。这个模式看似简单,但在生产环境中会遇到三大工程挑战:状态爆炸、不可恢复错误和上下文窗口泄漏。

class MinimalAgent:
    def __init__(self, llm, tools, system_prompt):
        self.llm = llm
        self.tools = {t.name: t for t in tools}
        self.system_prompt = system_prompt

    def run(self, user_query: str, max_steps: int = 10):
        messages = [
            {"role": "system", "content": self.system_prompt},
            {"role": "user", "content": user_query}
        ]

        for step in range(max_steps):
            response = self.llm.chat(
                messages=messages,
                tools=self._tool_definitions()
            )

            # 终止条件:模型未请求工具调用
            if not response.tool_calls:
                return response.content

            messages.append(response.to_message())

            for call in response.tool_calls:
                result = self._execute_tool(call)
                messages.append({
                    "role": "tool",
                    "tool_call_id": call.id,
                    "content": str(result)
                })

        return "达到最大步数限制"

这段代码能工作,但距离生产级部署差距甚远。真实场景需要处理:工具执行超时、LLM幻觉导致的无效工具参数、上下文窗口溢出、中间状态持久化等问题。

二、工具注册体系:从Decorator到类型安全

工具的注册方式决定了Agent的可维护性。一个生产级工具系统需要满足三个条件:类型安全、权限边界和调用审计。

from dataclasses import dataclass
from typing import Callable, Any, get_type_hints
import inspect
import json

@dataclass
class ToolSpec:
    name: str
    description: str
    parameters_schema: dict
    handler: Callable
    permission_level: str = "user"  # user, elevated, admin

    def execute(self, **kwargs) -> Any:
        # 类型校验
        hints = get_type_hints(self.handler)
        for key, value in kwargs.items():
            if key in hints and not isinstance(value, hints[key]):
                raise TypeError(
                    f"参数 {key} 期望类型 {hints[key].__name__}, "
                    f"实际得到 {type(value).__name__}"
                )
        return self.handler(**kwargs)

class ToolRegistry:
    def __init__(self):
        self._tools: dict[str, ToolSpec] = {}
        self._call_log: list[dict] = []

    def register(self, name: str, description: str, 
                 permission: str = "user"):
        def decorator(func: Callable):
            sig = inspect.signature(func)
            schema = self._build_schema(sig, func)
            self._tools[name] = ToolSpec(
                name=name,
                description=description,
                parameters_schema=schema,
                handler=func,
                permission_level=permission
            )
            return func
        return decorator

    def _build_schema(self, sig, func) -> dict:
        """从函数签名自动生成JSON Schema"""
        hints = get_type_hints(func)
        properties = {}
        required = []

        for param_name, param in sig.parameters.items():
            ptype = hints.get(param_name, str)
            prop = {"type": self._map_type(ptype)}
            if param_name in hints:
                prop["description"] = f"参数 {param_name}"
            if param.default is inspect.Parameter.empty:
                required.append(param_name)
            properties[param_name] = prop

        return {
            "type": "object",
            "properties": properties,
            "required": required
        }

    def execute(self, name: str, **kwargs) -> Any:
        if name not in self._tools:
            raise ValueError(f"未知工具: {name}")

        spec = self._tools[name]
        self._call_log.append({
            "tool": name,
            "params": kwargs,
            "permission": spec.permission_level
        })

        return spec.execute(**kwargs)

    @staticmethod
    def _map_type(ptype) -> str:
        mapping = {
            str: "string", int: "integer", 
            float: "number", bool: "boolean",
            list: "array", dict: "object"
        }
        return mapping.get(ptype, "string")

# 使用示例
registry = ToolRegistry()

@registry.register("github_search", "搜索GitHub仓库", permission="user")
def github_search(query: str, language: str = "", max_results: int = 5):
    """调用GitHub API搜索仓库"""
    # API调用实现...
    return {"repositories": []}

@registry.register("deploy_service", "部署服务到Kubernetes", permission="elevated")
def deploy_service(service_name: str, image: str, replicas: int = 3):
    """部署容器服务"""
    # K8s API调用实现...
    return {"status": "deployed"}

这套注册机制的核心优势在于:类型注解自动驱动Schema生成,权限级别在注册时声明而非执行时判断,调用日志自动记录使每次Agent决策都可追溯到具体参数。

三、ReAct的进阶工程化

Anthropic提出的ReAct模式(Reason + Act)是Agent推理的基石。但在实际部署中,纯ReAct会遇到"推理漂移"问题——Agent在长链路推理后忘记原始目标。

3.1 层级化ReAct架构

解决方案是引入计划层,将Agent分为三个抽象层级:

class HierarchicalReActAgent:
    """三层Agent:规划器 → 分解器 → 执行器"""

    def __init__(self):
        self.planner = PlannerLLM()     # 高层:制定子目标
        self.decomposer = TaskLLM()     # 中层:分解为步骤
        self.executor = ExecutorLLM()   # 低层:执行原子操作
        self.global_state = StateStore()

    def run(self, goal: str) -> dict:
        # 第一层:规划
        plan = self.planner.create_plan(goal, context=self.global_state)
        self.global_state.set("current_plan", plan)
        results = []

        for subgoal in plan.subgoals:
            # 第二层:分解
            steps = self.decomposer.decompose(subgoal)

            for step in steps:
                # 第三层:执行 + 自我修正
                result = self._execute_with_backoff(step)

                if result.status == "failed":
                    # 回退到分解层重新规划
                    alternative = self.decomposer.handle_failure(
                        step, result.error, context=steps
                    )
                    result = self._execute_with_backoff(alternative)

                results.append(result)
                self.global_state.append("execution_trace", result)

        return self.planner.synthesize(goal, results)

    def _execute_with_backoff(self, step, max_retries=3):
        """带回退的执行器"""
        for attempt in range(max_retries):
            try:
                return self.executor.execute(step)
            except RateLimitError:
                wait = 2 ** attempt * 10
                time.sleep(wait)
            except ToolExecutionError as e:
                return ExecutionResult(status="failed", error=str(e))

        return ExecutionResult(status="failed", error="超过最大重试次数")

3.2 防止推理漂移:目标锚定机制

class GoalAnchor:
    """在每轮推理中注入原始目标,防止推理漂移"""

    def __init__(self, original_goal: str):
        self.goal = original_goal
        self.drift_threshold = 0.7

    def wrap_messages(self, messages: list, context_window: int) -> list:
        """在消息列表末尾加入目标锚定"""
        anchor = (
            f"[目标锚定] 原始任务: {self.goal}\n"
            f"[进度检查] 当前执行是否仍服务于上述目标?"
            f"如果偏离,请回到正确轨道。"
        )

        # 在最新用户消息后插入锚定
        return messages + [{
            "role": "system",
            "content": anchor
        }]

    def check_drift(self, current_action: str) -> bool:
        """检测当前动作是否偏离原始目标"""
        # 使用小模型或关键词匹配检测偏离
        drift_keywords = ["偏离", "无关", "不确定是否"]
        return any(kw in current_action for kw in drift_keywords)

四、上下文窗口管理:Agent的"工作记忆"

当Agent执行数十步操作后,上下文窗口成为最稀缺资源。一个典型的5步工具调用链产生约15条消息(每步:assistant call + tool result + 推理追加),每条200-500 tokens,快速逼近窗口上限。

4.1 滑动窗口 + 摘要压缩

class ContextManager:
    """管理Agent的上下文窗口"""

    def __init__(self, max_tokens: int = 100000, 
                 llm_for_summary=None):
        self.max_tokens = max_tokens
        self.summarizer = llm_for_summary
        self.messages = []
        self.summaries = []  # 历史摘要栈

    def add(self, message: dict):
        self.messages.append(message)
        if self._total_tokens() > self.max_tokens * 0.8:
            self._compress()

    def _compress(self):
        """将早期消息压缩为摘要"""
        # 保留最近N条消息,其余压缩
        keep_recent = 6
        to_summarize = self.messages[:-keep_recent]

        summary_prompt = (
            f"将以下Agent交互历史压缩为结构化摘要:\n"
            f"- 已完成的动作(按时间顺序)\n"
            f"- 关键发现和中间结果\n"
            f"- 当前子目标进展\n"
            f"- 待完成的事项\n\n"
            f"{json.dumps(to_summarize, ensure_ascii=False, indent=2)}"
        )

        summary = self.summarizer.complete(summary_prompt)
        self.summaries.append(summary)
        self.messages = self.messages[-keep_recent:]

    def get_context(self) -> list:
        """组装最终上下文"""
        context = []
        if self.summaries:
            context.append({
                "role": "system",
                "content": f"[历史摘要]\n" + "\n---\n".join(self.summaries)
            })
        context.extend(self.messages)
        return context

    def _total_tokens(self) -> int:
        # 简化token计算,实际使用tiktoken
        return sum(len(m.get("content", "")) for m in self.messages) // 4

4.2 结构化记忆存储

除滑动窗口外,Agent还需要外部记忆来持久化关键信息:

class AgentMemory:
    """Agent的外部记忆系统"""

    def __init__(self):
        self.working_memory = {}      # 当前任务上下文
        self.episodic_memory = []      # 交互历史记录
        self.semantic_memory = {}      # 结构化知识库

    def remember(self, key: str, value: Any, 
                 memory_type: str = "working"):
        if memory_type == "working":
            self.working_memory[key] = value
        elif memory_type == "episodic":
            self.episodic_memory.append({
                "key": key, "value": value, "timestamp": time.time()
            })
        elif memory_type == "semantic":
            self.semantic_memory[key] = value

    def recall(self, key: str, memory_type: str = "working") -> Any:
        store = {
            "working": self.working_memory,
            "semantic": self.semantic_memory
        }.get(memory_type, {})
        return store.get(key)

    def format_for_context(self) -> str:
        """格式化为LLM可消费的上下文片段"""
        lines = ["[工作记忆]"]
        for k, v in self.working_memory.items():
            lines.append(f"  {k}: {v}")
        if self.semantic_memory:
            lines.append("[知识库]")
            for k, v in list(self.semantic_memory.items())[:5]:
                lines.append(f"  {k}: {v}")
        return "\n".join(lines)

五、多Agent协作:从编排到涌现

当任务复杂度超过单个Agent的处理能力时,需要引入多Agent协作。主流架构有三种模式:

5.1 Orchestrator-Worker模式

class OrchestratorAgent:
    """编排型Agent:分解任务并分配给Worker"""

    def __init__(self, workers: dict[str, BaseAgent]):
        self.workers = workers  # {"code_reviewer": CodeReviewer(), ...}

    def run(self, task: ComplexTask):
        subtasks = self.decompose(task)
        results = {}

        # 并行执行无依赖的子任务
        with ThreadPoolExecutor(max_workers=4) as pool:
            futures = {}
            for subtask in subtasks:
                if self._can_run(subtask, results):
                    worker = self.workers[subtask.assigned_to]
                    future = pool.submit(worker.run, subtask)
                    futures[future] = subtask

            for future in as_completed(futures):
                subtask = futures[future]
                results[subtask.id] = future.result()

        return self.merge_results(results)

5.2 辩论模式(Debate Pattern)

适用于高风险决策场景,多个Agent从不同角度分析后达成共识:

class DebateOrchestrator:
    def run(self, question: str, rounds: int = 3):
        agents = [
            RiskAverseAgent(),    # 偏保守角度
            InnovationAgent(),    # 偏创新角度  
            PracticalAgent()      # 偏实用角度
        ]

        positions = {a.name: a.initial_position(question) 
                    for a in agents}

        for r in range(rounds):
            for agent in agents:
                others = {k: v for k, v in positions.items() 
                         if k != agent.name}
                positions[agent.name] = agent.respond_to_others(
                    question, others, positions[agent.name]
                )

        return self.synthesize_consensus(positions)

六、生产级Agent的可靠性工程

将Agent部署到生产环境,需要面对与传统软件不同的可靠性挑战:LLM的非确定性输出导致相同输入可能产生不同行为路径。

6.1 幂等工具设计

def idempotent_tool(func):
    """确保工具幂等性的装饰器"""
    @wraps(func)
    def wrapper(*args, **kwargs):
        call_id = kwargs.get("__call_id")
        if call_id:
            cached = check_result_cache(call_id)
            if cached is not None:
                return cached
        result = func(*args, **kwargs)
        if call_id:
            store_result_cache(call_id, result, ttl=3600)
        return result
    return wrapper

6.2 可观测性体系

from contextlib import contextmanager
import uuid

class AgentTracer:
    """Agent运行时的全链路追踪"""

    @contextmanager
    def trace_run(self, agent_name: str, goal: str):
        run_id = str(uuid.uuid4())[:8]
        self._emit({
            "event": "agent_run_start",
            "run_id": run_id,
            "agent": agent_name,
            "goal_preview": goal[:100]
        })
        start = time.time()

        try:
            yield AgentRunContext(run_id=run_id, tracer=self)
            self._emit({
                "event": "agent_run_complete",
                "run_id": run_id,
                "duration_ms": (time.time() - start) * 1000
            })
        except Exception as e:
            self._emit({
                "event": "agent_run_failed",
                "run_id": run_id,
                "error": str(e),
                "duration_ms": (time.time() - start) * 1000
            })
            raise

    def trace_tool_call(self, tool_name: str, params: dict, 
                       result: Any, duration_ms: float):
        self._emit({
            "event": "tool_call",
            "tool": tool_name,
            "param_keys": list(params.keys()),
            "result_type": type(result).__name__,
            "duration_ms": duration_ms
        })

七、前沿方向:从Reactive到Proactive

2024年Agent架构正从"被动响应"向"主动感知"演进:

  • Long-running Agent:突破单次会话限制,Agent可以跨小时/天持续运行,监控外部事件并主动触发工作流
  • Sub-Agent Spawning:父Agent根据任务需要动态创建子Agent实例,形成Agent树结构,结束后自动回收资源
  • Reflexive Self-Improvement:Agent将执行过程中遇到的错误和纠正策略写入持久记忆,在后续运行中自动避免重复犯错
  • Multi-Modal Perception:Agent从纯文本输入扩展到视觉、音频、屏幕流等多模态感知,实现"看屏操作"

总结

AI Agent运行时的工程复杂度被严重低估。一个生产级Agent系统需要在工具注册、上下文管理、记忆系统、可靠性保障四个维度建立完整的工程化方案。核心设计原则是:将LLM视为一个强大但不可靠的核心,用工程手段兜底不确定性。

从单循环ReAct到层级化架构,从单Agent到多Agent协作,每一步演化都围绕同一个命题:如何让非确定性的LLM推理在确定性工程框架内可靠运行。这不仅是技术问题,更是工程哲学的体现。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部