Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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】
Expand Down
1 change: 0 additions & 1 deletion build_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)


Expand Down
2 changes: 0 additions & 2 deletions tests/test_update_workflow_planner.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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 }}"

7 changes: 0 additions & 7 deletions update_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions velvetflow/executor/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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))
Expand Down
27 changes: 23 additions & 4 deletions velvetflow/planner/orchestrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 0 additions & 2 deletions webapp/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)

Expand All @@ -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,
)

Expand Down
Loading