refactor: 重构 Tool Result 契约,移除 ui_hints 统一使用 result 字段
- ToolAgentOutput 移除 result_summary 和 ui_hints,统一使用 result 字段 - 日历/用户查找工具移除 ui_hints 输出,改为机器可读的结构化结果 - Agent History 移除 tool 消息的 ui_hints 处理逻辑 - App 版本检查改为 manifest.json 方式,支持多渠道发布 - 更新 settings 配置和测试用例适配新结构
This commit is contained in:
@@ -311,3 +311,74 @@ async def test_enqueue_run_rejects_too_many_attachments(monkeypatch) -> None:
|
||||
|
||||
assert exc_info.value.status_code == 422
|
||||
assert exc_info.value.detail == "Too many attachments"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_history_snapshot_filters_out_tool_messages() -> None:
|
||||
class _HistoryRepository(_FakeRepository):
|
||||
async def get_history_day(
|
||||
self, *, session_id: str, before: date | None
|
||||
) -> dict[str, object] | None:
|
||||
del session_id, before
|
||||
return {
|
||||
"day": "2026-03-17",
|
||||
"hasMore": False,
|
||||
"messages": [
|
||||
{
|
||||
"id": "00000000-0000-0000-0000-000000000111",
|
||||
"seq": 1,
|
||||
"role": "user",
|
||||
"content": "帮我查一下今天日程",
|
||||
"metadata": None,
|
||||
"timestamp": "2026-03-17T09:00:00Z",
|
||||
},
|
||||
{
|
||||
"id": "00000000-0000-0000-0000-000000000112",
|
||||
"seq": 2,
|
||||
"role": "tool",
|
||||
"content": "已获取日程列表,共 3 条",
|
||||
"metadata": {
|
||||
"run_id": "run-1",
|
||||
"tool_agent_output": {
|
||||
"tool_name": "calendar_read",
|
||||
"tool_call_id": "call-1",
|
||||
"status": "success",
|
||||
"result": "status=success total=3 returned=3",
|
||||
},
|
||||
},
|
||||
"timestamp": "2026-03-17T09:00:01Z",
|
||||
},
|
||||
{
|
||||
"id": "00000000-0000-0000-0000-000000000113",
|
||||
"seq": 3,
|
||||
"role": "assistant",
|
||||
"content": "今天共有 3 条日程。",
|
||||
"metadata": {
|
||||
"run_id": "run-1",
|
||||
"worker_agent_output": {
|
||||
"status": "success",
|
||||
"answer": "今天共有 3 条日程。",
|
||||
"key_points": [],
|
||||
"result_type": "summary",
|
||||
"suggested_actions": [],
|
||||
},
|
||||
},
|
||||
"timestamp": "2026-03-17T09:00:02Z",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
service = AgentService(
|
||||
repository=_HistoryRepository(),
|
||||
queue=_FakeQueue(),
|
||||
stream=_FakeStream(),
|
||||
attachment_storage=_FakeAttachmentStorage(),
|
||||
)
|
||||
|
||||
snapshot = await service.get_history_snapshot(
|
||||
thread_id="00000000-0000-0000-0000-000000000001",
|
||||
before=None,
|
||||
current_user=_user(),
|
||||
)
|
||||
|
||||
assert [message.role for message in snapshot.messages] == ["user", "assistant"]
|
||||
|
||||
@@ -16,21 +16,18 @@ class _FakeMessage:
|
||||
self.timestamp = datetime.now(timezone.utc)
|
||||
|
||||
|
||||
def test_convert_message_to_history_uses_ui_schema_key_for_tool_message() -> None:
|
||||
def test_convert_message_to_history_does_not_attach_ui_schema_for_tool_message() -> (
|
||||
None
|
||||
):
|
||||
message = _FakeMessage(
|
||||
role="tool",
|
||||
metadata={
|
||||
"tool_agent_output": {
|
||||
"ui_schema": {"version": "2.0", "root": {"type": "stack"}}
|
||||
}
|
||||
},
|
||||
metadata={"tool_agent_output": {"result": "done"}},
|
||||
)
|
||||
|
||||
result = convert_message_to_history(message) # type: ignore[arg-type]
|
||||
|
||||
assert "ui_schema" in result
|
||||
assert "ui_schema" not in result
|
||||
assert "uiSchema" not in result
|
||||
assert result["ui_schema"] == {"version": "2.0", "root": {"type": "stack"}}
|
||||
|
||||
|
||||
def test_convert_message_to_history_uses_ui_schema_key_for_assistant_message() -> None:
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
from v1.app import router
|
||||
|
||||
|
||||
def _write_manifest(tmp_path, data: dict):
|
||||
manifest_file = tmp_path / "manifest.json"
|
||||
manifest_file.write_text(json.dumps(data), encoding="utf-8")
|
||||
return manifest_file
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_check_updates_returns_none_when_manifest_missing(monkeypatch) -> None:
|
||||
monkeypatch.setattr(
|
||||
router, "_manifest_file_path", lambda: router.PROJECT_ROOT / "missing.json"
|
||||
)
|
||||
|
||||
result = await router.check_updates(
|
||||
current_version_code=2,
|
||||
current_version_name="0.1.1",
|
||||
platform="android",
|
||||
channel="release",
|
||||
)
|
||||
|
||||
assert result.has_update is False
|
||||
assert result.update_type == "none"
|
||||
assert result.latest_version_name == "0.1.1"
|
||||
assert result.latest_version_code == 2
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_check_updates_returns_optional_update(monkeypatch, tmp_path) -> None:
|
||||
manifest_file = _write_manifest(
|
||||
tmp_path,
|
||||
{
|
||||
"releases": [
|
||||
{
|
||||
"platform": "android",
|
||||
"channel": "release",
|
||||
"version_name": "0.1.2",
|
||||
"version_code": 3,
|
||||
"min_supported_version_code": 2,
|
||||
"file_name": "social-app-android-v0.1.2+3-release.apk",
|
||||
"release_notes": "优化体验",
|
||||
}
|
||||
]
|
||||
},
|
||||
)
|
||||
monkeypatch.setattr(router, "_manifest_file_path", lambda: manifest_file)
|
||||
monkeypatch.setattr(
|
||||
router.config.app_version,
|
||||
"download_base_url",
|
||||
"https://download.example.com",
|
||||
raising=False,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
router.config.app_version,
|
||||
"release_path_prefix",
|
||||
"releases",
|
||||
raising=False,
|
||||
)
|
||||
|
||||
result = await router.check_updates(
|
||||
current_version_code=2,
|
||||
current_version_name="0.1.1",
|
||||
platform="android",
|
||||
channel="release",
|
||||
)
|
||||
|
||||
assert result.has_update is True
|
||||
assert result.update_type == "optional"
|
||||
assert result.latest_version_name == "0.1.2"
|
||||
assert result.latest_version_code == 3
|
||||
assert result.min_supported_version_code == 2
|
||||
assert (
|
||||
result.download_url
|
||||
== "https://download.example.com/releases/social-app-android-v0.1.2+3-release.apk"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_check_updates_returns_required_update(monkeypatch, tmp_path) -> None:
|
||||
manifest_file = _write_manifest(
|
||||
tmp_path,
|
||||
{
|
||||
"releases": [
|
||||
{
|
||||
"platform": "android",
|
||||
"channel": "release",
|
||||
"version_name": "0.1.3",
|
||||
"version_code": 5,
|
||||
"min_supported_version_code": 4,
|
||||
"file_name": "social-app-android-v0.1.3+5-release.apk",
|
||||
}
|
||||
]
|
||||
},
|
||||
)
|
||||
monkeypatch.setattr(router, "_manifest_file_path", lambda: manifest_file)
|
||||
monkeypatch.setattr(
|
||||
router.config.app_version,
|
||||
"download_base_url",
|
||||
"https://download.example.com",
|
||||
raising=False,
|
||||
)
|
||||
|
||||
result = await router.check_updates(
|
||||
current_version_code=3,
|
||||
current_version_name="0.1.1",
|
||||
platform="android",
|
||||
channel="release",
|
||||
)
|
||||
|
||||
assert result.has_update is True
|
||||
assert result.update_type == "required"
|
||||
assert result.min_supported_version_code == 4
|
||||
Reference in New Issue
Block a user