File size: 7,919 Bytes
a23394a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
#!/usr/bin/env python
from __future__ import annotations

import argparse
import asyncio
import os
import re
import io
import contextlib
from typing import Optional

from dataflow_agent.state import DFRequest, DFState
from dataflow_agent.workflow.wf_pipeline_write import create_operator_write_graph
from dataflow_agent.utils import get_project_root

PROJDIR = get_project_root()

def parse_args():
    p = argparse.ArgumentParser(description="Run operator flow: match -> write -> (optional debug loop)")
    p.add_argument('--chat-api-url',  default='http://123.129.219.111:3000/v1/', help='LLM Chat API base')
    p.add_argument('--model',         default='gpt-4o', help='LLM model name')
    p.add_argument('--language',      default='en', help='Prompt output language')
    p.add_argument('--target',        required=True, help='User requirement / purpose for new operator')
    p.add_argument('--category',      default='Default', help='Operator category for matching (fallback if no classifier)')
    p.add_argument('--output',        default='', help='Optional path to write generated operator code')
    p.add_argument('--json-file',     default='', help='Path to test jsonl file used in debug run')
    p.add_argument('--need-debug',    action='store_true', help='Enable debug loop for executing and fixing the operator')
    p.add_argument('--max-debug-rounds', type=int, default=3, help='Max debug rounds when --need-debug is set')
    return p.parse_args()


"""
Entry script for operator write workflow.
Only keeps CLI parsing, DFState creation, and running the workflow graph.
Graph construction and node/tool definitions live in workflow/wf_pipeline_write.py
"""


async def main():
    args = parse_args()

    from dataflow.cli_funcs.paths import DataFlowPath

    req = DFRequest(
        language=args.language,
        chat_api_url=args.chat_api_url,
        api_key=os.getenv("DF_API_KEY", "sk-dummy"),
        model=args.model,
        target=args.target,
        need_debug=bool(args.need_debug),
        max_debug_rounds=int(args.max_debug_rounds),
        # 默认使用 dataflowagent 下的 10 条测试数据
        json_file=(args.json_file or f"{PROJDIR}/tests/test.jsonl"),
    )
    state = DFState(request=req, messages=[])
    if args.output:
        state.temp_data["pipeline_file_path"] = args.output
    # 若用户通过参数提供了类别,也存到 temp_data 作为兜底
    if args.category:
        state.temp_data["category"] = args.category

    # 显式初始化调试轮次
    state.temp_data["round"] = 0

    graph = create_operator_write_graph().build()
    # LangGraph 默认 recursion_limit=25,当 need_debug 且 max_debug_rounds 较大时容易超限。
    # 计算一个保守上限:主链 4 步 + 每轮 5 步 * 轮次 + buffer 5。
    recursion_limit = 4 + 5 * int(args.max_debug_rounds) + 5
    final_state: DFState = await graph.ainvoke(state, config={"recursion_limit": recursion_limit})

    # ---- 打印结果摘要 ----
    print("==== Match Operator Result ====")
    try:
        if isinstance(final_state, dict):
            matched = final_state.get("matched_ops")
            if not matched:
                matched = (
                    final_state.get("agent_results", {})
                    .get("match_operator", {})
                    .get("results", {})
                    .get("match_operators", [])
                )
        else:
            matched = getattr(final_state, "matched_ops", [])
            if not matched and hasattr(final_state, "agent_results"):
                matched = (
                    final_state.agent_results.get("match_operator", {})
                    .get("results", {})
                    .get("match_operators", [])
                )
        print("Matched ops:", matched or [])
    except Exception:
        print("Matched ops: <unavailable>")

    print("\n==== Writer Result ====")
    try:
        # 兼容 dict 与 DFState 两种返回形态
        if isinstance(final_state, dict):
            code_str = (
                final_state.get("temp_data", {}).get("pipeline_code", "")
                or final_state.get("draft_operator_code", "")
                or final_state.get("agent_results", {}).get("write_the_operator", {}).get("results", {}).get("code", "")
            )
            if not code_str:
                fp = final_state.get("temp_data", {}).get("pipeline_file_path")
                if fp:
                    from pathlib import Path
                    p = Path(fp)
                    try:
                        if p.exists():
                            code_str = p.read_text(encoding="utf-8")
                    except Exception:
                        pass
        else:
            code_str = (
                getattr(final_state, "temp_data", {}).get("pipeline_code", "")
                or getattr(final_state, "draft_operator_code", "")
                or getattr(getattr(final_state, "agent_results", {}), "get", lambda *_: {})("write_the_operator", {}).get("results", {}).get("code", "")
            )
            if not code_str:
                fp = getattr(final_state, "temp_data", {}).get("pipeline_file_path")
                if fp:
                    from pathlib import Path
                    p = Path(fp)
                    try:
                        if p.exists():
                            code_str = p.read_text(encoding="utf-8")
                    except Exception:
                        pass
    except Exception:
        code_str = ""
    print(f"Code length: {len(code_str)}")
    if args.output:
        print(f"Saved to: {args.output}")
    else:
        # 为避免终端刷屏,仅展示前 1000 字符
        preview = (code_str or "")[:1000]
        print("Code preview:\n", preview)

    # ---- Debug runtime 移至 workflow 的 instantiate_operator_main_node ----
    # 入口脚本不再负责内联实例化执行,避免过度复杂。

    # ---- 执行结果摘要 ----
    # 汇总执行结果(鲁棒回退):execution_result -> agent_results -> 挂载属性
    # 兼容 dict 与 DFState 两种返回形态
    if isinstance(final_state, dict):
        exec_res = final_state.get("execution_result", {}) or {}
        if not exec_res or ("success" not in exec_res):
            exec_res = final_state.get("agent_results", {}).get("operator_executor", {}).get("results", {}) or exec_res
    else:
        exec_res = getattr(final_state, "execution_result", {}) or {}
        if (not exec_res or ("success" not in exec_res)) and hasattr(final_state, "agent_results"):
            exec_res = final_state.agent_results.get("operator_executor", {}).get("results", {}) or exec_res
    success = bool(exec_res.get("success"))
    print("\n==== Execution Result (instantiate) ====")
    print("Success:", success)
    if not success:
        stderr = (exec_res.get("stderr") or exec_res.get("traceback") or "")
        print("stderr preview:\n", (stderr or "")[:500])

    # ---- 调试实例化输出预览(来自 instantiate_operator_main_node) ----
    try:
        dbg = None
        if isinstance(final_state, dict):
            dbg = (final_state.get("temp_data") or {}).get("debug_runtime")
        else:
            dbg = getattr(final_state, "temp_data", {}).get("debug_runtime")
        if dbg:
            print("\n==== Debug Runtime Preview ==== ")
            ik = dbg.get("input_key")
            ak = dbg.get("available_keys")
            print("input_key:", ik)
            if ak:
                print("available_keys:", ak)
            stdout_pv = (dbg.get("stdout") or "")[:1000]
            stderr_pv = (dbg.get("stderr") or "")[:1000]
            if stdout_pv:
                print("[debug stdout]\n", stdout_pv)
            if stderr_pv:
                print("[debug stderr]\n", stderr_pv)
    except Exception:
        pass


if __name__ == "__main__":
    asyncio.run(main())