一、 核心架构与数据流
本系统的核心是 “一个规划器,一组专业智能体,一张约束拓扑图” 。目标是构建一个能够理解和执行团队规范、并能从错误中学习反哺的协作式AI系统。
拓扑图给了规划器"在约束内自由"的能力,既避免了无限探索,又保留了动态适应的灵活性。
约束拓扑图不是限制,而是解放——它通过定义清晰的边界,让规划器在边界内可以安全、高效地探索最优路径。它把论文中的"动态规划"从理论变成了工程可实现的东西:
- 对规划器:它提供了决策的上下文和边界
- 对智能体:它定义了谁可以调用谁,避免无效调用
- 对研发:它让系统的行为变得可理解和可配置
- 对系统本身:它提供了错误恢复和追溯的蓝图
1、约束拓扑图到底是什么?
约束拓扑图是一个有向图 G = (V, Ê) ,其中:
- V (节点) :代表智能体(如配置生成器、代码生成器等)
- Ê (边) :定义允许的执行路径
但这是学术定义。工程上,约束拓扑图就是工作流的“交通规则” ——它规定了:
- 谁能接在谁后面(比如测试编码器必须在代码生成器之后)
- 谁可以重试谁(比如代码生成器失败后可以重新调用配置生成器)
- 谁能绕过谁(在特定条件下可以跳过某些步骤)
2、为什么需要约束拓扑图?——不这么做的后果
场景1:无约束的完全自由调用
# 伪代码:规划器可以随意调用任何智能体
planner.decide_next() # 可能输出:code_generator
# 但此时连配置文件都没生成,代码生成器根本不知道要生成什么
# 结果:空跑、浪费token、无限循环
场景2:固定的顺序工作流
# 固定顺序:config → utils → template → code → test
# 但如果config生成失败,整个流程卡死
# 或者如果test失败需要修改code,但固定顺序不允许回溯
约束拓扑图的解决方案
它既不是完全自由,也不是完全固定,而是在约束内的动态规划。
3、约束拓扑图应用
框架流程图:
会用到的agents定义
# topology.yaml -
agents:
config_generator:
description: "生成接口定义、数据库操作配置、缓存配置等"
success_criteria: "生成的YAML/JSON配置完整且符合规范"
utils_retriever:
description: "从代码库检索工具类、Utils方法、常量等"
success_criteria: "返回至少1个相关工具类"
dto_generator:
description: "生成Request/Response DTO类"
success_criteria: "生成的DTO包含所有字段和注解"
mapper_generator:
description: "生成MyBatis Mapper接口和XML"
success_criteria: "生成的Mapper包含必要的方法"
service_generator:
description: "生成Service层实现类"
success_criteria: "生成的Service包含完整的业务逻辑"
controller_generator:
description: "生成Controller层接口"
success_criteria: "生成的Controller包含正确的路由和参数"
test_generator:
description: "生成单元测试和集成测试"
success_criteria: "测试代码可编译,覆盖率>80%"
transitions:
start:
allowed_next: ["config_generator"]
config_generator:
allowed_next: ["utils_retriever", "dto_generator"]
utils_retriever:
allowed_next: ["dto_generator", "service_generator"]
dto_generator:
allowed_next: ["mapper_generator", "service_generator"]
mapper_generator:
allowed_next: ["service_generator"]
service_generator:
allowed_next: ["controller_generator", "test_generator", "config_generator"]
controller_generator:
allowed_next: ["test_generator"]
test_generator:
allowed_next: ["service_generator", "controller_generator"]
error_recovery:
service_generator_failed:
retry_same: true
fallback_to: ["config_generator", "dto_generator", "mapper_generator"]
test_generator_failed:
retry_same: true
fallback_to: ["service_generator", "controller_generator"]
4、约束拓扑图在规划器决策中的作用
现在看规划器如何利用这个图做决策。以下是规划器的核心决策代码:
# planner_with_topology.py - 展示拓扑图如何指导决策
class TopologyAwarePlanner:
def __init__(self, topology_config: Dict, llm_client):
self.topology = topology_config
self.llm = llm_client
async def decide_next_agent(self,
current_state: Dict,
last_agent: Optional[str],
failed_agents: List[str]) -> Dict:
"""
基于拓扑图决定下一步
Args:
current_state: 当前所有智能体的状态
last_agent: 上一步执行的智能体
failed_agents: 当前任务中失败的智能体列表
"""
# 1. 从拓扑图获取允许的下一个智能体
if last_agent is None:
# 初始状态
allowed_next = self.topology["transitions"]["start"]["allowed_next"]
else:
allowed_next = self.topology["transitions"][last_agent]["allowed_next"]
conditions = self.topology["transitions"][last_agent].get("conditions", {})
# 2. 过滤掉不需要的选项
filtered_next = self._filter_by_context(allowed_next, current_state, failed_agents)
# 3. 如果有失败的智能体,检查错误恢复路径
if failed_agents:
recovery_options = self._get_recovery_options(failed_agents[-1], filtered_next)
if recovery_options:
filtered_next = recovery_options
# 4. 如果过滤后没有选项,考虑HITL
if not filtered_next:
return {"type": "hitl", "reason": "无可用路径,需要人工介入"}
# 5. 让LLM从过滤后的选项中选择最优的
decision = await self._llm_select_best(
options=filtered_next,
conditions=conditions,
state=current_state
)
return decision
def _filter_by_context(self, allowed_next: List[str],
state: Dict, failed_agents: List[str]) -> List[str]:
"""根据上下文过滤选项"""
filtered = []
for agent in allowed_next:
# 检查该智能体需要的输入是否已存在
agent_spec = self.topology["agents"].get(agent, {})
required_inputs = agent_spec.get("input_required", [])
# 检查状态中是否已有这些输入
has_inputs = all(
inp in state.get("artifacts", {}) or
inp in state.get("context", {})
for inp in required_inputs
)
if has_inputs:
filtered.append(agent)
else:
# 如果缺少输入,但允许从失败中恢复,可能仍保留
if agent in failed_agents:
# 检查是否允许重试相同智能体
recovery = self.topology["error_recovery"].get(f"{agent}_failed", {})
if recovery.get("retry_same", False):
filtered.append(agent)
return filtered
def _get_recovery_options(self, failed_agent: str,
current_options: List[str]) -> List[str]:
"""获取错误恢复选项"""
recovery_key = f"{failed_agent}_failed"
recovery = self.topology["error_recovery"].get(recovery_key, {})
fallback = recovery.get("fallback_to", [])
# 只返回同时在当前选项和fallback中的
return [opt for opt in fallback if opt in current_options]
async def _llm_select_best(self, options: List[str],
conditions: Dict, state: Dict) -> Dict:
"""让LLM从多个合法选项中选择最优的"""
prompt = f"""
你需要在以下合法的智能体中选择下一个要调用的:
【可用选项】
{json.dumps(options, indent=2)}
【各选项的适用条件】
{json.dumps(conditions, indent=2)}
【当前任务状态】
- 已完成: {state.get('completed_steps', [])}
- 失败: {state.get('failed_agents', [])}
- 现有产物: {list(state.get('artifacts', {}).keys())}
【当前错误】
{json.dumps(state.get('errors', [])[-2:], indent=2)}
请基于以下原则选择最优的下一步:
1. **最小化迭代次数**:优先选择能直接推进任务的
2. **最大化成功率**:优先选择当前上下文最完备的
3. **错误恢复优先**:如果有失败的智能体,优先选择能修复错误的
输出格式:
{{
"actor": "选择的智能体名称",
"reason": "为什么选择这个",
"planner_input": "给该智能体的具体指令"
}}
"""
response = await self.llm.complete(prompt, temperature=0.1)
return json.loads(response)
5、约束拓扑图的核心价值
| 保证工程可靠性 | 没有拓扑图时可能发生的灾难planner: "调用 testcase_coder!"但此时 code_generator 还没成功,没有代码可测试结果:testcase_coder 不知道测试什么,空转有拓扑图时if"code_generator"notinstate["success"]: 拓扑图不允许从其他节点直接到 testcase_coder planner: "不能调用 testcase_coder,先完成代码生成" |
|---|---|
| 实现错误隔离和追溯 | 向研发人员解释为什么系统做了某个决定explanation=f"""当前步骤: {last_agent}根据拓扑图,允许的下一步: {allowed_next}基于当前上下文({context_summary}),选择了 {chosen_agent}原因: {reason}"""研发人员可以理解系统的行为逻辑 |
| 提供可解释性 | 向研发人员解释为什么系统做了某个决定explanation=f"""当前步骤: {last_agent}根据拓扑图,允许的下一步: {allowed_next}基于当前上下文({context_summary}),选择了 {chosen_agent}原因: {reason}"""研发人员可以理解系统的行为逻辑 |
| 支持渐进式团队适配 | 团队A的拓扑testcase_generator: allowed_next: ["testcase_coder"] 必须实现测试才能继续团队B的拓扑 testcase_generator: allowed_next: ["testcase_coder","code_generator"] 可以跳过测试先出代码 |
6、拓扑图的学习和动态演化
最后,拓扑图本身不是静态的,它可以学习和演化:
class TopologyLearner:
def __init__(self, base_topology):
self.topology = base_topology
self.execution_stats = {}
def record_execution(self, task_id: str, path: List[str], success: bool):
"""记录一次执行路径和结果"""
for i in range(len(path)-1):
from_agent = path[i]
to_agent = path[i+1]
key = f"{from_agent}->{to_agent}"
if key not in self.execution_stats:
self.execution_stats[key] = {"success": 0, "total": 0}
self.execution_stats[key]["total"] += 1
if success:
self.execution_stats[key]["success"] += 1
def suggest_topology_update(self) -> Dict:
"""基于历史数据建议拓扑图更新"""
suggestions = {}
for transition, stats in self.execution_stats.items():
success_rate = stats["success"] / stats["total"]
if stats["total"] > 10 and success_rate < 0.3:
# 这条路径成功率太低,建议移除或加条件
from_agent, to_agent = transition.split("->")
suggestions[transition] = {
"action": "remove_or_restrict",
"reason": f"成功率仅{success_rate:.1%}",
"alternative": self._find_alternative_path(from_agent, to_agent)
}
return suggestions
二、 关键模块工程化实现方案
模块一:环境定义与知识库构建
这是系统能够“个性化”的基础,需要工程化地表达团队知识。
- 拓扑图即代码 (Topology as Code) : 将论文中的约束拓扑图实现为一个可配置的、版本化的YAML或JSON文件。这个文件定义了智能体间的依赖关系,是整个工作流的“宪法”。
-
- 示例结构 (
agent_topology.yaml) :
- 示例结构 (
agents:
- name: config_generator
allowed_next: [utils_retriever, code_template_generator]
max_retries: 3
success_criteria: "生成YAML可通过Pydantic模型校验"
- name: code_generator
allowed_next: [testcase_generator, code_template_generator, HITL]
max_retries: 5
success_criteria: "生成的PySpark代码在测试数据集上执行无错误"
- 静态知识库向量化:
-
- 输入: 团队现有的代码库(如GitLab/GitHub仓库)、README文档、Wiki、最佳实践文档、以及历史的“特征规范配置(FSC)”和“数据框注册表(DFR)” 。
- 工程动作: 将这些静态文档切片、清洗,使用Embedding模型存入向量数据库。当智能体(如工具检索器)被调用时,规划器提供的“用户任务”会被向量化,从库中检索最相关的代码片段或文档,作为上下文发送给LLM。这是实现检索增强生成的关键。
模块二:规划器的工程实现
规划器是系统的“大脑”,它是一个特殊的、有状态的LLM智能体。
- 状态管理: 规划器需要维护一个短期记忆,记录当前任务中所有智能体的执行状态(成功/失败/重试次数)、产生的中间产物(配置、代码、错误日志)。
-
- 工程实现: 可以使用一个简单的内存对象或Redis缓存来存储任务的状态机。每次迭代,规划器都会收到这个状态的摘要。
- 决策引擎:
-
- 输入:
当前状态s_t+约束拓扑图G+全局任务描述T。 - 处理: 构造一个高级提示词(如论文附录F所示),要求LLM基于拓扑图的约束和当前状态,选择下一个最优的智能体,并生成该智能体所需的上下文指令(
planner_input)。 - 关键提示词工程: 提示词必须包含:
-
- 角色设定: “你是一个资深机器学习工程师的规划代理...”
- 约束规则: “你只能从拓扑图中定义的
allowed_next列表中选择...” - 状态摘要: “当前状态:config_generator (成功),utils_retriever (失败,错误信息:找不到工具) ...”
- 决策与输出: 要求LLM以严格的JSON格式输出,包含
call_type,actor,reason,args,便于后续程序解析。
- 人工介入(HITL)接口: 当规划器连续重试失败、或置信度低时,应能生成一个暂停信号,并通过即时通讯工具(如Slack、钉钉)向指定的研发人员发送一个带有明确上下文和选项的“审批/澄清请求” 。研发人员的反馈将被作为下一轮规划的输入。
- 输入:
模块三:专业智能体的开发
这些是执行具体任务的“手”,可以是调用LLM的函数,也可以是执行传统脚本的工具。
- 智能体抽象化: 为所有智能体定义一个统一的接口。例如:
-
输入: 一个字典,包含task_context(从规划器来的planner_input),static_knowledge(从向量库检索到的信息)。处理: 调用LLM(如Claude)并执行必要的代码逻辑。输出: 一个标准化的响应,包含status(成功/失败),output_data(生成的代码/配置),error_log(错误信息),metadata(如生成的token数)。
- 关键智能体
| 规划器 | 基于LLM的决策核心,根据当前任务状态和拓扑图约束,选择下一步调用的最优智能体 |
|---|---|
| 配置生成器 | 解析需求文档,生成接口定义、数据库操作配置、缓存策略等元数据 |
| 工具检索器 | 核心是一个代码解析+语义搜索组件。它接收 planner_input 描述的任务,去向量数据库中搜索最相关的工具函数,并返回其名称、签名、导入路径和使用示例。供后续代码生成使用。 |
| 代码生成器 | 提示词包含:任务描述、代码模板、选定的工具函数签名、单元测试用例、以及团队编码规范, 根据配置和工具,生成Controller/Service/DTO/Mapper等工程代码。 |
| 测试生成器 | 基于业务逻辑和代码,生成单元测试和集成测试用例 |
- 错误处理与自省: 当智能体执行失败时,它的输出必须包含结构化的失败原因分析和修复建议(如论文中的
<reason>和<fix>标签),这为规划器提供了关键的“下游失败”信号,用于追溯上游问题。
参考的资料:
Towards Reliable ML Feature Engineering via Planning in Constrained-Topology of LLM Agents
Himanshu Thakur,Anusha Kamath,Anurag Muthyala,Dhwani Sanmukhani,Smruthi Mukund,Jay Katukuri