- 预设 12 个本机工具并默认启用,装好即是可操作电脑的 Agent、无需总控配置或安装: 文件与文件夹的查看/读取/写入/建目录/移动/删除(默认不覆盖、删目录需 recursive、 拒绝删除系统目录与用户根目录)、用默认程序打开网址或文件、时间与时区换算 (tzdata 已随包内置)、查看进程、屏幕截图、读写剪贴板。 - 工具下发改为通用:任意 HTTP MCP 服务与 stdio 本机命令(Agent 拉起子进程, 容忍 stdout 噪声,命令缺失或进程崩溃会明确回报),支持多工具并存、 定义未变更时复用连接、替换/删除/退出时回收子进程。 - 多轮上下文:task_dispatch.history 作为此前的 user/assistant 文本拼接到本次请求前, OpenAI 兼容、Anthropic、Gemini 分别适配;Agent 侧独立校验与限幅。 - 工具级实时进度以 progress 事件回传;使用预设工具的任务标记为 builtin 执行来源。 - Skill 与历史内容上限与总控对齐,多个已批准 Skill 不再被静默截断。 - 修复 build.ps1 缺 UTF-8 BOM 导致 build.bat 在 PowerShell 5.1 下语法报错。
144 lines
5.1 KiB
Python
144 lines
5.1 KiB
Python
import json
|
|
|
|
from mulit_agent_app.infrastructure.openai_compatible_provider import OpenAICompatibleProvider
|
|
|
|
|
|
class _Credentials:
|
|
def load_llm_api_key(self):
|
|
return "test-key"
|
|
|
|
|
|
class _Response:
|
|
def __enter__(self):
|
|
return self
|
|
|
|
def __exit__(self, *args):
|
|
return False
|
|
|
|
def read(self):
|
|
return json.dumps({"choices": [{"message": {"content": " 模型结果 "}}]}).encode("utf-8")
|
|
|
|
|
|
def test_provider_posts_a_chat_completion(monkeypatch, tmp_path):
|
|
captured = {}
|
|
|
|
def fake_urlopen(request, timeout):
|
|
captured["url"] = request.full_url
|
|
captured["body"] = json.loads(request.data.decode("utf-8"))
|
|
captured["authorization"] = request.get_header("Authorization")
|
|
captured["timeout"] = timeout
|
|
return _Response()
|
|
|
|
monkeypatch.setattr("mulit_agent_app.infrastructure.openai_compatible_provider.urlopen", fake_urlopen)
|
|
|
|
output = OpenAICompatibleProvider("https://api.example.test/v1", "example-model", _Credentials()).generate("请总结")
|
|
|
|
assert output == "模型结果"
|
|
assert captured == {
|
|
"url": "https://api.example.test/v1/chat/completions",
|
|
"body": {"model": "example-model", "messages": [{"role": "user", "content": "请总结"}], "stream": False},
|
|
"authorization": "Bearer test-key",
|
|
"timeout": 90,
|
|
}
|
|
|
|
|
|
def test_provider_sends_the_conversation_history_before_the_new_turn(monkeypatch):
|
|
captured = {}
|
|
|
|
def fake_urlopen(request, timeout):
|
|
captured["body"] = json.loads(request.data.decode("utf-8"))
|
|
return _Response()
|
|
|
|
monkeypatch.setattr("mulit_agent_app.infrastructure.openai_compatible_provider.urlopen", fake_urlopen)
|
|
|
|
output = OpenAICompatibleProvider("https://api.example.test/v1", "example-model", _Credentials()).generate(
|
|
"把它复制到 D:\\target",
|
|
history=(("user", "下载到 D:\\demo"), ("assistant", "已下载到 D:\\demo\\a.zip")),
|
|
)
|
|
|
|
assert output == "模型结果"
|
|
assert captured["body"]["messages"] == [
|
|
{"role": "user", "content": "下载到 D:\\demo"},
|
|
{"role": "assistant", "content": "已下载到 D:\\demo\\a.zip"},
|
|
{"role": "user", "content": "把它复制到 D:\\target"},
|
|
]
|
|
|
|
|
|
def test_provider_without_history_still_sends_a_single_user_message(monkeypatch):
|
|
captured = {}
|
|
|
|
def fake_urlopen(request, timeout):
|
|
captured["body"] = json.loads(request.data.decode("utf-8"))
|
|
return _Response()
|
|
|
|
monkeypatch.setattr("mulit_agent_app.infrastructure.openai_compatible_provider.urlopen", fake_urlopen)
|
|
|
|
OpenAICompatibleProvider("https://api.example.test/v1", "example-model", _Credentials()).generate("请总结")
|
|
|
|
assert captured["body"]["messages"] == [{"role": "user", "content": "请总结"}]
|
|
|
|
|
|
def test_provider_removes_reasoning_tags_before_returning_result(monkeypatch):
|
|
class ThinkingResponse(_Response):
|
|
def read(self):
|
|
return json.dumps({"choices": [{"message": {"content": "<think>内部推理</think>最终答案"}}]}).encode("utf-8")
|
|
|
|
monkeypatch.setattr(
|
|
"mulit_agent_app.infrastructure.openai_compatible_provider.urlopen", lambda request, timeout: ThinkingResponse()
|
|
)
|
|
|
|
output = OpenAICompatibleProvider("https://api.example.test/v1", "example-model", _Credentials()).generate("请总结")
|
|
|
|
assert output == "最终答案"
|
|
|
|
|
|
def test_provider_executes_confirmed_local_tool_call_before_returning_answer(monkeypatch):
|
|
responses = [
|
|
{
|
|
"choices": [
|
|
{
|
|
"message": {
|
|
"content": None,
|
|
"tool_calls": [
|
|
{
|
|
"id": "call-1",
|
|
"type": "function",
|
|
"function": {"name": "run_powershell", "arguments": '{"command":"Get-Date"}'},
|
|
}
|
|
],
|
|
}
|
|
}
|
|
]
|
|
},
|
|
{"choices": [{"message": {"content": "已实际完成本机操作。"}}]},
|
|
]
|
|
captured: list[dict[str, object]] = []
|
|
|
|
class Response:
|
|
def __init__(self, payload):
|
|
self.payload = payload
|
|
|
|
def __enter__(self):
|
|
return self
|
|
|
|
def __exit__(self, *args):
|
|
return False
|
|
|
|
def read(self):
|
|
return json.dumps(self.payload).encode("utf-8")
|
|
|
|
def fake_urlopen(request, timeout):
|
|
captured.append(json.loads(request.data.decode("utf-8")))
|
|
return Response(responses.pop(0))
|
|
|
|
monkeypatch.setattr("mulit_agent_app.infrastructure.openai_compatible_provider.urlopen", fake_urlopen)
|
|
commands: list[str] = []
|
|
output = OpenAICompatibleProvider("https://api.example.test/v1", "example-model", _Credentials()).generate(
|
|
"在桌面创建文件夹", tool_executor=lambda command: commands.append(command) or "已创建。"
|
|
)
|
|
|
|
assert output == "已实际完成本机操作。"
|
|
assert commands == ["Get-Date"]
|
|
assert captured[0]["tools"]
|
|
assert captured[1]["messages"][-1] == {"role": "tool", "tool_call_id": "call-1", "content": "已创建。"}
|