diff --git a/pipeline_service/agent_loop.py b/pipeline_service/agent_loop.py index 82d3456..682b96d 100644 --- a/pipeline_service/agent_loop.py +++ b/pipeline_service/agent_loop.py @@ -172,6 +172,7 @@ AGENT_TOOLS = [ {"name":"git_status","description":"查看git仓库状态","params":{"repo_dir":"仓库子目录(可选,默认repos下第一个)"}}, {"name":"git_commit_push","description":"git add + commit + push","params":{"message":"提交信息","repo_dir":"仓库子目录(可选)"}}, {"name":"ask_question","description":"向用户提问(缺少信息时使用)","params":{"question":"问题"}}, + {"name":"deliver","description":"提交最终交付件(代码文件已用write_file写好、git已提交时调用)","params":{"deliverable_type":"交付件类型(如code_files/design_doc)","summary":"概述","result":"交付件正文","files":"JSON数组[{\"path\":\"repos/仓库/src/x.py\",\"content\":\"代码内容\"}](可选)","git_commit_message":"git提交信息(可选)"}}, ] AGENT_SYSTEM_PROMPT = """你是软件开发产线中的「__ROLE__」角色Agent。 @@ -510,6 +511,28 @@ def _resolve_repo_target(workspace_dir, repo_dir): return workspace_dir +def _agent_tools_to_openai_schema(agent_tools): + """把 v1 AGENT_TOOLS({name,description,params})转成 OpenAI function-calling schema。""" + return [ + { + "type": "function", + "function": { + "name": t["name"], + "description": t["description"], + "parameters": { + "type": "object", + "properties": { + k: {"type": "string", "description": v} + for k, v in (t.get("params") or {}).items() + }, + "required": list((t.get("params") or {}).keys()), + }, + }, + } + for t in agent_tools + ] + + async def _exec_agent_tool(tool, params, workspace_dir): p = params or {} try: @@ -619,22 +642,51 @@ async def role_agent_run(project_id, role, agent_id=None, model_name=None): msgs = [{"role": "system", "content": system}] msgs.append({"role": "user", "content": f"执行任务:{title}\n参数:{params_str}"}) - from .llm_bridge import llm_call_msgs + from .llm_bridge import llm_call_msgs, llm_call_msgs_native + tools_schema = _agent_tools_to_openai_schema(AGENT_TOOLS) deliverable = None ask_question = None - # ── Tool Loop ── + # ── Tool Loop(原生 function calling)── for turn in range(15): try: - raw = await llm_call_msgs(msgs, model=model_name, temperature=0.4) + resp = await llm_call_msgs_native(msgs, tools=tools_schema, model=model_name, temperature=0.4) except Exception as e: await sor.sqlExe("UPDATE pipeline_tasks SET state='failed' WHERE id=${tid}$", {"tid": task_id}) logger.error(f"role_agent_run llm failed: task={task_id} err={e}") return {"status": "failed", "task_id": task_id, "error": str(e)[:200]} - act = _parse_agent_action(raw) + native_calls = (resp.get("tool_calls") or []) if isinstance(resp, dict) else [] + if native_calls: + # 回填 assistant(含 tool_calls,OpenAI 原生格式) + msgs.append({"role": "assistant", "content": resp.get("content") or None, "tool_calls": native_calls}) + for tc in native_calls: + fn = tc.get("function", {}) if isinstance(tc, dict) else {} + tool = fn.get("name", "") + try: + params = json.loads(fn.get("arguments") or "{}") + except Exception: + params = {} + + if tool == "deliver": + deliverable = {"action": "deliver", **params} + break + if tool == "ask_question": + ask_question = params.get("question", "") + break + + result = await _exec_agent_tool(tool, params, workspace_dir) + msgs.append({"role": "tool", "tool_call_id": tc.get("id", ""), "content": str(result)}) + logger.info(f"role_agent tool_call: {tool} -> {str(result)[:100]}") + if deliverable or ask_question: + break + continue + + # 无 tool_calls → 文本兜底(deliver/ask 的 JSON) + raw = resp.get("content", "") if isinstance(resp, dict) else str(resp) + act = _parse_agent_action(raw) if act.get('action') == 'deliver': deliverable = act break @@ -647,7 +699,7 @@ async def role_agent_run(project_id, role, agent_id=None, model_name=None): result = await _exec_agent_tool(tool, params, workspace_dir) msgs.append({"role": "assistant", "content": raw}) msgs.append({"role": "user", "content": f"工具 {tool} 结果:\n{result}"}) - logger.info(f"role_agent tool_call: {tool} -> {result[:100]}") + logger.info(f"role_agent tool_call(text): {tool} -> {str(result)[:100]}") else: deliverable = {"result": raw} break @@ -671,6 +723,11 @@ async def role_agent_run(project_id, role, agent_id=None, model_name=None): # 写入代码文件 files_written = [] code_files = deliverable.get("files") or [] + if isinstance(code_files, str): + try: + code_files = json.loads(code_files) + except Exception: + code_files = [] if isinstance(code_files, list): for f in code_files: if isinstance(f, dict) and f.get("path") and f.get("content"):