fix: role_agent_run 切原生 function calling(deliver/ask 作工具 + tool_calls 循环)

This commit is contained in:
p 2026-08-14 11:14:25 +08:00
parent 262d678e2a
commit edd0a946a4

View File

@ -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_callsOpenAI 原生格式)
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"):