mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-11-06 21:22:23 +01:00
* fix(llm): keep reasoning out of answers when thinking is off Small-budget utility calls on reasoning models spend every token on reasoning, and llm_call then returned reasoning_content as the answer. Web search extraction sent that text to SearXNG as the query, which found nothing, and session titles fell back as unusable. Treat thinking_mode="off" as "only the answer": non-streaming calls now return empty instead of substituting reasoning, matching the streaming path. Callers that do not turn thinking off keep the existing fallback. Turn thinking off for search query extraction, session titles without a configured effort, scheduled task names, and deep research classification, and strip inline think blocks from the extracted query. * fix(routing): require computer task wording for named hosts "on <word>" and "from <word>" counted as a named computer on their own, so "Who is Jensen Huang from NVIDIA?" and "What happened on Monday?" routed to shell tools and the workspace toolset. A target now only counts next to wording that describes work on a machine, such as "check disk usage on gpu-box" or "copy the logs from gpu-box". The intent classifier and the agent loop's local computer check share the pattern. * fix(chat): resolve follow-up references in web search queries Query extraction only saw the latest message, so "Does he work with Lisa Su?" lost who "he" was. Include the last four turns, capped per turn, and instruct the model to use them only to resolve references. Incognito turns do not use saved session history. * test(llm): stop reasoning fallback tests hanging in isolation The module replaced src.agent_tools and core.database with MagicMock before importing agent_loop. strip_tool_blocks then returned a mock, and _strip_think_blocks looped forever because a mock's find() never returns -1. This hung whenever the file ran alone or first on a worker. Import the real modules instead. * test(search): remove extra blank line at EOF --------- Co-authored-by: Alexandre Teixeira <general@alexandreteixeira.dev>
573 lines
20 KiB
Python
573 lines
20 KiB
Python
"""Opt-in ``utility_reasoning_effort`` for utility/background LLM calls.
|
|
|
|
The setting is resolved in ``task_llm_call_async`` and sent only to candidates
|
|
on the utility chain whose model advertises the level. Explicit caller values
|
|
win; with the setting empty nothing changes.
|
|
"""
|
|
import asyncio
|
|
|
|
import pytest
|
|
|
|
from src import llm_core, task_endpoint
|
|
from src.chatgpt_subscription import validate_reasoning_effort
|
|
|
|
UTIL = ("https://api.openai.example/v1", "gpt-5.5", {})
|
|
UTIL_FALLBACK = ("https://fallback.example/v1", "gpt-5.5-mini", {})
|
|
OVERRIDE = ("https://override.example/v1", "gpt-5.5", {})
|
|
NO_EVIDENCE = ("https://local.example/v1", "tiny-local-model", {})
|
|
|
|
|
|
def _stub_resolvers(monkeypatch, utility):
|
|
"""Stub the resolvers in both modules that look the utility chain up."""
|
|
from src import endpoint_resolver
|
|
|
|
resolve = lambda prefix, *a, **k: { # noqa: E731
|
|
"utility": utility, "task": OVERRIDE, "default": NO_EVIDENCE,
|
|
}[prefix]
|
|
fallbacks = lambda owner=None: [UTIL_FALLBACK] # noqa: E731
|
|
for mod in (task_endpoint, endpoint_resolver):
|
|
monkeypatch.setattr(mod, "resolve_endpoint", resolve)
|
|
monkeypatch.setattr(mod, "resolve_utility_fallback_candidates", fallbacks)
|
|
|
|
|
|
@pytest.fixture
|
|
def chain(monkeypatch):
|
|
"""Stub the resolvers and capture what reaches llm_call_async per model."""
|
|
_stub_resolvers(monkeypatch, UTIL)
|
|
|
|
async def _quiet(*_a, **_k):
|
|
return None
|
|
monkeypatch.setattr(task_endpoint, "wait_for_interactive_quiet", _quiet)
|
|
|
|
calls = {}
|
|
|
|
async def fake_call(url, model, messages, **kw):
|
|
calls[url] = kw.get("reasoning_effort")
|
|
if url != UTIL_FALLBACK[0]:
|
|
raise RuntimeError("force walk down the chain")
|
|
return "ok"
|
|
|
|
monkeypatch.setattr(llm_core, "llm_call_async", fake_call)
|
|
return calls
|
|
|
|
|
|
def _set_effort(monkeypatch, value):
|
|
monkeypatch.setattr(
|
|
"src.settings.get_user_setting",
|
|
lambda key, owner="", default=None: value if key == "utility_reasoning_effort" else default,
|
|
)
|
|
|
|
|
|
def _run(**kw):
|
|
return asyncio.run(task_endpoint.task_llm_call_async(
|
|
[{"role": "user", "content": "hi"}], **kw,
|
|
))
|
|
|
|
|
|
def test_setting_applies_only_to_utility_chain(chain, monkeypatch):
|
|
_set_effort(monkeypatch, "high")
|
|
_run()
|
|
# The Background Tasks route shares the model id with the utility model but
|
|
# is a different (url, model) route, so it gets no effort.
|
|
assert chain[OVERRIDE[0]] is None
|
|
assert chain[UTIL[0]] == "high"
|
|
assert chain[UTIL_FALLBACK[0]] == "high"
|
|
|
|
|
|
def test_utility_candidate_gets_validated_effort(chain, monkeypatch):
|
|
_set_effort(monkeypatch, "HIGH")
|
|
_run()
|
|
assert chain[UTIL[0]] == "high"
|
|
|
|
|
|
def test_effort_without_evidence_is_dropped(chain, monkeypatch):
|
|
_set_effort(monkeypatch, "high")
|
|
_stub_resolvers(monkeypatch, ("https://local.example/v1", "tiny-local-model", {}))
|
|
_run()
|
|
assert chain["https://local.example/v1"] is None
|
|
|
|
|
|
def test_unsupported_level_is_dropped(chain, monkeypatch):
|
|
_set_effort(monkeypatch, "bogus")
|
|
_run()
|
|
assert chain[UTIL[0]] is None
|
|
|
|
|
|
def test_empty_setting_changes_nothing(chain, monkeypatch):
|
|
_set_effort(monkeypatch, "")
|
|
_run()
|
|
assert chain and all(effort is None for effort in chain.values())
|
|
|
|
|
|
def test_explicit_caller_effort_wins(chain, monkeypatch):
|
|
_set_effort(monkeypatch, "high")
|
|
_run(reasoning_effort="low")
|
|
assert chain and all(effort == "low" for effort in chain.values())
|
|
|
|
|
|
def test_fallback_factory_can_override_messages_and_kwargs(monkeypatch):
|
|
seen = []
|
|
|
|
async def fake_call(url, model, messages, **kw):
|
|
seen.append((model, messages, kw.get("reasoning_effort"), kw.get("temperature")))
|
|
if len(seen) == 1:
|
|
raise RuntimeError("first fails")
|
|
return "ok"
|
|
|
|
monkeypatch.setattr(llm_core, "llm_call_async", fake_call)
|
|
|
|
def factory(index, url, model, headers):
|
|
return {"kwargs": {"reasoning_effort": f"e{index}"}}
|
|
|
|
out = asyncio.run(llm_core.llm_call_async_with_fallback(
|
|
[("http://a/v1", "m1", {}), ("http://b/v1", "m2", {})],
|
|
[{"role": "user", "content": "x"}],
|
|
temperature=0.1,
|
|
candidate_request_factory=factory,
|
|
))
|
|
assert out == "ok"
|
|
assert [(s[0], s[2], s[3]) for s in seen] == [("m1", "e0", 0.1), ("m2", "e1", 0.1)]
|
|
|
|
|
|
def test_validate_reasoning_effort_needs_evidence():
|
|
assert validate_reasoning_effort("gpt-5.5", "high") == "high"
|
|
assert validate_reasoning_effort("tiny-local-model", "high") is None
|
|
assert validate_reasoning_effort("gpt-5.5", "") is None
|
|
|
|
|
|
def test_chatgpt_wire_carries_effort_and_default_omits_it():
|
|
sent = llm_core._build_chatgpt_responses_payload(
|
|
"gpt-5.5", [{"role": "user", "content": "hi"}], 0.3, 64, reasoning_effort="high",
|
|
)
|
|
assert sent["reasoning"] == {"effort": "high"}
|
|
plain = llm_core._build_chatgpt_responses_payload(
|
|
"gpt-5.5", [{"role": "user", "content": "hi"}], 0.3, 64,
|
|
)
|
|
assert "reasoning" not in plain
|
|
|
|
|
|
def test_sync_llm_call_cache_key_includes_effort(monkeypatch):
|
|
keys = []
|
|
|
|
def fake_cached(key):
|
|
keys.append(key)
|
|
return "cached"
|
|
|
|
monkeypatch.setattr(llm_core, "_get_cached_response", fake_cached)
|
|
msgs = [{"role": "user", "content": "hi"}]
|
|
llm_core.llm_call("http://x/v1", "m", msgs)
|
|
llm_core.llm_call("http://x/v1", "m", msgs, reasoning_effort="high")
|
|
llm_core.llm_call("http://x/v1", "m", msgs, reasoning_effort="HIGH")
|
|
assert keys[0] != keys[1]
|
|
assert keys[1] == keys[2]
|
|
|
|
|
|
def test_setting_is_registered_default_and_per_user():
|
|
from src.settings import DEFAULT_SETTINGS, _PER_USER_KEYS
|
|
|
|
assert DEFAULT_SETTINGS["utility_reasoning_effort"] == ""
|
|
assert "utility_reasoning_effort" in _PER_USER_KEYS
|
|
|
|
|
|
# --- effort_for_call: setting, then the chat session's own effort ---
|
|
|
|
CHAT = ("https://chat.example/v1", "gpt-5.5", {})
|
|
|
|
|
|
class _Sess:
|
|
def __init__(self, thinking_mode="effort:low", url=CHAT[0], model=CHAT[1], owner="alice"):
|
|
self.thinking_mode = thinking_mode
|
|
self.endpoint_url = url
|
|
self.model = model
|
|
self.owner = owner
|
|
self.headers = {}
|
|
|
|
|
|
@pytest.fixture
|
|
def util(monkeypatch):
|
|
"""Utility chain = {UTIL}; the setting is controlled via ``settings[owner]``."""
|
|
from src import utility_effort
|
|
|
|
settings = {}
|
|
monkeypatch.setattr(utility_effort, "utility_routes", lambda owner=None: {(UTIL[0], UTIL[1])})
|
|
monkeypatch.setattr(
|
|
"src.settings.get_user_setting",
|
|
lambda key, owner="", default=None: (
|
|
settings.get(owner, "") if key == "utility_reasoning_effort" else default
|
|
),
|
|
)
|
|
util_mod = utility_effort
|
|
util_mod.settings = settings
|
|
return util_mod
|
|
|
|
|
|
def test_setting_applies_to_utility_route_and_wins_over_chat_effort(util):
|
|
util.settings["alice"] = "medium"
|
|
assert util.effort_for_call(UTIL[0], UTIL[1], "alice", _Sess("effort:low")) == "medium"
|
|
assert util.effort_for_call(UTIL[0], UTIL[1], "alice") == "medium"
|
|
|
|
|
|
def test_setting_is_per_user(util):
|
|
util.settings["alice"] = "high"
|
|
assert util.effort_for_call(UTIL[0], UTIL[1], "alice") == "high"
|
|
assert util.effort_for_call(UTIL[0], UTIL[1], "bob") is None
|
|
|
|
|
|
def test_session_effort_inherited_on_the_session_route(util):
|
|
assert util.effort_for_call(CHAT[0], CHAT[1], "alice", _Sess("effort:low")) == "low"
|
|
# Endpoint spelling differences (trailing slash) are still the same route.
|
|
assert util.effort_for_call(CHAT[0] + "/", CHAT[1], "alice", _Sess("effort:low")) == "low"
|
|
|
|
|
|
def test_session_effort_not_inherited_on_another_route(util):
|
|
assert util.effort_for_call(UTIL[0], UTIL[1], "alice", _Sess("effort:low")) is None
|
|
assert util.effort_for_call(CHAT[0], "gpt-5.5-mini", "alice", _Sess("effort:low")) is None
|
|
|
|
|
|
def test_session_effort_off_or_missing_inherits_nothing(util):
|
|
assert util.effort_for_call(CHAT[0], CHAT[1], "alice", _Sess("off")) is None
|
|
assert util.effort_for_call(CHAT[0], CHAT[1], "alice", _Sess("")) is None
|
|
|
|
|
|
def test_no_session_means_no_inheritance(util):
|
|
assert util.effort_for_call(CHAT[0], CHAT[1], "alice") is None
|
|
|
|
|
|
def test_explicit_off_setting_blocks_session_effort_inheritance(util):
|
|
util.settings["alice"] = "off"
|
|
assert util.effort_for_call(CHAT[0], CHAT[1], "alice", _Sess("effort:low")) is None
|
|
assert util.candidate_effort_factory("alice") is None
|
|
|
|
util.settings["alice"] = "default"
|
|
assert util.effort_for_call(CHAT[0], CHAT[1], "alice", _Sess("effort:low")) is None
|
|
assert util.candidate_effort_factory("alice") is None
|
|
|
|
|
|
def test_inherited_effort_is_validated_against_the_model(util):
|
|
assert util.effort_for_call(
|
|
NO_EVIDENCE[0], NO_EVIDENCE[1], "alice",
|
|
_Sess("effort:low", url=NO_EVIDENCE[0], model=NO_EVIDENCE[1]),
|
|
) is None
|
|
assert util.effort_for_call(CHAT[0], CHAT[1], "alice", _Sess("effort:bogus")) is None
|
|
|
|
|
|
def test_effort_for_call_never_raises(util, monkeypatch):
|
|
def boom(*_a, **_k):
|
|
raise RuntimeError("db down")
|
|
|
|
monkeypatch.setattr(util, "effort_for_route", boom)
|
|
assert util.effort_for_call(UTIL[0], UTIL[1], "alice", _Sess()) is None
|
|
|
|
|
|
# --- no global fill: callers that are not utility call sites never get an effort ---
|
|
|
|
def test_llm_call_async_adds_no_default_effort(util, monkeypatch):
|
|
# The owner-less helpers resolve at global scope; set it there and per user.
|
|
util.settings[""] = util.settings["alice"] = "high"
|
|
seen = []
|
|
|
|
def fake_key(url, model, messages, temperature, max_tokens, headers=None,
|
|
thinking_mode=None, reasoning_effort=None):
|
|
seen.append(reasoning_effort)
|
|
return "k"
|
|
|
|
monkeypatch.setattr(llm_core, "_get_cache_key", fake_key)
|
|
monkeypatch.setattr(llm_core, "_get_cached_response", lambda key: "cached")
|
|
msgs = [{"role": "user", "content": "hi"}]
|
|
# Same (url, model) as the utility route, but not a utility call site.
|
|
asyncio.run(llm_core.llm_call_async(UTIL[0], UTIL[1], msgs))
|
|
llm_core.llm_call(UTIL[0], UTIL[1], msgs)
|
|
asyncio.run(llm_core.llm_call_async(UTIL[0], UTIL[1], msgs, reasoning_effort="low"))
|
|
assert seen == [None, None, "low"]
|
|
|
|
|
|
# --- call sites pass the effort explicitly ---
|
|
|
|
def _capture_llm(monkeypatch):
|
|
sent = []
|
|
|
|
async def fake_call(url, model, messages, **kw):
|
|
sent.append((url, model, kw.get("reasoning_effort")))
|
|
return "A title"
|
|
|
|
monkeypatch.setattr(llm_core, "llm_call_async", fake_call)
|
|
return sent
|
|
|
|
|
|
class _History:
|
|
role = "user"
|
|
content = "hello there"
|
|
|
|
|
|
def _title_session(**kw):
|
|
sess = _Sess(**kw)
|
|
sess.id = "s1"
|
|
sess.history = [_History()]
|
|
return sess
|
|
|
|
|
|
class _Manager:
|
|
def update_session_name(self, *_a):
|
|
pass
|
|
|
|
|
|
@pytest.fixture
|
|
def title_registered_endpoint(tmp_path, monkeypatch):
|
|
import core.database as database
|
|
import src.database as compat_database
|
|
import src.endpoint_resolver as resolver
|
|
from tests.helpers.database import disposable_database
|
|
|
|
monkeypatch.setenv("AUTH_ENABLED", "true")
|
|
monkeypatch.setattr("src.settings.load_settings", lambda: {})
|
|
monkeypatch.setattr(resolver, "resolve_url", lambda url: url)
|
|
with disposable_database(tmp_path) as factory:
|
|
monkeypatch.setattr(compat_database, "SessionLocal", factory)
|
|
monkeypatch.setattr(resolver, "SessionLocal", factory)
|
|
with factory() as db:
|
|
db.add(database.ModelEndpoint(id="chat", name="chat", owner="alice",
|
|
base_url=CHAT[0], is_enabled=True))
|
|
db.add(database.ModelEndpoint(id="shared", name="shared", owner=None,
|
|
base_url="https://shared.example/v1", is_enabled=True))
|
|
db.commit()
|
|
yield
|
|
|
|
|
|
def test_auto_title_inherits_chat_effort_when_utility_is_blank(util, monkeypatch, title_registered_endpoint):
|
|
from routes import chat_helpers
|
|
from src.endpoint_resolver import build_chat_url
|
|
|
|
sent = _capture_llm(monkeypatch)
|
|
chat_url = build_chat_url(CHAT[0])
|
|
asyncio.run(chat_helpers.auto_name_session(_Manager(), _title_session(url=chat_url)))
|
|
assert sent == [(chat_url, CHAT[1], "low")]
|
|
|
|
|
|
@pytest.mark.parametrize("owner,url", [
|
|
("alice", "https://unregistered.example/v1"),
|
|
("bob", CHAT[0]),
|
|
(None, "https://shared.example/v1"),
|
|
("", "https://shared.example/v1"),
|
|
(" ", "https://shared.example/v1"),
|
|
(" alice ", "https://shared.example/v1"),
|
|
("system", "https://shared.example/v1"),
|
|
(42, "https://shared.example/v1"),
|
|
])
|
|
def test_auto_title_rejects_unauthorized_chat_endpoint_when_utility_is_blank(
|
|
util, monkeypatch, title_registered_endpoint, owner, url,
|
|
):
|
|
from routes import chat_helpers
|
|
|
|
sent = _capture_llm(monkeypatch)
|
|
asyncio.run(chat_helpers.auto_name_session(_Manager(), _title_session(owner=owner, url=url)))
|
|
assert sent == []
|
|
|
|
|
|
def test_auto_title_missing_owner_fails_closed_in_single_user_mode(util, monkeypatch, title_registered_endpoint):
|
|
from routes import chat_helpers
|
|
|
|
monkeypatch.setenv("AUTH_ENABLED", "false")
|
|
sent = _capture_llm(monkeypatch)
|
|
asyncio.run(chat_helpers.auto_name_session(
|
|
_Manager(), _title_session(owner=None, url="https://shared.example/v1"),
|
|
))
|
|
assert sent == []
|
|
|
|
|
|
def test_auto_title_setting_beats_chat_effort(util, monkeypatch):
|
|
from routes import chat_helpers
|
|
|
|
util.settings["alice"] = "medium"
|
|
sent = _capture_llm(monkeypatch)
|
|
monkeypatch.setattr(task_endpoint, "resolve_task_endpoint", lambda *a, **k: (UTIL[0], UTIL[1], {}))
|
|
asyncio.run(chat_helpers.auto_name_session(_Manager(), _title_session()))
|
|
assert sent == [(UTIL[0], UTIL[1], "medium")]
|
|
|
|
|
|
def test_auto_title_on_a_different_utility_model_gets_no_chat_effort(util, monkeypatch):
|
|
from routes import chat_helpers
|
|
|
|
sent = _capture_llm(monkeypatch)
|
|
monkeypatch.setattr(task_endpoint, "resolve_task_endpoint", lambda *a, **k: (UTIL[0], UTIL[1], {}))
|
|
asyncio.run(chat_helpers.auto_name_session(_Manager(), _title_session()))
|
|
assert sent == [(UTIL[0], UTIL[1], None)]
|
|
|
|
|
|
def _capture_title_thinking(monkeypatch):
|
|
sent = []
|
|
|
|
async def fake_call(url, model, messages, **kw):
|
|
sent.append((kw.get("reasoning_effort"), kw.get("thinking_mode")))
|
|
return "A title"
|
|
|
|
monkeypatch.setattr(llm_core, "llm_call_async", fake_call)
|
|
return sent
|
|
|
|
|
|
def test_auto_title_turns_thinking_off_without_a_configured_effort(util, monkeypatch):
|
|
from routes import chat_helpers
|
|
|
|
sent = _capture_title_thinking(monkeypatch)
|
|
monkeypatch.setattr(task_endpoint, "resolve_task_endpoint", lambda *a, **k: (UTIL[0], UTIL[1], {}))
|
|
asyncio.run(chat_helpers.auto_name_session(_Manager(), _title_session()))
|
|
assert sent == [(None, "off")]
|
|
|
|
|
|
def test_auto_title_keeps_thinking_default_with_a_configured_effort(util, monkeypatch):
|
|
from routes import chat_helpers
|
|
|
|
util.settings["alice"] = "medium"
|
|
sent = _capture_title_thinking(monkeypatch)
|
|
monkeypatch.setattr(task_endpoint, "resolve_task_endpoint", lambda *a, **k: (UTIL[0], UTIL[1], {}))
|
|
asyncio.run(chat_helpers.auto_name_session(_Manager(), _title_session()))
|
|
assert sent == [("medium", None)]
|
|
|
|
|
|
def test_memory_extraction_passes_chat_effort(util, monkeypatch):
|
|
from services.memory import memory_extractor
|
|
|
|
sent = []
|
|
|
|
async def fake_call(url, model, messages, **kw):
|
|
sent.append(kw.get("reasoning_effort"))
|
|
return "[]"
|
|
|
|
monkeypatch.setattr(llm_core, "llm_call_async", fake_call)
|
|
|
|
class Sess(_Sess):
|
|
def get_context_messages(self):
|
|
return [{"role": "user", "content": "I live in Oslo"},
|
|
{"role": "assistant", "content": "Noted"}]
|
|
|
|
asyncio.run(memory_extractor.extract_and_store(
|
|
Sess(), object(), object(), CHAT[0], CHAT[1], {},
|
|
))
|
|
assert sent and sent[0] == "low"
|
|
|
|
|
|
def test_email_summary_sends_setting_effort_for_owner_only(util, monkeypatch):
|
|
from routes.email import email_helpers
|
|
|
|
util.settings["alice"] = "medium"
|
|
sent = []
|
|
|
|
async def fake_call(**kw):
|
|
sent.append(kw.get("reasoning_effort"))
|
|
return "- point"
|
|
|
|
monkeypatch.setattr(llm_core, "llm_call_async", fake_call)
|
|
for owner in ("alice", "bob", None):
|
|
asyncio.run(email_helpers._generate_email_summary(
|
|
UTIL[0], UTIL[1], "a@x", "s", "body", owner=owner,
|
|
))
|
|
assert sent == ["medium", None, None]
|
|
|
|
|
|
def test_candidate_effort_factory_scopes_to_utility_route(util):
|
|
from src.utility_effort import candidate_effort_factory
|
|
|
|
assert candidate_effort_factory("alice") is None
|
|
util.settings["alice"] = "medium"
|
|
factory = candidate_effort_factory("alice")
|
|
assert factory(0, UTIL[0], UTIL[1], {}) == {"kwargs": {"reasoning_effort": "medium"}}
|
|
assert factory(1, NO_EVIDENCE[0], NO_EVIDENCE[1], {}) == {}
|
|
assert candidate_effort_factory("bob") is None
|
|
|
|
|
|
def test_fallback_route_efforts_and_factory(monkeypatch):
|
|
from src.utility_effort import candidate_effort_factory, effort_for_route
|
|
|
|
def fake_resolve_id(ep_id, model, owner=None):
|
|
if ep_id == "ep-fallback":
|
|
return ("https://fallback.example/v1", model, {})
|
|
return None
|
|
|
|
monkeypatch.setattr("src.endpoint_resolver.resolve_endpoint_by_id", fake_resolve_id)
|
|
monkeypatch.setattr(
|
|
"src.settings.load_settings",
|
|
lambda: {"utility_model_fallbacks": [{"endpoint_id": "ep-fallback", "model": "gpt-5.5-mini", "reasoning_effort": "low"}]},
|
|
)
|
|
monkeypatch.setattr(
|
|
"src.settings.get_user_setting",
|
|
lambda key, owner="", default=None: default,
|
|
)
|
|
|
|
factory = candidate_effort_factory("alice")
|
|
assert factory is not None
|
|
assert effort_for_route("https://api.openai.example/v1", "gpt-5.5", "alice") is None
|
|
assert effort_for_route("https://fallback.example/v1", "gpt-5.5-mini", "alice") == "low"
|
|
assert factory(0, "https://fallback.example/v1", "gpt-5.5-mini", {}) == {"kwargs": {"reasoning_effort": "low"}}
|
|
|
|
|
|
def test_sync_openai_compatible_wire_transmits_explicit_effort(monkeypatch):
|
|
import httpx
|
|
|
|
sent = []
|
|
|
|
def fake_post(url, headers, **kwargs):
|
|
sent.append(dict(kwargs["json"]))
|
|
return httpx.Response(
|
|
200, json={"choices": [{"message": {"content": "ok"}}]}
|
|
)
|
|
|
|
monkeypatch.setattr(llm_core, "_get_cached_response", lambda *_: None)
|
|
monkeypatch.setattr(llm_core, "_set_cached_response", lambda *a, **k: None)
|
|
monkeypatch.setattr(llm_core, "note_model_activity", lambda *a: None)
|
|
monkeypatch.setattr(llm_core, "httpx_post_kimi_aware", fake_post)
|
|
|
|
url = "https://example.test/v1"
|
|
messages = [{"role": "user", "content": "hi"}]
|
|
|
|
llm_core.llm_call(
|
|
url, "gpt-5.5", messages, max_tokens=64,
|
|
reasoning_effort="HIGH",
|
|
)
|
|
llm_core.llm_call(url, "gpt-5.5", messages, max_tokens=64)
|
|
|
|
assert sent[0]["reasoning_effort"] == "high"
|
|
assert "reasoning_effort" not in sent[1]
|
|
|
|
|
|
def test_async_openai_compatible_wire_transmits_explicit_effort(monkeypatch):
|
|
import asyncio
|
|
import httpx
|
|
from contextlib import asynccontextmanager
|
|
|
|
sent = []
|
|
|
|
@asynccontextmanager
|
|
async def fake_slot(*args, **kwargs):
|
|
yield
|
|
|
|
async def fake_post(client, url, headers, **kwargs):
|
|
sent.append(dict(kwargs["json"]))
|
|
return httpx.Response(
|
|
200, json={"choices": [{"message": {"content": "ok"}}]}
|
|
)
|
|
|
|
monkeypatch.setattr(llm_core, "_get_cached_response", lambda *_: None)
|
|
monkeypatch.setattr(llm_core, "_set_cached_response", lambda *a, **k: None)
|
|
monkeypatch.setattr(llm_core, "_get_http_client", lambda: object())
|
|
monkeypatch.setattr(llm_core, "_is_host_dead", lambda *_: False)
|
|
monkeypatch.setattr(llm_core, "_local_model_slot", fake_slot)
|
|
monkeypatch.setattr(llm_core, "note_model_activity", lambda *a: None)
|
|
monkeypatch.setattr(llm_core, "httpx_post_kimi_aware_async", fake_post)
|
|
|
|
async def exercise():
|
|
url = "https://example.test/v1"
|
|
messages = [{"role": "user", "content": "hi"}]
|
|
|
|
await llm_core.llm_call_async(
|
|
url, "gpt-5.5", messages, max_tokens=0,
|
|
max_retries=1, reasoning_effort="medium",
|
|
)
|
|
await llm_core.llm_call_async(
|
|
url, "gpt-5.5", messages, max_tokens=0,
|
|
max_retries=1,
|
|
)
|
|
|
|
asyncio.run(exercise())
|
|
|
|
assert sent[0]["reasoning_effort"] == "medium"
|
|
assert "reasoning_effort" not in sent[1]
|