| from __future__ import annotations |
|
|
| from dataflow_agent.state import DFState |
| import re |
| from dataflow_agent.graphbuilder.graph_builder import GenericGraphBuilder |
| from dataflow_agent.toolkits.optool.op_tools import ( |
| local_tool_for_get_purpose, |
| get_operator_content_str, |
| ) |
| from dataflow_agent.toolkits.basetool.file_tools import ( |
| local_tool_for_sample, |
| ) |
| from dataflow_agent.toolkits.optool.op_tools import ( |
| local_tool_for_get_match_operator_code, |
| ) |
| from dataflow_agent.agentroles.data_agents.match import create_match |
| from dataflow_agent.agentroles.data_agents.writer import create_writer |
| from dataflow_agent.agentroles.data_agents.debugger import create_code_debugger |
| from dataflow_agent.agentroles.data_agents.oprewriter import create_rewriter |
| from dataflow_agent.agentroles.data_agents.append_llm_serving import create_llm_append_serving |
| from dataflow_agent.agentroles.data_agents.instantiator import create_llm_instantiator |
| from dataflow_agent.utils import get_project_root |
|
|
| from dataflow_agent.utils import get_project_root |
| PROJDIR = get_project_root() |
|
|
| def create_operator_write_graph() -> GenericGraphBuilder: |
| """Build the operator write workflow graph. |
| |
| Flow: match_operator -> write_the_operator -> operator_executor |
| -> (code_debugger -> op_rewriter -> after_rewrite -> operator_executor)* |
| """ |
| builder = GenericGraphBuilder(state_model=DFState, entry_point="match_operator") |
|
|
| |
| @builder.pre_tool("get_operator_content", "match_operator") |
| def pre_get_operator_content(state: DFState): |
| cat = state.category.get("category") or state.request and getattr(state.request, "category", None) |
| data_type = cat or state.temp_data.get("category") or "Default" |
| return get_operator_content_str(data_type=data_type) |
|
|
| @builder.pre_tool("purpose", "match_operator") |
| def pre_get_purpose(state: DFState): |
| return local_tool_for_get_purpose(state.request) |
|
|
| |
| @builder.pre_tool("example", "write_the_operator") |
| def pre_example_from_matched(state: DFState): |
| """ |
| 为写算子提供更强的 in-context 示例: |
| 将匹配到的所有算子源码(含 import + 类定义)拼接为示例,让 LLM 模仿项目风格。 |
| 优先从 DFState.matched_ops 读取;若为空则回退读取 agent_results。 |
| """ |
| names: list[str] = [] |
| try: |
| if isinstance(state.matched_ops, list) and state.matched_ops: |
| names = list(dict.fromkeys(state.matched_ops)) |
| else: |
| res = state.agent_results.get("match_operator", {}).get("results", {}) |
| names = list(dict.fromkeys(res.get("match_operators", []) or [])) |
| except Exception: |
| names = [] |
|
|
| if not names: |
| return "" |
|
|
| blocks = [] |
| chunk = 3 |
| for i in range(0, len(names), chunk): |
| part = names[i:i+chunk] |
| try: |
| blocks.append(local_tool_for_get_match_operator_code({"match_operators": part})) |
| except Exception: |
| continue |
| code_examples = "\n\n".join([b for b in blocks if b]) |
| |
| return code_examples |
|
|
| @builder.pre_tool("target", "write_the_operator") |
| def pre_target(state: DFState): |
| return state.request.target |
|
|
| |
|
|
| |
| @builder.pre_tool("pipeline_code", "code_debugger") |
| def dbg_get_code(state: DFState): |
| return state.temp_data.get("pipeline_code", "") or getattr(state, "draft_operator_code", "") |
|
|
| @builder.pre_tool("error_trace", "code_debugger") |
| def dbg_get_err(state: DFState): |
| return state.execution_result.get("stderr", "") or state.execution_result.get("traceback", "") |
|
|
| @builder.pre_tool("pipeline_code", "op_rewriter") |
| def rw_get_code(state: DFState): |
| return state.temp_data.get("pipeline_code", "") or getattr(state, "draft_operator_code", "") |
|
|
| @builder.pre_tool("error_trace", "op_rewriter") |
| def rw_get_err(state: DFState): |
| return state.execution_result.get("stderr", "") or state.execution_result.get("traceback", "") |
|
|
| @builder.pre_tool("debug_reason", "op_rewriter") |
| def rw_get_reason(state: DFState): |
| return state.code_debug_result.get("reason", "") |
|
|
| |
| @builder.pre_tool("data_sample", "op_rewriter") |
| def rw_get_data_sample(state: DFState): |
| try: |
| |
| from types import SimpleNamespace as _SN |
| default_test_file = f"{PROJDIR}/tests/test.jsonl" |
| eff_path = getattr(state.request, "json_file", "") or default_test_file |
| stats = local_tool_for_sample(_SN(json_file=eff_path), sample_size=2) |
| return stats.get("samples", []) if isinstance(stats, dict) else [] |
| except Exception: |
| return [] |
|
|
| @builder.pre_tool("available_keys", "op_rewriter") |
| def rw_get_available_keys(state: DFState): |
| try: |
| |
| dbg = state.temp_data.get("debug_runtime", {}) |
| if isinstance(dbg, dict): |
| dbg_keys = dbg.get("available_keys", []) or [] |
| if dbg_keys: |
| return dbg_keys |
| from types import SimpleNamespace as _SN |
| default_test_file = f"{PROJDIR}/tests/test.jsonl" |
| eff_path = getattr(state.request, "json_file", "") or default_test_file |
| stats = local_tool_for_sample(_SN(json_file=eff_path), sample_size=2) |
| return stats.get("available_keys", []) if isinstance(stats, dict) else [] |
| except Exception: |
| return [] |
|
|
| |
| @builder.pre_tool("target", "op_rewriter") |
| def rw_get_target(state: DFState): |
| return getattr(state.request, "target", "") |
|
|
| @builder.pre_tool("preselected_input_key", "op_rewriter") |
| def rw_get_preselected_key(state: DFState): |
| try: |
| from types import SimpleNamespace as _SN |
| default_test_file = f"{PROJDIR}/tests/test.jsonl" |
| eff_path = getattr(state.request, "json_file", "") or default_test_file |
| stats = local_tool_for_sample(_SN(json_file=eff_path), sample_size=2) |
| samples = stats.get("samples", []) if isinstance(stats, dict) else [] |
| keys = stats.get("available_keys", []) if isinstance(stats, dict) else [] |
| if not samples or not keys: |
| return "" |
| import numpy as _np |
| best_k, best_len = "", -1.0 |
| for k in keys: |
| try: |
| vals = [str(s.get(k, "")) for s in samples] |
| avg_len = _np.mean([len(v) for v in vals]) if vals else 0.0 |
| except Exception: |
| avg_len = 0.0 |
| if avg_len > best_len: |
| best_k, best_len = k, avg_len |
| return best_k |
| except Exception: |
| return "" |
|
|
| |
| @builder.pre_tool("pipeline_code", "llm_append_serving") |
| def pre_llm_append_code(state: DFState): |
| return state.temp_data.get("pipeline_code", "") or getattr(state, "draft_operator_code", "") |
|
|
| @builder.pre_tool("llm_serving_snippet", "llm_append_serving") |
| def pre_llm_serving_snippet(state: DFState): |
| return ( |
| "# -------- LLM Serving (Remote) --------\n" |
| "self.llm_serving = APILLMServing_request(\n" |
| ' api_url="http://123.129.219.111:3000/v1/chat/completions",\n' |
| ' key_name_of_api_key="DF_API_KEY",\n' |
| ' model_name="gpt-4o",\n' |
| " max_workers=100,\n" |
| ")\n" |
| ) |
|
|
| |
| @builder.pre_tool("example_data", "llm_append_serving") |
| def pre_llm_append_example(state: DFState): |
| try: |
| from types import SimpleNamespace as _SN |
| default_test_file = f"{PROJDIR}/tests/test.jsonl" |
| eff_path = getattr(state.request, "json_file", "") or default_test_file |
| stats = local_tool_for_sample(_SN(json_file=eff_path), sample_size=2) |
| return stats.get("samples", []) if isinstance(stats, dict) else [] |
| except Exception: |
| return [] |
|
|
| @builder.pre_tool("available_keys", "llm_append_serving") |
| def pre_llm_append_keys(state: DFState): |
| try: |
| from types import SimpleNamespace as _SN |
| default_test_file = f"{PROJDIR}/tests/test.jsonl" |
| eff_path = getattr(state.request, "json_file", "") or default_test_file |
| stats = local_tool_for_sample(_SN(json_file=eff_path), sample_size=2) |
| return stats.get("available_keys", []) if isinstance(stats, dict) else [] |
| except Exception: |
| return [] |
|
|
| @builder.pre_tool("target", "llm_append_serving") |
| def pre_llm_append_target(state: DFState): |
| return getattr(state.request, "target", "") |
|
|
| |
| @builder.pre_tool("pipeline_code", "llm_instantiate") |
| def pre_inst_code(state: DFState): |
| return state.temp_data.get("pipeline_code", "") or getattr(state, "draft_operator_code", "") |
|
|
| @builder.pre_tool("target", "llm_instantiate") |
| def pre_inst_target(state: DFState): |
| return getattr(state.request, "target", "") |
|
|
| @builder.pre_tool("example_data", "llm_instantiate") |
| def pre_inst_example(state: DFState): |
| try: |
| from types import SimpleNamespace as _SN |
| default_test_file = f"{PROJDIR}/tests/test.jsonl" |
| eff_path = getattr(state.request, "json_file", "") or default_test_file |
| stats = local_tool_for_sample(_SN(json_file=eff_path), sample_size=2) |
| return stats.get("samples", []) if isinstance(stats, dict) else [] |
| except Exception: |
| return [] |
|
|
| @builder.pre_tool("available_keys", "llm_instantiate") |
| def pre_inst_keys(state: DFState): |
| try: |
| from types import SimpleNamespace as _SN |
| default_test_file = f"{PROJDIR}/tests/test.jsonl" |
| eff_path = getattr(state.request, "json_file", "") or default_test_file |
| stats = local_tool_for_sample(_SN(json_file=eff_path), sample_size=2) |
| return stats.get("available_keys", []) if isinstance(stats, dict) else [] |
| except Exception: |
| return [] |
|
|
| @builder.pre_tool("preselected_input_key", "llm_instantiate") |
| def pre_inst_preselected_key(state: DFState): |
| try: |
| from types import SimpleNamespace as _SN |
| default_test_file = f"{PROJDIR}/tests/test.jsonl" |
| eff_path = getattr(state.request, "json_file", "") or default_test_file |
| stats = local_tool_for_sample(_SN(json_file=eff_path), sample_size=2) |
| samples = stats.get("samples", []) if isinstance(stats, dict) else [] |
| keys = stats.get("available_keys", []) if isinstance(stats, dict) else [] |
| if not samples or not keys: |
| return "" |
| |
| import numpy as _np |
| best_k, best_len = "", -1.0 |
| for k in keys: |
| try: |
| vals = [str(s.get(k, "")) for s in samples] |
| avg_len = _np.mean([len(v) for v in vals]) if vals else 0.0 |
| except Exception: |
| avg_len = 0.0 |
| if avg_len > best_len: |
| best_k, best_len = k, avg_len |
| return best_k |
| except Exception: |
| return "" |
|
|
| @builder.pre_tool("test_data_path", "llm_instantiate") |
| def pre_inst_test_path(state: DFState): |
| try: |
| default_test_file = f"{PROJDIR}/tests/test.jsonl" |
| return getattr(state.request, 'json_file', '') or default_test_file |
| except Exception: |
| return "" |
|
|
| |
| async def match_node(s: DFState) -> DFState: |
| agent = create_match() |
| return await agent.execute(s, use_agent=False) |
|
|
| async def write_node(s: DFState) -> DFState: |
| agent = create_writer() |
| return await agent.execute(s, use_agent=False) |
|
|
| |
| async def executor_node(s: DFState) -> DFState: |
| return s |
|
|
| async def inject_llm_serving_node(s: DFState) -> DFState: |
| from dataflow_agent.toolkits.tool_manager import get_tool_manager |
| |
| code_str = s.temp_data.get("pipeline_code", "") or getattr(s, "draft_operator_code", "") |
| if code_str and ("self.llm_serving" in code_str or "APILLMServing_request" in code_str): |
| return s |
| agent = create_llm_append_serving(tool_manager=get_tool_manager(), model_name="gpt-4o") |
| s2 = await agent.execute(s, use_agent=True) |
| |
| code_str2 = s2.temp_data.get("pipeline_code", "") or getattr(s2, "draft_operator_code", "") |
| if not code_str2: |
| |
| try: |
| |
| def _hard_inject(code: str) -> str: |
| if (not code) or ("self.llm_serving" in code) or ("APILLMServing_request" in code): |
| return code |
| return code + "\nfrom dataflow.serving import APILLMServing_request\n" |
| new_code = _hard_inject(code_str or "") |
| if new_code and new_code != (code_str or ""): |
| s2.temp_data["pipeline_code"] = new_code |
| s2.draft_operator_code = new_code |
| except Exception: |
| pass |
| return s2 |
|
|
| async def debugger_node(s: DFState) -> DFState: |
| from dataflow_agent.toolkits.tool_manager import get_tool_manager |
|
|
| debugger = create_code_debugger(tool_manager=get_tool_manager()) |
| return await debugger.execute(s, use_agent=True) |
|
|
| async def rewriter_node(s: DFState) -> DFState: |
| from dataflow_agent.toolkits.tool_manager import get_tool_manager |
|
|
| rewriter = create_rewriter(tool_manager=get_tool_manager(), model_name="o3") |
| return await rewriter.execute(s, use_agent=True) |
|
|
| def after_rewrite_node(s: DFState) -> DFState: |
| from dataflow_agent.toolkits.tool_manager import get_tool_manager |
|
|
| rewriter = create_rewriter(tool_manager=get_tool_manager(), model_name="o3") |
| return rewriter.after_rewrite(s) |
|
|
| |
| async def instantiate_operator_main_node(s: DFState) -> DFState: |
| from dataflow_agent.toolkits.tool_manager import get_tool_manager |
| try: |
| agent = create_llm_instantiator(tool_manager=get_tool_manager(), model_name="gpt-4o") |
| s2 = await agent.execute(s, use_agent=True) |
| code_str = s2.temp_data.get("pipeline_code", "") or getattr(s2, "draft_operator_code", "") |
| if not code_str: |
| |
| return s2 |
|
|
| import io, contextlib |
| buf_out, buf_err = io.StringIO(), io.StringIO() |
| try: |
| with contextlib.redirect_stdout(buf_out), contextlib.redirect_stderr(buf_err): |
| exec(code_str, {"__name__": "__main__"}) |
| except SystemExit: |
| pass |
| except Exception as e: |
| s2.temp_data.setdefault("debug_runtime", {}) |
| s2.temp_data["debug_runtime"]["exec_error"] = str(e) |
|
|
| out_s, err_s = buf_out.getvalue(), buf_err.getvalue() |
| selected_key = None |
| try: |
| import re as _re |
| for line in (out_s or "").splitlines(): |
| m = _re.search(r"\[selected_input_key\]\s*(.+)", line) |
| if m: |
| selected_key = m.group(1).strip() |
| break |
| except Exception: |
| selected_key = None |
|
|
| |
| success = False |
| try: |
| import pandas as _pd |
| from pathlib import Path as _Path |
| p = _Path("./cache_local/dataflow_cache_step_step1.jsonl") |
| if p.exists(): |
| df = _pd.read_json(str(p), lines=True) |
| success = (not df.empty) |
| except Exception: |
| success = False |
|
|
| if not selected_key: |
| success = False |
|
|
| |
| scanned_keys = [] |
| try: |
| from types import SimpleNamespace as _SN |
| from dataflow_agent.toolkits.basetool.file_tools import local_tool_for_sample as _lts |
| default_test_file = f"{PROJDIR}/tests/test.jsonl" |
| eff_path = getattr(s2.request, "json_file", "") or default_test_file |
| stats = _lts(_SN(json_file=eff_path), sample_size=2) |
| scanned_keys = stats.get("available_keys", []) if isinstance(stats, dict) else [] |
| except Exception: |
| scanned_keys = [] |
|
|
| if selected_key and scanned_keys and (selected_key not in scanned_keys): |
| success = False |
| if not scanned_keys: |
| success = False |
|
|
| s2.temp_data.setdefault("debug_runtime", {}) |
| s2.temp_data["debug_runtime"].update({ |
| "stdout": out_s[:2000] if out_s else "", |
| "stderr": err_s[:2000] if err_s else "", |
| "input_key": selected_key, |
| "available_keys": scanned_keys or s2.temp_data.get("available_keys", []), |
| "reason": ("NO_SELECTED_INPUT_KEY" if not selected_key else ""), |
| }) |
|
|
| s2.execution_result = { |
| "success": bool(success), |
| "stdout": out_s, |
| "stderr": err_s or s2.temp_data.get("debug_runtime", {}).get("exec_error", ""), |
| "file_path": s2.temp_data.get("pipeline_file_path", ""), |
| } |
| return s2 |
| except Exception: |
| return s |
|
|
| |
| def exec_condition(s: DFState): |
| if s.request.need_debug: |
| if s.execution_result.get("success"): |
| return "__end__" |
| if s.temp_data.get("round", 0) >= s.request.max_debug_rounds: |
| return "__end__" |
| return "code_debugger" |
| else: |
| return "__end__" |
|
|
| nodes = { |
| "match_operator": match_node, |
| "write_the_operator": write_node, |
| "llm_append_serving": inject_llm_serving_node, |
| "llm_instantiate": instantiate_operator_main_node, |
| "code_debugger": debugger_node, |
| "rewriter": rewriter_node, |
| "after_rewrite": after_rewrite_node, |
| } |
| edges = [ |
| ("match_operator", "write_the_operator"), |
| ("write_the_operator", "llm_append_serving"), |
| ("llm_append_serving", "llm_instantiate"), |
| ("code_debugger", "rewriter"), |
| ("rewriter", "after_rewrite"), |
| ("after_rewrite", "llm_append_serving"), |
| ("llm_append_serving", "llm_instantiate"), |
| ] |
|
|
| builder.add_nodes(nodes).add_edges(edges).add_conditional_edges({"llm_instantiate": exec_condition}) |
| return builder |
|
|