- 重构 core/ 底层 (7文件): app/runner/engine/parser/history/safety/display - FC原生调用为主,prompt-JSON保底 - 移除所有工具返回截断 (ai_search/git_tools/cmd_executor/browser_auto/network_tools/sub_agent/tool_search) - 删除无用文件 (旧服务器/WebUI/彩蛋/启动器等21个文件) - 清理 constants.py 死代码 - 版本号更新为 7.0.0
147 lines
5.1 KiB
Python
147 lines
5.1 KiB
Python
"""对话循环 — 唯一循环,无重复"""
|
|
import threading
|
|
from datetime import datetime
|
|
from .types import ToolCall, ToolResult
|
|
from xcli_core.tool_result import ErrorCode
|
|
from .parser import interpret
|
|
|
|
|
|
class Runner:
|
|
def __init__(self, engine, manager, history, safety, display):
|
|
self._engine = engine # EngineAdapter
|
|
self._mgr = manager # UnifiedToolManager
|
|
self._history = history # History
|
|
self._safety = safety # Safety
|
|
self._display = display # Display
|
|
self._cancel = threading.Event()
|
|
self._max_rounds = 50
|
|
self._prompt_builder = None
|
|
|
|
def set_prompt_builder(self, b):
|
|
self._prompt_builder = b
|
|
|
|
def set_engine(self, e):
|
|
self._engine = e
|
|
|
|
def run(self, user_input):
|
|
self._history.add_user(user_input)
|
|
self._cancel.clear()
|
|
return self._loop(user_input)
|
|
|
|
def _loop(self, seed):
|
|
rounds = 0
|
|
current = seed
|
|
fc = self._engine.supports_fc()
|
|
final_text = ""
|
|
|
|
while rounds < self._max_rounds:
|
|
rounds += 1
|
|
if self._cancel.is_set():
|
|
self._display.info("(用户取消)")
|
|
break
|
|
|
|
# 系统提示词
|
|
sp = self._prompt_builder() if self._prompt_builder else ""
|
|
if not fc:
|
|
sp = (sp + "\n" + self._mgr.get_tools_prompt()) if sp else self._mgr.get_tools_prompt()
|
|
|
|
# 生成
|
|
if fc:
|
|
msgs = self._history.render(sp, current, fc=True)
|
|
tools = self._get_tools()
|
|
else:
|
|
msgs = self._history.render(sp, current, fc=False)
|
|
tools = None
|
|
|
|
resp = self._engine.generate(msgs, tools, sp)
|
|
|
|
if resp.thinking:
|
|
self._display.thinking(resp.thinking)
|
|
|
|
# 空响应
|
|
if not resp.text and not resp.tool_calls:
|
|
break
|
|
|
|
# 解释
|
|
plan = interpret(resp, fc)
|
|
|
|
# 显示+历史
|
|
if plan.text:
|
|
self._history.add_assistant(plan.text)
|
|
self._display.text(plan.text)
|
|
|
|
if plan.action == "final":
|
|
final_text = plan.text
|
|
break
|
|
if plan.action == "continue":
|
|
current = None
|
|
continue
|
|
if plan.action == "use_tool":
|
|
results = self._exec_tools(plan.tool_calls, fc)
|
|
if results is None:
|
|
break
|
|
current = None
|
|
continue
|
|
else:
|
|
self._display.info(f"(达到最大轮数{self._max_rounds})")
|
|
|
|
return final_text
|
|
|
|
def _exec_tools(self, calls, fc):
|
|
if self._cancel.is_set():
|
|
for tc in calls:
|
|
self._history.add_cancel(tc.name, tc.args)
|
|
return None
|
|
|
|
results = []
|
|
for tc in calls:
|
|
r = self._exec_one(tc)
|
|
results.append((tc, r))
|
|
|
|
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
if fc:
|
|
self._history.add_assistant("", tool_calls=calls)
|
|
for tc, r in results:
|
|
text = r.data if r.data is not None else (r.error_message or "")
|
|
self._history.add_tool(tc.id, tc.name, str(text or ""))
|
|
else:
|
|
if len(results) == 1:
|
|
tc, r = results[0]
|
|
text = r.data if r.data is not None else (r.error_message or "")
|
|
self._history.add_user(f"工具调用结果 (时间: {now}):\n{text}")
|
|
else:
|
|
parts = [f"工具{i+1}结果:\n{r.data if r.data is not None else (r.error_message or '')}"
|
|
for i, (tc, r) in enumerate(results)]
|
|
self._history.add_user(f"批量工具结果 (时间: {now}):\n" + "\n".join(parts))
|
|
return results
|
|
|
|
def _exec_one(self, tc):
|
|
if self._cancel.is_set():
|
|
return ToolResult.fail("用户取消", code=ErrorCode.NOT_IMPLEMENTED, tool_name=tc.name)
|
|
allowed, reason = self._safety.check(tc)
|
|
if not allowed:
|
|
return ToolResult.fail(f"被拒绝: {reason}", code=ErrorCode.PERMISSION_DENIED, tool_name=tc.name)
|
|
self._display.tool_start(tc)
|
|
r = self._mgr.execute(tc.name, tc.args)
|
|
self._display.tool_result(tc, r)
|
|
return r
|
|
|
|
def _get_tools(self):
|
|
"""生成FC工具定义"""
|
|
defs = []
|
|
for tool in self._mgr.tools:
|
|
mcp = tool.get("mcp_definition")
|
|
if mcp:
|
|
defs.append({"type": "function", "function": {
|
|
"name": mcp.get("name", tool.get("name", "")),
|
|
"description": mcp.get("description", tool.get("description", "")),
|
|
"parameters": mcp.get("inputSchema", {"type": "object", "properties": {}})
|
|
}})
|
|
else:
|
|
defs.append({"type": "function", "function": {
|
|
"name": tool.get("name", ""),
|
|
"description": tool.get("description", ""),
|
|
"parameters": {"type": "object", "properties": {}}
|
|
}})
|
|
return defs
|