- 重构 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
171 lines
7.6 KiB
Python
171 lines
7.6 KiB
Python
"""引擎适配 — FC为主,prompt-JSON保底"""
|
|
import json, re, uuid
|
|
from .types import EngineResponse, ToolCall
|
|
|
|
|
|
class EngineAdapter:
|
|
def __init__(self, engine):
|
|
self._engine = engine
|
|
self._fc = hasattr(engine, 'generate_with_tools')
|
|
|
|
def supports_fc(self):
|
|
return self._fc
|
|
|
|
def name(self):
|
|
return getattr(self._engine, 'name', 'unknown')
|
|
|
|
def generate(self, messages, tools=None, system_prompt=None):
|
|
if self._fc and tools:
|
|
return self._gen_fc(messages, tools, system_prompt)
|
|
return self._gen_legacy(messages, system_prompt)
|
|
|
|
def _gen_fc(self, messages, tools, system_prompt):
|
|
try:
|
|
raw = self._engine.generate_with_tools(messages, tools, system_prompt=system_prompt)
|
|
text = ""
|
|
thinking = ""
|
|
tool_calls = None
|
|
if isinstance(raw, dict):
|
|
text = raw.get("content", "") or ""
|
|
thinking = raw.get("reasoning_content", "") or ""
|
|
tc_list = raw.get("tool_calls", []) or []
|
|
if tc_list:
|
|
tool_calls = []
|
|
for tc in tc_list:
|
|
func = tc.get("function", {})
|
|
cid = tc.get("id", str(uuid.uuid4())[:8])
|
|
name = func.get("name", "")
|
|
args_str = func.get("arguments", "{}")
|
|
arguments = None
|
|
try:
|
|
if isinstance(args_str, str) and args_str:
|
|
arguments = json.loads(args_str)
|
|
except json.JSONDecodeError:
|
|
pass
|
|
args = self._to_legacy(name, arguments) if arguments else (args_str or "")
|
|
tool_calls.append(ToolCall(id=cid, name=name, args=args,
|
|
arguments=arguments, raw=tc))
|
|
return EngineResponse(text=text, thinking=thinking, tool_calls=tool_calls, raw=raw)
|
|
except Exception as e:
|
|
return EngineResponse(text=f"引擎调用失败: {e}")
|
|
|
|
def _gen_legacy(self, messages, system_prompt):
|
|
user_input = ""
|
|
for msg in reversed(messages):
|
|
if msg.get("role") == "user":
|
|
user_input = msg.get("content", "")
|
|
break
|
|
try:
|
|
resp = self._engine.generate_response(user_input, system_prompt=system_prompt)
|
|
except Exception as e:
|
|
return EngineResponse(text=f"引擎调用失败: {e}")
|
|
if not resp:
|
|
return EngineResponse()
|
|
text, thinking = self._strip_thinking(resp)
|
|
return EngineResponse(text=text, thinking=thinking, raw=resp)
|
|
|
|
def _strip_thinking(self, text):
|
|
if not text:
|
|
return "", ""
|
|
thinking = ""
|
|
if "<thinking>" in text and "</thinking>" in text:
|
|
m = re.search(r'<thinking>(.*?)</thinking>(.*)', text, re.DOTALL)
|
|
if m:
|
|
thinking = m.group(1).strip()
|
|
text = m.group(2).strip()
|
|
start = getattr(self._engine, 'thinking_start_marker', None)
|
|
end = getattr(self._engine, 'thinking_end_marker', None)
|
|
if start and end and start in text and end in text:
|
|
parts = text.split(start)
|
|
if len(parts) >= 2:
|
|
rest = parts[1]
|
|
if end in rest:
|
|
tp, rp = rest.split(end, 1)
|
|
thinking = (thinking + "\n" + tp.strip()).strip() if thinking else tp.strip()
|
|
text = (parts[0] + rp).strip()
|
|
return text, thinking
|
|
|
|
def _to_legacy(self, tool_name, args):
|
|
"""FC dict参数 → 旧插件字符串参数"""
|
|
if not args:
|
|
return ""
|
|
if tool_name == "ai_search":
|
|
url = args.get("url", "")
|
|
q = args.get("query", args.get("search", ""))
|
|
if url: return f"url {url}"
|
|
if q: return f"search {q}"
|
|
if tool_name == "network_tools":
|
|
op = args.get("operation", args.get("op", "get"))
|
|
t = args.get("target", args.get("url", args.get("host", "")))
|
|
if op and t: return f"{op} {t}"
|
|
if tool_name == "code_editor":
|
|
op = args.get("operation", args.get("action", ""))
|
|
fp = args.get("file", args.get("path", args.get("filename", "")))
|
|
ct = args.get("content", args.get("text", args.get("code", "")))
|
|
old = args.get("old", args.get("old_code", args.get("find", "")))
|
|
new = args.get("new", args.get("new_code", args.get("replace", "")))
|
|
ln = args.get("line", args.get("line_number", ""))
|
|
sl = args.get("start", args.get("start_line", ""))
|
|
el = args.get("end", args.get("end_line", ""))
|
|
pat = args.get("pattern", args.get("file_pattern", ""))
|
|
d = args.get("directory", args.get("dir", "."))
|
|
if op == "create" and fp and ct: return f"create {fp}\n{ct}"
|
|
if op == "write" and fp and ct: return f"write {fp}\n{ct}"
|
|
if op == "append" and fp and ct: return f"append {fp}\n{ct}"
|
|
if op in ("read_range", "read"): return f"read_range {fp} {sl or 1} {el or 50}"
|
|
if op == "edit": return f"edit {fp}\n{old}\n<<<>>>\n{new}"
|
|
if op == "insert": return f"insert {fp} {ln}\n{ct}"
|
|
if op in ("find", "search"): return f"find {pat} {d}"
|
|
if op == "syntax_check": return f"syntax_check {fp}"
|
|
return json.dumps(args, ensure_ascii=False)
|
|
if tool_name == "code_search":
|
|
op = args.get("operation", "find")
|
|
pat = args.get("pattern", args.get("query", args.get("search", "")))
|
|
d = args.get("directory", args.get("dir", "."))
|
|
return f"{op} {pat} {d}"
|
|
if tool_name == "git_tools":
|
|
op = args.get("operation", "status")
|
|
a = args.get("args", "")
|
|
return f"{op} {a}" if a else op
|
|
if tool_name == "cmd_executor":
|
|
return args.get("command", args.get("cmd", args.get("shell", "")))
|
|
if tool_name == "file_manager":
|
|
op = args.get("operation", args.get("action", "list"))
|
|
p = args.get("path", args.get("file", "."))
|
|
ct = args.get("content", args.get("text", ""))
|
|
if op in ("read", "list", "info"): return f"{op} {p}"
|
|
if op == "write" and ct: return f'write {p} -c "{ct}"'
|
|
if op == "append" and ct: return f'append {p} -c "{ct}"'
|
|
if op and p: return f"{op} {p}"
|
|
if tool_name == "browser_auto":
|
|
op = args.get("operation", "")
|
|
url = args.get("url", "")
|
|
if op == "open" and url: return f"open {url}"
|
|
if tool_name == "auto_engineer":
|
|
op = args.get("operation", "")
|
|
t = args.get("target", args.get("path", ""))
|
|
if op and t: return f"{op} {t}"
|
|
return op
|
|
if tool_name == "sub_agent":
|
|
op = args.get("operation", "list")
|
|
if op == "create":
|
|
task = args.get("task", args.get("prompt", ""))
|
|
return f"create {task}"
|
|
return op
|
|
if tool_name in ("task_manager", "scheduler"):
|
|
return args.get("operation", "list")
|
|
# 通用
|
|
if "operation" in args:
|
|
op = args["operation"]
|
|
rest = " ".join(str(v) for k, v in args.items() if k != "operation")
|
|
return f"{op} {rest}".strip()
|
|
return " ".join(str(v) for v in args.values())
|
|
|
|
def handle_command(self, cmd):
|
|
if hasattr(self._engine, 'handle_command'):
|
|
return self._engine.handle_command(cmd)
|
|
return False
|
|
|
|
def get_raw(self):
|
|
return self._engine
|