refactor: use a single agent model path
This commit is contained in:
+14
-14
@@ -201,7 +201,7 @@ async def test_tool_call_batch_is_bounded(settings) -> None:
|
||||
|
||||
try:
|
||||
answer = await loop.run(
|
||||
spec=get_model_spec("work-medium"),
|
||||
spec=get_model_spec("terra"),
|
||||
messages=[{"role": "user", "content": "检查当前状态"}],
|
||||
identity=UserIdentity("u1", "u1@example.test", "U1", "user"),
|
||||
raw_user_jwt="jwt",
|
||||
@@ -245,7 +245,7 @@ async def test_read_only_tool_calls_run_in_parallel(settings) -> None:
|
||||
|
||||
try:
|
||||
answer = await loop.run(
|
||||
spec=get_model_spec("work-light"),
|
||||
spec=get_model_spec("luna"),
|
||||
messages=[{"role": "user", "content": "inspect both"}],
|
||||
identity=UserIdentity("u1", "", "", "user"),
|
||||
raw_user_jwt="jwt",
|
||||
@@ -314,7 +314,7 @@ async def test_mutating_tool_calls_are_serialized(settings) -> None:
|
||||
|
||||
try:
|
||||
await loop.run(
|
||||
spec=get_model_spec("work-light"),
|
||||
spec=get_model_spec("luna"),
|
||||
messages=[{"role": "user", "content": "write both"}],
|
||||
identity=UserIdentity("u1", "", "", "user"),
|
||||
raw_user_jwt="jwt",
|
||||
@@ -374,7 +374,7 @@ async def test_artifact_completion_is_rejected_until_written_and_verified(settin
|
||||
|
||||
try:
|
||||
answer = await AgentLoop(provider, registry, store, max_tool_output_chars=10_000).run(
|
||||
spec=get_model_spec("work-light"),
|
||||
spec=get_model_spec("luna"),
|
||||
messages=[{"role": "user", "content": "写一个报告"}],
|
||||
identity=UserIdentity("u1", "", "", "user"),
|
||||
raw_user_jwt="jwt",
|
||||
@@ -469,7 +469,7 @@ async def test_script_and_report_completion_requires_both_files(settings) -> Non
|
||||
store,
|
||||
max_tool_output_chars=10_000,
|
||||
).run(
|
||||
spec=get_model_spec("work-light"),
|
||||
spec=get_model_spec("luna"),
|
||||
messages=[{"role": "user", "content": "写一个脚本并给一个报告"}],
|
||||
identity=UserIdentity("u1", "", "", "user"),
|
||||
raw_user_jwt="jwt",
|
||||
@@ -575,7 +575,7 @@ async def test_failed_execution_must_be_repaired_before_completion(settings) ->
|
||||
|
||||
try:
|
||||
answer = await AgentLoop(provider, registry, store, max_tool_output_chars=10_000).run(
|
||||
spec=get_model_spec("work-light"),
|
||||
spec=get_model_spec("luna"),
|
||||
messages=[{"role": "user", "content": "写脚本对比排序算法"}],
|
||||
identity=UserIdentity("u1", "", "", "user"),
|
||||
raw_user_jwt="jwt",
|
||||
@@ -662,7 +662,7 @@ async def test_unchanged_failed_command_is_blocked_until_a_repair(settings) -> N
|
||||
|
||||
try:
|
||||
answer = await AgentLoop(provider, registry, store, max_tool_output_chars=10_000).run(
|
||||
spec=get_model_spec("work-light"),
|
||||
spec=get_model_spec("luna"),
|
||||
messages=[{"role": "user", "content": "写脚本并运行"}],
|
||||
identity=UserIdentity("u1", "", "", "user"),
|
||||
raw_user_jwt="jwt",
|
||||
@@ -707,7 +707,7 @@ async def test_duplicate_commands_in_one_model_response_execute_once(settings) -
|
||||
|
||||
try:
|
||||
answer = await AgentLoop(provider, registry, store, max_tool_output_chars=10_000).run(
|
||||
spec=get_model_spec("work-light"),
|
||||
spec=get_model_spec("luna"),
|
||||
messages=[{"role": "user", "content": "写脚本并运行"}],
|
||||
identity=UserIdentity("u1", "", "", "user"),
|
||||
raw_user_jwt="jwt",
|
||||
@@ -753,7 +753,7 @@ async def test_duplicate_identical_writes_execute_once(settings) -> None:
|
||||
|
||||
try:
|
||||
answer = await AgentLoop(provider, registry, store, max_tool_output_chars=10_000).run(
|
||||
spec=get_model_spec("work-light"),
|
||||
spec=get_model_spec("luna"),
|
||||
messages=[{"role": "user", "content": "写脚本并运行"}],
|
||||
identity=UserIdentity("u1", "", "", "user"),
|
||||
raw_user_jwt="jwt",
|
||||
@@ -815,7 +815,7 @@ async def test_script_delivery_requires_successful_execution(settings) -> None:
|
||||
|
||||
try:
|
||||
answer = await AgentLoop(provider, registry, store, max_tool_output_chars=10_000).run(
|
||||
spec=get_model_spec("work-light"),
|
||||
spec=get_model_spec("luna"),
|
||||
messages=[{"role": "user", "content": "写一个 Python 脚本"}],
|
||||
identity=UserIdentity("u1", "", "", "user"),
|
||||
raw_user_jwt="jwt",
|
||||
@@ -846,7 +846,7 @@ async def test_rejected_checkpoint_does_not_spin_without_tool_calls(settings) ->
|
||||
store,
|
||||
max_tool_output_chars=10_000,
|
||||
).run(
|
||||
spec=get_model_spec("work-light"),
|
||||
spec=get_model_spec("luna"),
|
||||
messages=[{"role": "user", "content": "写一个报告"}],
|
||||
identity=UserIdentity("u1", "", "", "user"),
|
||||
raw_user_jwt="jwt",
|
||||
@@ -888,7 +888,7 @@ async def test_bare_interactive_exec_is_blocked_before_registry(settings) -> Non
|
||||
store,
|
||||
max_tool_output_chars=10_000,
|
||||
).run(
|
||||
spec=get_model_spec("work-light"),
|
||||
spec=get_model_spec("luna"),
|
||||
messages=[{"role": "user", "content": "你好"}],
|
||||
identity=UserIdentity("u1", "", "", "user"),
|
||||
raw_user_jwt="jwt",
|
||||
@@ -926,7 +926,7 @@ async def test_unchanged_invalid_python_write_is_not_revalidated(settings) -> No
|
||||
|
||||
try:
|
||||
answer = await AgentLoop(provider, registry, store, max_tool_output_chars=10_000).run(
|
||||
spec=get_model_spec("work-light"),
|
||||
spec=get_model_spec("luna"),
|
||||
messages=[{"role": "user", "content": "你好"}],
|
||||
identity=UserIdentity("u1", "", "", "user"),
|
||||
raw_user_jwt="jwt",
|
||||
@@ -999,7 +999,7 @@ async def test_extreme_tier_preserves_reasoning_state_across_tool_turns(settings
|
||||
|
||||
try:
|
||||
answer = await AgentLoop(provider, registry, store, max_tool_output_chars=10_000).run(
|
||||
spec=get_model_spec("work-extreme"),
|
||||
spec=get_model_spec("deepseek-v4-pro"),
|
||||
messages=[{"role": "user", "content": "读取 notes.txt 并检查内容"}],
|
||||
identity=UserIdentity("u1", "", "", "user"),
|
||||
raw_user_jwt="jwt",
|
||||
|
||||
+8
-12
@@ -1,20 +1,16 @@
|
||||
from agent_platform.bootstrap import _model_payload
|
||||
from agent_platform.bootstrap import LEGACY_MODEL_IDS, _model_payload
|
||||
|
||||
|
||||
def test_bootstrap_models_override_provider_ids_and_are_public() -> None:
|
||||
def test_bootstrap_models_are_agent_only_and_public() -> None:
|
||||
models = _model_payload()
|
||||
assert len(models) == 8
|
||||
assert {model["id"] for model in models} == {"luna", "terra", "sol", "deepseek-v4-pro"}
|
||||
assert all(model["base_model_id"] is None for model in models)
|
||||
assert all(model["params"] == {} for model in models)
|
||||
assert all(model["meta"]["toolIds"] == [] for model in models)
|
||||
assert all(model["meta"]["tags"] == [{"name": "Agent"}] for model in models)
|
||||
assert all(
|
||||
model["access_grants"] == [{"principal_type": "user", "principal_id": "*", "permission": "read"}]
|
||||
for model in models
|
||||
)
|
||||
|
||||
chat = next(model for model in models if model["id"] == "chat-medium")
|
||||
assert chat["params"]["function_calling"] == "native"
|
||||
assert chat["meta"]["toolIds"] == ["server:workspace"]
|
||||
assert chat["meta"]["capabilities"]["builtin_tools"] is False
|
||||
|
||||
work = next(model for model in models if model["id"] == "work-medium")
|
||||
assert work["params"] == {}
|
||||
assert work["meta"]["toolIds"] == []
|
||||
assert "chat-medium" in LEGACY_MODEL_IDS
|
||||
assert "work-medium" in LEGACY_MODEL_IDS
|
||||
|
||||
+24
-20
@@ -1,23 +1,27 @@
|
||||
from agent_platform.models import MODEL_SPECS, get_model_spec, openai_model_list
|
||||
|
||||
|
||||
def test_fixed_public_model_matrix() -> None:
|
||||
assert set(MODEL_SPECS) == {
|
||||
"chat-light",
|
||||
"chat-medium",
|
||||
"chat-high",
|
||||
"chat-extreme",
|
||||
"work-light",
|
||||
"work-medium",
|
||||
"work-high",
|
||||
"work-extreme",
|
||||
}
|
||||
assert get_model_spec("chat-light").provider_model == "ChatGPT-5.6:Luna"
|
||||
assert get_model_spec("work-medium").provider_model == "ChatGPT-5.6:Terra"
|
||||
assert get_model_spec("work-high").provider_model == "ChatGPT-5.6:Sol"
|
||||
extreme = get_model_spec("work-extreme")
|
||||
assert extreme.provider == "deepseek"
|
||||
assert extreme.provider_model == "deepseek-v4-pro"
|
||||
assert extreme.thinking_enabled is True
|
||||
assert extreme.reasoning_effort == "max"
|
||||
assert {item["id"] for item in openai_model_list()["data"]} == set(MODEL_SPECS)
|
||||
def test_fixed_public_model_catalog_separates_model_and_thinking() -> None:
|
||||
assert set(MODEL_SPECS) == {"luna", "terra", "sol", "deepseek-v4-pro"}
|
||||
assert get_model_spec("luna").provider_model == "ChatGPT-5.6:Luna"
|
||||
assert get_model_spec("terra").provider_model == "ChatGPT-5.6:Terra"
|
||||
assert get_model_spec("sol").provider_model == "ChatGPT-5.6:Sol"
|
||||
|
||||
for model_id in ("luna", "terra", "sol"):
|
||||
spec = get_model_spec(model_id)
|
||||
assert spec.thinking_enabled is True
|
||||
assert spec.thinking_adjustable is False
|
||||
assert spec.thinking_label == "开启(不分档)"
|
||||
assert spec.reasoning_effort is None
|
||||
|
||||
deepseek = get_model_spec("deepseek-v4-pro")
|
||||
assert deepseek.provider == "deepseek"
|
||||
assert deepseek.provider_model == "deepseek-v4-pro"
|
||||
assert deepseek.thinking_enabled is True
|
||||
assert deepseek.thinking_adjustable is False
|
||||
assert deepseek.thinking_label == "极高"
|
||||
assert deepseek.reasoning_effort == "max"
|
||||
|
||||
public_models = openai_model_list()["data"]
|
||||
assert {item["id"] for item in public_models} == set(MODEL_SPECS)
|
||||
assert all("k1412" in item for item in public_models)
|
||||
|
||||
+44
-97
@@ -18,7 +18,7 @@ class FakeModelProvider:
|
||||
async def complete(self, **kwargs):
|
||||
self.completed.append(kwargs)
|
||||
return {
|
||||
"choices": [{"message": {"role": "assistant", "content": "work complete"}, "finish_reason": "stop"}],
|
||||
"choices": [{"message": {"role": "assistant", "content": "agent complete"}, "finish_reason": "stop"}],
|
||||
"usage": {"total_tokens": 10},
|
||||
}
|
||||
|
||||
@@ -26,25 +26,13 @@ class FakeModelProvider:
|
||||
self.forwarded.append(payload)
|
||||
self.forward_options.append(kwargs)
|
||||
request = httpx.Request("POST", "https://model.example.test/v1/chat/completions")
|
||||
if payload.get("stream"):
|
||||
return httpx.Response(
|
||||
200,
|
||||
request=request,
|
||||
headers={"content-type": "text/event-stream"},
|
||||
content=(
|
||||
'data: {"model":"ChatGPT-5.6:Luna","choices":[{"index":0,"delta":'
|
||||
'{"tool_calls":[{"index":0,"id":"call-1","type":"function","function":'
|
||||
'{"name":"read_file","arguments":"{}"}}]},"finish_reason":null}]}\n\n'
|
||||
"data: [DONE]\n\n"
|
||||
),
|
||||
)
|
||||
return httpx.Response(
|
||||
200,
|
||||
request=request,
|
||||
headers={"content-type": "application/json"},
|
||||
json={
|
||||
"model": payload["model"],
|
||||
"choices": [{"message": {"role": "assistant", "content": "chat reply"}, "finish_reason": "stop"}],
|
||||
"choices": [{"message": {"role": "assistant", "content": "background reply"}}],
|
||||
},
|
||||
)
|
||||
|
||||
@@ -71,135 +59,94 @@ def headers(settings, identity_jwt, chat_id="chat-1"):
|
||||
}
|
||||
|
||||
|
||||
def test_model_list_requires_internal_key(settings) -> None:
|
||||
app = create_app(
|
||||
def _app(settings, provider=None):
|
||||
return create_app(
|
||||
settings,
|
||||
store=RuntimeStore(settings.database_url),
|
||||
provider=FakeModelProvider(),
|
||||
provider=provider or FakeModelProvider(),
|
||||
tools=FakeTools(),
|
||||
)
|
||||
with TestClient(app) as client:
|
||||
|
||||
|
||||
def test_model_list_requires_internal_key_and_exposes_four_models(settings) -> None:
|
||||
with TestClient(_app(settings)) as client:
|
||||
assert client.get("/v1/models").status_code == 401
|
||||
response = client.get(
|
||||
"/v1/models",
|
||||
headers={"Authorization": f"Bearer {settings.internal_provider_key}"},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert len(response.json()["data"]) == 8
|
||||
|
||||
|
||||
def test_chat_maps_provider_model_and_work_upgrade_is_one_way(settings, identity_jwt) -> None:
|
||||
provider = FakeModelProvider()
|
||||
app = create_app(settings, store=RuntimeStore(settings.database_url), provider=provider, tools=FakeTools())
|
||||
with TestClient(app) as client:
|
||||
chat = client.post(
|
||||
"/v1/chat/completions",
|
||||
headers=headers(settings, identity_jwt),
|
||||
json={"model": "chat-light", "messages": [{"role": "user", "content": "hello"}]},
|
||||
)
|
||||
assert chat.status_code == 200
|
||||
assert provider.forwarded[0]["model"] == "ChatGPT-5.6:Luna"
|
||||
assert chat.json()["model"] == "chat-light"
|
||||
|
||||
extreme_chat = client.post(
|
||||
"/v1/chat/completions",
|
||||
headers=headers(settings, identity_jwt, "extreme-chat"),
|
||||
json={"model": "chat-extreme", "messages": [{"role": "user", "content": "think"}]},
|
||||
)
|
||||
assert extreme_chat.status_code == 200
|
||||
assert provider.forwarded[-1]["model"] == "deepseek-v4-pro"
|
||||
assert provider.forward_options[-1] == {
|
||||
"provider": "deepseek",
|
||||
"thinking_enabled": True,
|
||||
"reasoning_effort": "max",
|
||||
"max_output_tokens": 16_384,
|
||||
assert {item["id"] for item in response.json()["data"]} == {
|
||||
"luna",
|
||||
"terra",
|
||||
"sol",
|
||||
"deepseek-v4-pro",
|
||||
}
|
||||
|
||||
work = client.post(
|
||||
"/v1/chat/completions",
|
||||
headers=headers(settings, identity_jwt),
|
||||
json={"model": "work-high", "messages": [{"role": "user", "content": "do it"}]},
|
||||
)
|
||||
assert work.status_code == 200
|
||||
assert provider.completed[-1]["model"] == "ChatGPT-5.6:Sol"
|
||||
|
||||
extreme_work = client.post(
|
||||
def test_every_user_model_runs_the_custom_agent_loop(settings, identity_jwt) -> None:
|
||||
provider = FakeModelProvider()
|
||||
with TestClient(_app(settings, provider)) as client:
|
||||
luna = client.post(
|
||||
"/v1/chat/completions",
|
||||
headers=headers(settings, identity_jwt, "extreme-work"),
|
||||
json={"model": "work-extreme", "messages": [{"role": "user", "content": "do it carefully"}]},
|
||||
headers=headers(settings, identity_jwt, "luna-run"),
|
||||
json={"model": "luna", "messages": [{"role": "user", "content": "hello"}]},
|
||||
)
|
||||
assert extreme_work.status_code == 200
|
||||
assert luna.status_code == 200
|
||||
assert luna.json()["model"] == "luna"
|
||||
assert provider.completed[-1]["model"] == "ChatGPT-5.6:Luna"
|
||||
assert provider.completed[-1]["thinking_enabled"] is True
|
||||
assert provider.forwarded == []
|
||||
|
||||
deepseek = client.post(
|
||||
"/v1/chat/completions",
|
||||
headers=headers(settings, identity_jwt, "deepseek-run"),
|
||||
json={"model": "deepseek-v4-pro", "messages": [{"role": "user", "content": "do it"}]},
|
||||
)
|
||||
assert deepseek.status_code == 200
|
||||
assert provider.completed[-1]["model"] == "deepseek-v4-pro"
|
||||
assert provider.completed[-1]["provider"] == "deepseek"
|
||||
assert provider.completed[-1]["reasoning_effort"] == "max"
|
||||
|
||||
downgrade = client.post(
|
||||
legacy = client.post(
|
||||
"/v1/chat/completions",
|
||||
headers=headers(settings, identity_jwt),
|
||||
json={"model": "chat-medium", "messages": [{"role": "user", "content": "back"}]},
|
||||
headers=headers(settings, identity_jwt, "legacy-run"),
|
||||
json={"model": "work-medium", "messages": [{"role": "user", "content": "old"}]},
|
||||
)
|
||||
assert downgrade.status_code == 409
|
||||
assert legacy.status_code == 404
|
||||
|
||||
|
||||
def test_chat_stream_hides_provider_model_and_preserves_native_tool_calls(settings, identity_jwt) -> None:
|
||||
def test_openwebui_background_task_bypasses_agent_loop(settings, identity_jwt) -> None:
|
||||
provider = FakeModelProvider()
|
||||
app = create_app(settings, store=RuntimeStore(settings.database_url), provider=provider, tools=FakeTools())
|
||||
with TestClient(app) as client:
|
||||
response = client.post(
|
||||
"/v1/chat/completions",
|
||||
headers=headers(settings, identity_jwt, "chat-stream"),
|
||||
json={
|
||||
"model": "chat-light",
|
||||
"stream": True,
|
||||
"messages": [{"role": "user", "content": "inspect"}],
|
||||
},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert '"model": "chat-light"' in response.text
|
||||
assert "ChatGPT-5.6:Luna" not in response.text
|
||||
assert '"tool_calls"' in response.text
|
||||
assert '"name": "read_file"' in response.text
|
||||
assert "data: [DONE]" in response.text
|
||||
|
||||
|
||||
def test_openwebui_background_task_bypasses_work_loop(settings, identity_jwt) -> None:
|
||||
provider = FakeModelProvider()
|
||||
app = create_app(settings, store=RuntimeStore(settings.database_url), provider=provider, tools=FakeTools())
|
||||
with TestClient(app) as client:
|
||||
with TestClient(_app(settings, provider)) as client:
|
||||
response = client.post(
|
||||
"/v1/chat/completions",
|
||||
headers=headers(settings, identity_jwt, "background-task"),
|
||||
json={
|
||||
"model": "work-medium",
|
||||
"model": "terra",
|
||||
"messages": [{"role": "user", "content": "Generate tags"}],
|
||||
"metadata": {"task": "tags_generation"},
|
||||
},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json()["model"] == "work-medium"
|
||||
assert response.json()["model"] == "terra"
|
||||
assert provider.completed == []
|
||||
assert provider.forwarded[-1]["model"] == "ChatGPT-5.6:Terra"
|
||||
|
||||
|
||||
def test_work_stream_never_emits_delta_tool_calls(settings, identity_jwt) -> None:
|
||||
app = create_app(
|
||||
settings,
|
||||
store=RuntimeStore(settings.database_url),
|
||||
provider=FakeModelProvider(),
|
||||
tools=FakeTools(),
|
||||
)
|
||||
with TestClient(app) as client:
|
||||
def test_agent_stream_never_emits_delta_tool_calls(settings, identity_jwt) -> None:
|
||||
with TestClient(_app(settings)) as client:
|
||||
response = client.post(
|
||||
"/v1/chat/completions",
|
||||
headers=headers(settings, identity_jwt, "stream-chat"),
|
||||
json={
|
||||
"model": "work-medium",
|
||||
"model": "terra",
|
||||
"stream": True,
|
||||
"messages": [{"role": "user", "content": "do it"}],
|
||||
},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert "work complete" in response.text
|
||||
assert "agent complete" in response.text
|
||||
for line in response.text.splitlines():
|
||||
if line.startswith("data: {"):
|
||||
chunk = json.loads(line.removeprefix("data: "))
|
||||
|
||||
@@ -3,18 +3,6 @@ from __future__ import annotations
|
||||
from agent_platform.store import RuntimeStore
|
||||
|
||||
|
||||
async def test_conversation_mode_upgrade_is_one_way(settings) -> None:
|
||||
store = RuntimeStore(settings.database_url)
|
||||
await store.initialize()
|
||||
try:
|
||||
assert await store.select_mode("u1", "c1", "chat") == "chat"
|
||||
assert await store.select_mode("u1", "c1", "work") == "work"
|
||||
assert await store.select_mode("u1", "c1", "chat") == "work"
|
||||
assert await store.select_mode("u2", "c1", "chat") == "chat"
|
||||
finally:
|
||||
await store.close()
|
||||
|
||||
|
||||
async def test_memory_and_events_are_user_scoped(settings) -> None:
|
||||
store = RuntimeStore(settings.database_url)
|
||||
await store.initialize()
|
||||
|
||||
Reference in New Issue
Block a user