如果你正在探索如何将AI大模型从“玩具”变成真正的“生产力工具”,那么这篇文章就是为你准备的。我们经常看到各种炫酷的AI演示,但一到企业级应用,问题就来了:如何让AI稳定、安全、可控地处理复杂业务流程?如何让多个AI智能体(Agent)像一支训练有素的团队一样协同工作,而不是各自为战?如何确保AI在犯错时能被及时纠正,并且能持续学习进化?

这正是“Harness Engineering”理念和“多Agent协同”架构要解决的核心问题。它不是一个简单的API调用,而是一套完整的工程化体系,旨在将AI大模型无缝、可靠地集成到企业生产环境中。本文将带你深入一个实战项目,拆解如何构建一个包含 多Agent协同、沙箱(SandBox)安全隔离、自我进化技能(Skill)库以及人工介入 的企业级AI应用。我们将从概念落地到代码,让你不仅理解“为什么”,更能亲手“怎么做”。

1. 从概念到实战:Harness Engineering 究竟是什么?

“Harness Engineering”这个词近期在技术社区热度很高,但很多讨论停留在概念层面。简单来说, Harness Engineering 的核心思想是“驾驭工程化” 。它强调将AI大模型(尤其是Agent)视为一个需要被严格管理、引导和约束的“新员工”,而不是一个可以随意调用的黑盒函数。

传统的AI集成方式是“请求-响应”模式:发送一个Prompt,等待一个答案。这种方式在简单任务上有效,但在复杂、多步骤、有状态的企业流程中,它缺乏 可控性、可观测性和可复用性 。Harness Engineering 则倡导一种“Agent-First”的架构,其关键支柱包括:

  1. 多Agent协同(Multi-Agent) :不同Agent扮演不同角色(如规划者、执行者、审核者),通过明确的通信协议和协作流程共同完成任务。这模拟了人类团队的分工协作。
  2. 沙箱环境(SandBox) :为每个Agent的执行提供一个隔离的、资源受限的运行环境。这是 安全性的基石 ,防止AI操作对主系统造成破坏,也便于监控和资源回收。
  3. 技能库与自我进化(Skill) :将可复用的能力封装成“技能”(Skill)。技能可以像乐高积木一样被组合,并且能够根据执行结果(成功/失败)和人工反馈进行优化和迭代,实现“越用越聪明”。
  4. 人工介入(Human-in-the-Loop) :在关键决策点、异常情况或低置信度时,流程自动暂停并请求人工审核或指导。这确保了最终输出的可靠性,并为技能进化提供了高质量的训练数据。

本次实战项目,我们将模拟一个“码士集团”的智能开发助手场景,构建一个能自动处理GitHub Issue,并生成代码修复方案的多Agent系统。

2. 核心组件与架构设计

在开始写代码之前,我们必须理清系统的核心组件和它们之间的关系。一个典型的企业级多Agent系统架构如下图所示(概念图):

[用户/系统触发]
        |
        v
[任务调度与编排中心 (Orchestrator)]
        |
        |-- 任务解析 --> [规划Agent (Planner)]
        |-- 技能匹配 --> [技能库 (Skill Library)]
        |-- 执行调度 --> [执行Agent (Executor)] --(在沙箱中)--> [执行技能]
        |-- 结果审核 --> [审核Agent (Reviewer)]
        |
        v
[结果输出] <---> [人工介入平台 (HITL)]

核心组件详解:

  • Orchestrator (编排器) :系统的大脑。负责接收初始任务,将其分解为子任务,调度合适的Agent和Skill,并管理整个工作流的状态。它通常是一个常驻服务。
  • Agent (智能体) :具有特定角色和能力的执行单元。每个Agent有明确的目标和通信接口。
    • Planner (规划Agent) :分析任务,制定执行计划(Plan)。例如,将“修复Issue #123”分解为“理解Issue”、“定位代码”、“分析原因”、“生成补丁”、“运行测试”。
    • Executor (执行Agent) :负责在沙箱中安全地执行具体的Skill,如运行Shell命令、调用API、执行代码片段。
    • Reviewer (审核Agent) :对Executor产生的结果(如生成的代码)进行初步审核,检查语法、潜在风险、是否符合要求。
  • Skill (技能) :原子化的能力封装。一个Skill通常对应一个具体的操作,例如:
    • git_clone_skill : 克隆代码仓库。
    • analyze_issue_skill : 调用大模型分析Issue描述。
    • run_unit_test_skill : 在沙箱中运行单元测试。
    • generate_patch_skill : 根据问题描述和代码上下文,生成代码补丁。
  • SandBox (沙箱) :一个隔离的、一次性的执行环境。每个需要执行代码或命令的Skill都必须在独立的沙箱中运行。沙箱在任务完成后自动销毁,确保不会留下副作用或安全漏洞。常见的实现方式包括Docker容器、轻量级虚拟机(如gVisor)或基于命名空间隔离的技术。
  • HITL Platform (人工介入平台) :当Agent置信度低、任务超出预设范围或审核Agent标记为“需要人工确认”时,任务状态会被推送到此平台,等待人工处理。人工的决策(通过/驳回/修改)会作为反馈回流到系统,用于优化Skill和Agent的决策。

3. 环境准备与项目初始化

我们将使用Python作为主要开发语言,并利用一些成熟的开源框架来加速开发。请注意,以下版本为示例,请根据你的实际环境调整。

基础环境:

  • Python 3.9+
  • Docker & Docker Compose (用于沙箱环境)
  • Git

核心依赖库: 我们将使用 langchain 框架来构建Agent,因为它提供了丰富的工具链和Agent抽象。同时,我们需要一个沙箱管理库,这里我们使用 python-on-whales 来方便地操作Docker。

# 创建项目目录并初始化虚拟环境
mkdir harness-engineering-demo && cd harness-engineering-demo
python -m venv venv
source venv/bin/activate  # Linux/Mac
# venv\Scripts\activate  # Windows

# 安装核心依赖
pip install langchain==0.1.0 openai==1.3.0 python-on-whales==0.64.0
pip install fastapi==0.104.1 uvicorn==0.24.0 pydantic==2.5.0  # 用于构建简单的API
pip install python-dotenv==1.0.0  # 管理环境变量

项目结构:

harness-engineering-demo/
├── .env                    # 环境变量(API Keys等)
├── docker-compose.yml      # 沙箱服务定义
├── main.py                 # 主入口,Orchestrator
├── agents/
│   ├── __init__.py
│   ├── base_agent.py      # Agent基类
│   ├── planner_agent.py
│   ├── executor_agent.py
│   └── reviewer_agent.py
├── skills/
│   ├── __init__.py
│   ├── skill_base.py      # Skill基类
│   ├── git_skills.py      # Git相关技能
│   ├── code_skills.py     # 代码分析/生成技能
│   └── skill_registry.py  # 技能注册中心
├── sandbox/
│   ├── __init__.py
│   ├── manager.py         # 沙箱生命周期管理
│   └── Dockerfile         # 基础沙箱镜像
├── models/
│   └── workflow_models.py # Pydantic数据模型
└── storage/
    └── skill_memory.json  # 技能执行历史存储(用于进化)

4. 核心模块实现:从沙箱到技能

4.1 构建安全的沙箱管理器

沙箱是安全的底线。我们使用Docker来创建一次性容器。

# file: sandbox/manager.py
from python_on_whales import DockerClient
from typing import Optional, Dict, Any
import tempfile
import os

class SandboxManager:
    def __init__(self):
        self.docker = DockerClient()
        # 确保基础镜像存在
        self._ensure_base_image()

    def _ensure_base_image(self):
        """构建或拉取一个轻量级的Python沙箱基础镜像"""
        try:
            self.docker.image.inspect("harness-sandbox:latest")
        except:
            # 如果不存在,则构建。这里使用一个简单的Python镜像。
            self.docker.build(
                path=os.path.join(os.path.dirname(__file__), "."),
                tags="harness-sandbox:latest",
            )

    def create_sandbox(self, env_vars: Optional[Dict[str, str]] = None) -> str:
        """
        创建一个新的沙箱容器并返回其ID。
        Args:
            env_vars: 需要注入到沙箱的环境变量。
        Returns:
            容器ID。
        """
        container = self.docker.container.create(
            image="harness-sandbox:latest",
            command=["tail", "-f", "/dev/null"], # 保持容器运行
            detach=True,
            environment=env_vars or {},
            network_disabled=True, # 关键!禁用网络,除非任务需要
            read_only=True, # 根文件系统只读
            # 可以进一步限制CPU、内存
            # mem_limit="512m",
            # cpus="0.5",
        )
        container.start()
        return container.id

    def execute_in_sandbox(self, container_id: str, command: list, workdir: str = "/workspace") -> Dict[str, Any]:
        """
        在指定沙箱容器内执行命令。
        Args:
            container_id: 容器ID。
            command: 要执行的命令列表,如 ["python", "script.py"]。
            workdir: 工作目录。
        Returns:
            包含 stdout, stderr, return_code 的字典。
        """
        exec_result = self.docker.container.execute(
            container_id,
            command=command,
            workdir=workdir,
        )
        # 简化处理,实际应更细致地解析结果
        return {
            "stdout": exec_result[0] if exec_result else "",
            "stderr": exec_result[1] if len(exec_result) > 1 else "",
            "return_code": 0 if exec_result and len(exec_result[1]) == 0 else 1, # 简化
        }

    def destroy_sandbox(self, container_id: str):
        """销毁沙箱容器,清理资源。"""
        try:
            container = self.docker.container.inspect(container_id)
            container.stop(timeout=2)
            container.remove(force=True)
        except Exception as e:
            print(f"销毁沙箱 {container_id} 时出错: {e}")

# file: sandbox/Dockerfile
FROM python:3.9-slim
WORKDIR /workspace
# 安装最小化依赖,例如git和一些常用工具
RUN apt-get update && apt-get install -y --no-install-recommends \
    git \
    curl \
    && rm -rf /var/lib/apt/lists/*
# 创建一个非root用户以增强安全
RUN useradd -m -u 1000 sandboxuser
USER sandboxuser

4.2 定义可进化的技能基类

技能(Skill)是能力的载体。一个良好的Skill设计应该包含执行、自描述和反馈学习的能力。

# file: skills/skill_base.py
from abc import ABC, abstractmethod
from typing import Dict, Any, Optional
from pydantic import BaseModel, Field
import json

class SkillInput(BaseModel):
    """技能的输入参数模型"""
    pass

class SkillOutput(BaseModel):
    """技能的输出结果模型"""
    success: bool = Field(..., description="技能执行是否成功")
    result: Any = Field(None, description="执行结果数据")
    message: str = Field("", description="执行信息或错误信息")
    confidence: float = Field(1.0, description="技能对本次执行的置信度 (0.0-1.0)")

class BaseSkill(ABC):
    """所有技能的基类"""
    name: str = "base_skill"
    description: str = "一个基础的技能"
    version: str = "1.0.0"

    def __init__(self, skill_memory_path: str = "storage/skill_memory.json"):
        self.memory_path = skill_memory_path
        self.load_memory()

    def load_memory(self):
        """加载该技能的历史执行记录,用于自我进化"""
        try:
            with open(self.memory_path, 'r') as f:
                all_memory = json.load(f)
                self.memory = all_memory.get(self.name, [])
        except FileNotFoundError:
            self.memory = []

    def save_memory(self, execution_record: Dict[str, Any]):
        """保存本次执行记录"""
        self.memory.append(execution_record)
        # 只保留最近N条记录,防止无限膨胀
        if len(self.memory) > 100:
            self.memory = self.memory[-100:]
        try:
            with open(self.memory_path, 'r') as f:
                all_memory = json.load(f)
        except FileNotFoundError:
            all_memory = {}
        all_memory[self.name] = self.memory
        with open(self.memory_path, 'w') as f:
            json.dump(all_memory, f, indent=2)

    @abstractmethod
    async def execute(self, input_data: SkillInput, **kwargs) -> SkillOutput:
        """
        执行技能的核心逻辑。
        子类必须实现此方法。
        """
        pass

    def learn_from_feedback(self, execution_id: str, human_feedback: str, corrected_result: Any):
        """
        根据人工反馈进行学习。
        这是一个简单的示例,实际可能涉及微调提示词、更新内部参数等。
        """
        record = next((r for r in self.memory if r.get('execution_id') == execution_id), None)
        if record:
            record['human_feedback'] = human_feedback
            record['corrected_result'] = corrected_result
            # 标记此记录可用于后续的“技能进化”训练
            record['needs_retraining'] = True
            self.save_memory(record)
            print(f"技能 {self.name} 已记录来自人工的反馈。")

4.3 实现一个具体的Git克隆技能

让我们实现一个会在沙箱中运行的、相对复杂的技能。

# file: skills/git_skills.py
from .skill_base import BaseSkill, SkillInput, SkillOutput
from sandbox.manager import SandboxManager
from pydantic import Field
import uuid

class GitCloneInput(SkillInput):
    repo_url: str = Field(..., description="Git仓库的URL")
    branch: str = Field("main", description="要克隆的分支")
    target_dir: str = Field("/workspace/repo", description="沙箱内的目标目录")

class GitCloneSkill(BaseSkill):
    name = "git_clone_skill"
    description = "在沙箱环境中克隆一个Git仓库到指定目录。"
    version = "1.0.0"

    def __init__(self):
        super().__init__()
        self.sandbox_manager = SandboxManager()

    async def execute(self, input_data: GitCloneInput, **kwargs) -> SkillOutput:
        execution_id = str(uuid.uuid4())
        sandbox_id = None
        try:
            # 1. 创建沙箱
            sandbox_id = self.sandbox_manager.create_sandbox()
            print(f"[{self.name}] 创建沙箱: {sandbox_id}")

            # 2. 在沙箱中执行git clone命令
            # 注意:这里需要处理认证,为简化示例,我们假设是公开仓库。
            clone_cmd = ["git", "clone", "-b", input_data.branch, input_data.repo_url, input_data.target_dir]
            result = self.sandbox_manager.execute_in_sandbox(sandbox_id, clone_cmd, workdir="/workspace")

            # 3. 解析结果
            if result["return_code"] == 0:
                output = SkillOutput(
                    success=True,
                    result={"sandbox_id": sandbox_id, "repo_path": input_data.target_dir},
                    message=f"成功克隆仓库 {input_data.repo_url} 到沙箱 {sandbox_id}",
                    confidence=0.95
                )
            else:
                output = SkillOutput(
                    success=False,
                    result={"sandbox_id": sandbox_id, "error": result["stderr"]},
                    message=f"克隆仓库失败: {result['stderr']}",
                    confidence=0.1
                )

            # 4. 保存执行记录
            record = {
                "execution_id": execution_id,
                "skill": self.name,
                "input": input_data.dict(),
                "output": output.dict(),
                "sandbox_id": sandbox_id,
                "timestamp": ... # 添加时间戳
            }
            self.save_memory(record)

            return output

        except Exception as e:
            # 5. 异常处理
            output = SkillOutput(
                success=False,
                result={"error": str(e)},
                message=f"技能执行过程中发生异常: {e}",
                confidence=0.0
            )
            return output
        finally:
            # 6. 重要!如果技能失败,且沙箱不再需要,可以选择立即销毁。
            # 更优的策略是由Orchestrator根据工作流决定何时销毁。
            # if sandbox_id and not output.success:
            #     self.sandbox_manager.destroy_sandbox(sandbox_id)
            pass

5. 构建多Agent协同工作流

有了技能和沙箱,现在我们需要让Agent们动起来。我们使用LangChain来快速构建具有推理能力的Agent。

5.1 规划Agent (Planner) 实现

规划Agent负责理解任务并制定计划。它通常由一个强大的大模型(如GPT-4)驱动。

# file: agents/planner_agent.py
from langchain.agents import AgentExecutor, create_openai_tools_agent
from langchain_openai import ChatOpenAI
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain.tools import Tool
from skills.skill_registry import SkillRegistry
import json

class PlannerAgent:
    def __init__(self, openai_api_key: str, model_name: str = "gpt-4"):
        self.llm = ChatOpenAI(model=model_name, temperature=0, openai_api_key=openai_api_key)
        self.skill_registry = SkillRegistry()
        self._init_agent()

    def _init_agent(self):
        """初始化LangChain Agent"""
        # 1. 获取所有可用技能,并将其转化为LangChain Tool
        available_skills = self.skill_registry.list_skills()
        tools = []
        for skill_info in available_skills:
            # 这里简化处理,实际需要将Skill封装成Tool的_func
            tool = Tool(
                name=skill_info["name"],
                func=lambda x: f"Skill {skill_info['name']} will be invoked by Executor.", # 占位函数
                description=skill_info["description"],
            )
            tools.append(tool)

        # 2. 构建提示词模板,指导Planner如何分解任务
        prompt = ChatPromptTemplate.from_messages([
            ("system", """你是一个高级任务规划专家。你的目标是将一个复杂的用户请求分解成一个清晰的、可执行的步骤计划。
            可用的技能工具有:{tools}。
            请输出一个JSON格式的计划,包含以下字段:
            - `goal`: 总体目标。
            - `steps`: 步骤列表,每个步骤应包含:
                * `id`: 步骤ID。
                * `description`: 步骤描述。
                * `required_skill`: 执行此步骤所需的技能名称。
                * `depends_on`: 此步骤所依赖的步骤ID列表(可选)。
                * `input_parameters`: 此步骤需要的输入参数(从上下文或上一步输出中获取)。
            请确保计划逻辑严谨,步骤间依赖关系明确。"""),
            MessagesPlaceholder(variable_name="chat_history"),
            ("human", "{input}"),
        ])

        # 3. 创建Agent
        agent = create_openai_tools_agent(self.llm, tools, prompt)
        self.agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True, handle_parsing_errors=True)

    async def create_plan(self, user_request: str) -> dict:
        """根据用户请求生成执行计划"""
        # 这里我们直接让LLM生成结构化计划,而不是真正运行Tools。
        # 更复杂的实现可以结合Tool使用来获取额外信息(如查询代码库结构)。
        plan_prompt = f"""
        用户请求:{user_request}
        请基于上述可用技能,生成一个JSON格式的执行计划。
        """
        # 简化:直接调用LLM生成JSON。实际应使用LangChain的OutputParser。
        from langchain.schema import HumanMessage
        messages = [
            HumanMessage(content=plan_prompt)
        ]
        # 注意:这里需要配置正确的OpenAI API Key
        response = await self.llm.ainvoke(messages)
        # 假设response.content是合法的JSON字符串
        try:
            plan = json.loads(response.content)
            return plan
        except json.JSONDecodeError:
            # 如果解析失败,可以尝试用文本提取或重试
            print(f"Planner 返回非JSON内容: {response.content}")
            # 返回一个兜底的简单计划
            return {
                "goal": user_request,
                "steps": [
                    {
                        "id": "1",
                        "description": "分析用户请求并理解意图",
                        "required_skill": "analyze_issue_skill",
                        "depends_on": [],
                        "input_parameters": {"issue_text": user_request}
                    }
                ]
            }

5.2 编排器 (Orchestrator) 实现

编排器是粘合剂,负责协调所有Agent和Skill。

# file: main.py (Orchestrator 核心部分)
import asyncio
from agents.planner_agent import PlannerAgent
from agents.executor_agent import ExecutorAgent
from agents.reviewer_agent import ReviewerAgent
from models.workflow_models import WorkflowState, WorkflowStep
from skills.skill_registry import SkillRegistry
import json

class Orchestrator:
    def __init__(self, openai_api_key: str):
        self.planner = PlannerAgent(openai_api_key)
        self.executor = ExecutorAgent()
        self.reviewer = ReviewerAgent(openai_api_key)
        self.skill_registry = SkillRegistry()
        self.workflow_state = {}

    async def execute_workflow(self, request_id: str, user_request: str):
        """执行完整的工作流"""
        print(f"[Orchestrator] 开始处理请求: {request_id}")
        # 1. 初始化工作流状态
        self.workflow_state[request_id] = WorkflowState(
            request_id=request_id,
            original_request=user_request,
            status="PLANNING"
        )

        # 2. 规划阶段
        print(f"[{request_id}] 阶段: 规划")
        plan = await self.planner.create_plan(user_request)
        self.workflow_state[request_id].plan = plan
        self.workflow_state[request_id].status = "EXECUTING"
        print(f"生成计划: {json.dumps(plan, indent=2, ensure_ascii=False)}")

        # 3. 按计划执行步骤
        steps = plan.get("steps", [])
        for step in steps:
            step_id = step["id"]
            print(f"[{request_id}] 执行步骤: {step_id} - {step['description']}")
            # 更新步骤状态
            current_step = WorkflowStep(**step, status="RUNNING")
            self.workflow_state[request_id].current_step = current_step

            # 执行技能
            skill_name = step["required_skill"]
            skill = self.skill_registry.get_skill(skill_name)
            if not skill:
                print(f"错误: 未找到技能 {skill_name}")
                current_step.status = "FAILED"
                current_step.error = f"Skill {skill_name} not found."
                self.workflow_state[request_id].status = "FAILED"
                break

            # 准备输入参数(这里需要从上下文或上一步结果中解析,简化处理)
            input_params = step.get("input_parameters", {})
            # 执行
            output = await skill.execute(input_params)

            # 记录结果
            current_step.output = output.dict()
            current_step.status = "SUCCESS" if output.success else "FAILED"

            if not output.success:
                print(f"步骤 {step_id} 执行失败: {output.message}")
                self.workflow_state[request_id].status = "FAILED"
                # 触发人工介入
                await self._trigger_human_intervention(request_id, step_id, output)
                break

            # 将输出存入上下文,供后续步骤使用
            self.workflow_state[request_id].context[step_id] = output.result

        # 4. 审核阶段 (如果执行成功)
        if self.workflow_state[request_id].status == "EXECUTING":
            self.workflow_state[request_id].status = "REVIEWING"
            print(f"[{request_id}] 阶段: 审核")
            final_result = self.workflow_state[request_id].context.get(steps[-1]["id"])
            review_passed = await self.reviewer.review(user_request, final_result)
            if review_passed:
                self.workflow_state[request_id].status = "COMPLETED"
                print(f"[{request_id}] 工作流执行完成!")
            else:
                self.workflow_state[request_id].status = "NEEDS_HUMAN_REVIEW"
                print(f"[{request_id}] 审核未通过,等待人工复审。")
                # 触发人工介入

        # 5. 清理资源(如销毁所有沙箱)
        await self._cleanup(request_id)

        return self.workflow_state[request_id]

    async def _trigger_human_intervention(self, request_id: str, step_id: str, error_output):
        """模拟触发人工介入,例如发送通知到消息队列或API"""
        print(f"[HITL] 请求 {request_id} 的步骤 {step_id} 需要人工介入。错误: {error_output.message}")
        # 在实际系统中,这里可能会:
        # 1. 将任务状态写入数据库,并标记为“待处理”
        # 2. 向Slack/钉钉频道发送通知
        # 3. 调用一个Webhook到人工处理平台
        # 本例中我们只是打印日志并暂停工作流。
        self.workflow_state[request_id].hitl_pending = True

    async def _cleanup(self, request_id: str):
        """清理工作流占用的资源,如沙箱"""
        # 遍历所有步骤,找到使用了沙箱的技能,并销毁沙箱
        state = self.workflow_state.get(request_id)
        if state and state.plan:
            for step in state.plan.get("steps", []):
                step_data = state.context.get(step["id"], {})
                sandbox_id = step_data.get("sandbox_id")
                if sandbox_id:
                    # 调用沙箱管理器销毁
                    # sandbox_manager.destroy_sandbox(sandbox_id)
                    print(f"[Cleanup] 销毁沙箱 {sandbox_id}")

6. 运行与验证:处理一个GitHub Issue

现在,让我们模拟一个完整的场景。假设我们收到一个用户请求:“请分析并尝试修复GitHub仓库 https://github.com/example/demo-repo 中的Issue #1,该Issue标题是‘登录接口在密码为空时返回500错误’。”

启动服务与执行流程:

  1. 准备环境变量 :在项目根目录创建 .env 文件,填入你的OpenAI API Key。

    OPENAI_API_KEY=sk-your-key-here
    
  2. 编写一个简单的启动脚本

    # file: run_demo.py
    import asyncio
    import sys
    from main import Orchestrator
    from dotenv import load_dotenv
    import os
    
    load_dotenv()
    
    async def main():
        api_key = os.getenv("OPENAI_API_KEY")
        if not api_key:
            print("错误: 请在 .env 文件中设置 OPENAI_API_KEY")
            sys.exit(1)
    
        orchestrator = Orchestrator(openai_api_key=api_key)
        request_id = "demo_001"
        user_request = "请分析并尝试修复GitHub仓库 https://github.com/example/demo-repo 中的Issue #1,该Issue标题是‘登录接口在密码为空时返回500错误’。"
        
        print(f"提交请求: {user_request}")
        result_state = await orchestrator.execute_workflow(request_id, user_request)
        
        print("\n" + "="*50)
        print("工作流最终状态:")
        print(f"  请求ID: {result_state.request_id}")
        print(f"  状态: {result_state.status}")
        print(f"  当前步骤: {result_state.current_step.id if result_state.current_step else 'N/A'}")
        if result_state.status == "COMPLETED":
            print("  结果: 工作流成功完成!")
        elif result_state.status == "NEEDS_HUMAN_REVIEW":
            print("  结果: 需要人工复审。")
        else:
            print("  结果: 工作流执行失败。")
    
    if __name__ == "__main__":
        asyncio.run(main())
    
  3. 运行并观察输出

    python run_demo.py
    

    预期输出(简化示例):

    [Orchestrator] 开始处理请求: demo_001
    [demo_001] 阶段: 规划
    生成计划: {
      "goal": "修复demo-repo仓库中关于登录接口空密码500错误的Issue #1",
      "steps": [
        {
          "id": "1",
          "description": "克隆目标代码仓库到沙箱环境",
          "required_skill": "git_clone_skill",
          "depends_on": [],
          "input_parameters": {
            "repo_url": "https://github.com/example/demo-repo",
            "branch": "main"
          }
        },
        {
          "id": "2",
          "description": "获取并分析Issue #1的详细内容",
          "required_skill": "fetch_issue_skill",
          "depends_on": ["1"],
          "input_parameters": {
            "issue_number": 1
          }
        },
        {
          "id": "3",
          "description": "定位与登录接口相关的代码文件",
          "required_skill": "locate_code_skill",
          "depends_on": ["2"],
          "input_parameters": {
            "keywords": ["login", "auth", "password"]
          }
        },
        {
          "id": "4",
          "description": "分析代码,找出空密码导致500错误的原因",
          "required_skill": "analyze_code_skill",
          "depends_on": ["3"],
          "input_parameters": {}
        },
        {
          "id": "5",
          "description": "生成修复该问题的代码补丁",
          "required_skill": "generate_patch_skill",
          "depends_on": ["4"],
          "input_parameters": {}
        },
        {
          "id": "6",
          "description": "在沙箱中运行项目的单元测试,验证修复是否破坏现有功能",
          "required_skill": "run_test_skill",
          "depends_on": ["5"],
          "input_parameters": {}
        }
      ]
    }
    [demo_001] 执行步骤: 1 - 克隆目标代码仓库到沙箱环境
    [git_clone_skill] 创建沙箱: abc123def
    [demo_001] 执行步骤: 2 - 获取并分析Issue #1的详细内容
    ...
    [demo_001] 阶段: 审核
    [Reviewer] 审核通过。生成的补丁逻辑正确,且通过了单元测试。
    [demo_001] 工作流执行完成!
    ==================================================
    工作流最终状态:
      请求ID: demo_001
      状态: COMPLETED
      当前步骤: 6
      结果: 工作流成功完成!
    

7. 常见问题与排查思路

在构建和运行此类系统时,你会遇到一些典型问题。以下是一个快速排查指南:

问题现象 可能原因 排查方式 解决方案
Planner生成的计划不合理 1. Prompt指令不清晰。
2. 大模型对可用技能理解有误。
3. 上下文信息不足。
1. 检查规划Agent的System Prompt是否准确描述了技能和能力边界。
2. 在Prompt中提供更详细的技能描述和示例。
3. 输出计划后,加入一个“计划验证”步骤(可由另一个轻量级模型或规则完成)。
优化Prompt工程,加入少样本示例(Few-Shot)。考虑使用更强大的模型(如GPT-4)进行规划。
Skill在沙箱中执行失败 1. 沙箱内缺少必要的依赖或工具。
2. 网络权限问题(沙箱可能禁用了网络)。
3. 命令执行超时或资源不足。
1. 检查沙箱基础镜像的Dockerfile,确保安装了所有需要的包。
2. 查看 execute_in_sandbox 返回的 stderr 信息。
3. 监控沙箱容器的资源使用情况(CPU、内存)。
完善基础镜像。对于需要网络的技能,在创建沙箱时谨慎开启网络(如 --network host ),并评估安全风险。设置执行超时和资源限制。
多步骤间数据传递失败 1. 上一步Skill的输出格式不符合下一步Skill的输入要求。
2. Orchestrator的上下文管理有bug。
1. 为每个Skill定义严格的Pydantic输入/输出模型。
2. 在Orchestrator中打印每一步的输入和输出,进行比对。
3. 编写单元测试,测试两个Skill的串联。
使用统一的、结构化的数据格式(如JSON Schema)定义Skill的输入输出。在Orchestrator中实现一个数据转换层。
人工介入流程未触发 1. 审核Agent的置信度阈值设置过高或过低。
2. HITL平台的通知机制(如Webhook)配置错误。
3. 工作流状态机逻辑有误。
1. 检查审核Agent的日志,看其是否对结果产生了“低置信度”或“拒绝”的判断。
2. 测试HITL平台的接收端点。
3. 在关键决策点添加日志,跟踪状态流转。
调整审核逻辑的阈值。为HITL流程编写集成测试。使用更可靠的消息队列(如RabbitMQ)进行通知。
系统性能低下 1. 串行执行步骤,未利用并发。
2. 沙箱创建/销毁开销大。
3. 大模型调用延迟高。
1. 分析工作流,识别可以并行执行的独立步骤。
2. 考虑使用沙箱池(Sandbox Pool)进行预热和复用。
3. 对大模型响应进行缓存,或使用更小、更快的模型处理简单步骤。
在Orchestrator中实现步骤的依赖图分析和并行调度。对于轻量级任务,评估是否必须使用沙箱。

8. 最佳实践与工程建议

将多Agent系统投入生产环境,需要遵循严格的工程准则:

  1. 安全性第一

    • 最小权限原则 :每个沙箱只拥有完成任务所必需的最小权限。永远不要以root身份运行。
    • 网络隔离 :默认禁用沙箱网络。必须联网时,使用白名单机制限制可访问的域名和端口。
    • 输入验证与净化 :对所有传入Skill的参数进行严格的验证和净化,防止注入攻击。
    • 敏感信息管理 :API Keys、令牌等绝不以明文形式传递或记录。使用安全的秘密管理服务(如HashiCorp Vault、AWS Secrets Manager)。
  2. 可观测性与日志

    • 结构化日志 :为每个请求、每个步骤、每个Skill执行生成唯一的追踪ID(Trace ID),并记录结构化的日志(JSON格式),便于聚合和查询。
    • 监控指标 :收集关键指标,如:工作流成功率、各步骤平均耗时、沙箱资源使用率、大模型调用次数与成本、人工介入率。
    • 链路追踪 :集成OpenTelemetry等工具,可视化整个工作流的调用链路,快速定位瓶颈和故障点。
  3. 技能(Skill)的设计与管理

    • 单一职责 :每个Skill应只做一件事,并把它做好。这有利于复用、测试和进化。
    • 版本化 :对Skill进行版本控制。当Skill更新后,旧的工作流实例应能继续使用旧版本,新工作流可以使用新版本。
    • 自动化测试 :为每个Skill编写单元测试和集成测试,确保其功能正确,并且与沙箱环境的交互正常。
    • 进化机制 :建立系统化的反馈循环。将人工介入的修正结果、Skill执行的成功/失败记录,定期用于重新训练或优化Skill的提示词(Prompt)或内部逻辑。
  4. 错误处理与韧性

    • 优雅降级 :当某个非核心Skill失败时,工作流应能跳过或采用备选方案继续,而不是整体崩溃。
    • 重试与回退 :对于网络波动、第三方API暂时不可用等临时性错误,实现带指数退避的重试机制。对于无法恢复的错误,应有清晰的回退路径(如转人工)。
    • 状态持久化 :Orchestrator的工作流状态应定期持久化到数据库。这样在服务重启后,可以恢复中断的工作流。
  5. 成本与性能优化

    • 模型选型 :根据任务复杂度选择合适的模型。规划、创意生成等复杂任务用大模型(如GPT-4),简单的文本提取、格式转换可以用小模型(如GPT-3.5-Turbo)或规则引擎。
    • 缓存策略 :对频繁出现且结果稳定的子任务(如代码库的依赖分析结果)进行缓存。
    • 异步处理 :将耗时长的任务(如代码生成、测试运行)设计为异步,通过消息队列触发,避免阻塞HTTP请求。

构建企业级的多Agent协同系统,技术实现只是第一步。更重要的是建立起围绕它的开发流程、运维规范和持续改进的文化。从一个小而精的场景(如自动处理GitHub Issue)开始验证,逐步扩展技能库和应用范围,是稳妥且高效的落地路径。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐