diff --git a/README.md b/README.md index bbaa04bd..9a9248fb 100644 --- a/README.md +++ b/README.md @@ -238,7 +238,7 @@ LLM / Agent SDK 相关节点说明: - **结构规划 Agent**:需求拆解在前置阶段完成,规划时直接复用结构化任务清单,再通过节点增删改与 `update_node_params` 工具补全 params(Jinja 表达式为主)。`WorkflowBuilder` 会把推导出的 edges、condition 分支与 `depends_on` 写回骨架,方便下游校验共享上下文;节点字段也会按节点类型或 action schema 过滤无关字段,避免 Agent 生成不可识别的参数。【F:velvetflow/planner/structure.py†L373-L1168】【F:velvetflow/planner/workflow_builder.py†L20-L222】 - **动作合法性守卫**:若发现 `action_id` 缺失或未注册,会先尝试基于 display_name/原 action_id 检索替换,再将剩余问题交给 LLM 修复,避免幻觉动作进入最终 Workflow。【F:velvetflow/planner/orchestrator.py†L104-L343】 - **Jinja 规范化与参数一致性**:规划/校验阶段会将简单路径转换为 Jinja 字符串,并对非模板参数给出修复建议;参数补全阶段的 schema 约束来自 `params_tools.py`,确保节点 params 与动作 arg_schema 对齐。【F:velvetflow/planner/params_tools.py†L1-L193】【F:velvetflow/verification/jinja_validation.py†L10-L189】 -- **自修复 Agent**:当静态校验或本地修复仍未通过时,使用当前 workflow 字典与 `ValidationError` 列表提示模型修复。Agent 可以调用命名修复工具(如替换引用、补必填参数、规范化绑定路径)或提交补丁文本,直至通过或达到 `max_repair_rounds`,并在过程中保留最近一次合法版本以确保可回退。【F:velvetflow/planner/repair.py†L616-L756】【F:velvetflow/planner/orchestrator.py†L664-L940】 +- **自修复 Agent**:当静态校验或本地修复仍未通过时,使用当前 workflow 字典与 `ValidationError` 列表提示模型修复。Agent 可以调用命名修复工具(如替换引用、补必填参数、规范化绑定路径)或提交补丁文本,直至通过,并在过程中保留最近一次合法版本以确保可回退。【F:velvetflow/planner/repair.py†L616-L756】【F:velvetflow/planner/orchestrator.py†L664-L940】 ### Agent 工具的设计与运行方式 - **会话级工具与闭包状态**:结构规划的工具集(需求拆解、检索、设置 meta、节点增删改、参数补全)在 `plan_workflow_structure_with_llm` 内使用 `@function_tool(strict_mode=False)` 声明,并依托闭包保存 `WorkflowBuilder`、动作候选与检索结果等上下文,`planner/agent_runtime.py` 统一导出 `Agent`/`Runner`/`function_tool` 便于切换 Agent SDK 版本。【F:velvetflow/planner/structure.py†L373-L1836】【F:velvetflow/planner/agent_runtime.py†L4-L26】 diff --git a/build_workflow.py b/build_workflow.py index 3c64be4e..6ae25bb3 100644 --- a/build_workflow.py +++ b/build_workflow.py @@ -57,7 +57,6 @@ def plan_workflow(user_nl: str, search_service: Optional[HybridActionSearchServi search_service=hybrid_searcher, action_registry=BUSINESS_ACTIONS, max_rounds=50, - max_repair_rounds=3, ) diff --git a/tests/test_update_workflow_planner.py b/tests/test_update_workflow_planner.py index 21eacf62..ae1bed67 100644 --- a/tests/test_update_workflow_planner.py +++ b/tests/test_update_workflow_planner.py @@ -88,7 +88,6 @@ def _fake_plan( search_service=_DummySearch(), action_registry=[{"action_id": "demo.start", "params_schema": {}}, {"action_id": "demo.summary", "params_schema": {}}], max_rounds=1, - max_repair_rounds=0, ) assert captured_existing["value"]["workflow_name"] == "demo" @@ -99,4 +98,3 @@ def _fake_plan( summarize = next(node for node in result_nodes if node.get("id") == "summarize") assert summarize.get("depends_on") == ["start"] assert summarize.get("params", {}).get("source") == "{{ result_of.start.message }}" - diff --git a/update_workflow.py b/update_workflow.py index e0378953..a25e5bd5 100644 --- a/update_workflow.py +++ b/update_workflow.py @@ -90,12 +90,6 @@ def main(argv: list[str] | None = None) -> int: default=100, help="结构规划阶段的最大迭代轮次(默认: 100)", ) - parser.add_argument( - "--max-repair-rounds", - type=int, - default=3, - help="LLM 修复阶段的最大迭代轮次(默认: 3)", - ) args = parser.parse_args(argv) # Parse requirement from CLI/file input before reading the workflow file. @@ -133,7 +127,6 @@ def main(argv: list[str] | None = None) -> int: search_service=search_service, action_registry=action_registry, max_rounds=args.max_rounds, - max_repair_rounds=args.max_repair_rounds, model=args.model or OPENAI_MODEL, ) except Exception as exc: # noqa: BLE001 - surface model/IO issues diff --git a/velvetflow/executor/graph.py b/velvetflow/executor/graph.py index fa5e3d13..0eba107f 100644 --- a/velvetflow/executor/graph.py +++ b/velvetflow/executor/graph.py @@ -24,7 +24,7 @@ def _derive_edges(self, workflow: Workflow) -> List[Dict[str, Any]]: def _find_start_nodes( self, nodes: Mapping[str, Dict[str, Any]], edges: List[Dict[str, Any]] ) -> List[str]: - """Locate start nodes by combining explicit标记与“无入度”节点。""" + """Locate start nodes, preferring explicit start markers.""" all_ids = set(nodes.keys()) to_ids = set() @@ -50,7 +50,7 @@ def _find_start_nodes( outbound_roots = [nid for nid in inbound_free if nid in from_ids] if starts: - return list(dict.fromkeys(starts + condition_starts + outbound_roots)) + return list(dict.fromkeys(starts)) # 若没有显式 start 节点,则退化为无入度节点集合以保证流程仍可执行。 merged = list(dict.fromkeys(condition_starts + outbound_roots + inbound_free)) diff --git a/velvetflow/planner/orchestrator.py b/velvetflow/planner/orchestrator.py index fd4eb5f0..c233b6f9 100644 --- a/velvetflow/planner/orchestrator.py +++ b/velvetflow/planner/orchestrator.py @@ -9,6 +9,7 @@ """ import json +from dataclasses import asdict, is_dataclass from typing import Any, Callable, Dict, List, Mapping from velvetflow.logging_utils import ( @@ -39,6 +40,16 @@ def _validate_workflow( ) +def _serialize_validation_error(error: ValidationError) -> Dict[str, Any]: + """Convert validation errors into JSON-serializable dictionaries.""" + + if hasattr(error, "model_dump"): + return error.model_dump() + if is_dataclass(error): + return asdict(error) + return error.__dict__ + + def plan_workflow_with_two_pass( nl_requirement: str, search_service: HybridActionSearchService, @@ -103,9 +114,13 @@ def plan_workflow_with_two_pass( validation_errors = _validate_workflow(guarded, action_registry) if validation_errors: + serialized_errors = json.dumps( + [_serialize_validation_error(err) for err in validation_errors], + ensure_ascii=False, + ) log_warn( - "[plan_workflow_with_two_pass] Validation produced errors; returning workflow with warnings.", - json.dumps([err.model_dump() for err in validation_errors], ensure_ascii=False), + "[plan_workflow_with_two_pass] Validation produced errors; returning workflow with warnings. " + f"{serialized_errors}", ) return guarded @@ -172,9 +187,13 @@ def update_workflow_with_two_pass( validation_errors = _validate_workflow(updated_workflow, action_registry) if validation_errors: + serialized_errors = json.dumps( + [_serialize_validation_error(err) for err in validation_errors], + ensure_ascii=False, + ) log_warn( - "[update_workflow_with_two_pass] Validation produced errors; returning workflow with warnings.", - json.dumps([err.model_dump() for err in validation_errors], ensure_ascii=False), + "[update_workflow_with_two_pass] Validation produced errors; returning workflow with warnings. " + f"{serialized_errors}", ) return updated_workflow diff --git a/webapp/server.py b/webapp/server.py index 4dcc6e1b..03239765 100644 --- a/webapp/server.py +++ b/webapp/server.py @@ -154,7 +154,6 @@ def _run_planner_for_request( search_service=search_service, action_registry=BUSINESS_ACTIONS, max_rounds=100, - max_repair_rounds=3, progress_callback=progress_callback, ) @@ -163,7 +162,6 @@ def _run_planner_for_request( search_service=search_service, action_registry=BUSINESS_ACTIONS, max_rounds=100, - max_repair_rounds=3, progress_callback=progress_callback, )