mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-06-06 20:15:17 +02:00
refactor(agents): delete deliverable dead twins in shared/tools; fix live image api_base bug
The deliverables subagent runs its own generate_image/podcast/report/resume/ video_presentation (via tools/index.py); the shared/tools copies had zero production importers — classic dead twins. Removed them so deliverable tools live only in their vertical slice. While repointing the 2 stranded unit tests at the LIVE deliverables modules, found the OpenRouter empty-api_base defense (resolve_api_base) existed ONLY in the dead shared generate_image, never propagated to the live multi-agent copy. Ported the fix into deliverables/tools/generate_image.py (both the global-config and user-DB-config branches) so an empty api_base no longer falls through to LiteLLM's global api_base (Azure) and 404s. Tests now exercise the live Command/receipt-returning tools (invoke the raw coroutine with a hand-built ToolRuntime; resume progress events neutralized).
This commit is contained in:
parent
64512c604d
commit
8d0090c6a1
10 changed files with 104 additions and 2519 deletions
|
|
@ -1,17 +1,58 @@
|
|||
"""Unit tests for resume page-limit helpers and enforcement flow."""
|
||||
"""Unit tests for resume page-limit helpers and enforcement flow.
|
||||
|
||||
Targets the live deliverables resume tool. The tool returns a
|
||||
``Command`` (payload JSON-encoded in ``update["messages"][0].content``
|
||||
plus a receipt), so flow tests invoke it via a ToolCall dict and unwrap
|
||||
the payload.
|
||||
"""
|
||||
|
||||
import io
|
||||
import json
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
import pypdf
|
||||
import pytest
|
||||
from langchain.tools import ToolRuntime
|
||||
|
||||
from app.agents.shared.tools import resume as resume_tool
|
||||
from app.agents.multi_agent_chat.subagents.builtins.deliverables.tools import (
|
||||
resume as resume_tool,
|
||||
)
|
||||
|
||||
pytestmark = pytest.mark.unit
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _silence_progress_events(monkeypatch):
|
||||
"""The live tool emits ``dispatch_custom_event`` progress updates that
|
||||
require a langgraph run context; neutralize them for direct unit calls."""
|
||||
monkeypatch.setattr(resume_tool, "dispatch_custom_event", lambda *a, **k: None)
|
||||
|
||||
|
||||
def _runtime(tool_call_id: str = "call-1") -> ToolRuntime:
|
||||
"""Minimal ToolRuntime; the resume tool only reads ``tool_call_id``."""
|
||||
return ToolRuntime(
|
||||
state={},
|
||||
context=None,
|
||||
config={},
|
||||
stream_writer=None,
|
||||
tool_call_id=tool_call_id,
|
||||
store=None,
|
||||
)
|
||||
|
||||
|
||||
async def _invoke(tool, args: dict) -> dict:
|
||||
"""Drive a Command-returning tool and return its decoded payload.
|
||||
|
||||
These tools take an injected ``ToolRuntime`` and return a
|
||||
``Command``; invoke the raw coroutine with a hand-built runtime
|
||||
(the repo's pattern for unit-testing such tools) and decode the
|
||||
ToolMessage payload.
|
||||
"""
|
||||
command = await tool.coroutine(runtime=_runtime(), **args)
|
||||
return json.loads(command.update["messages"][0].content)
|
||||
|
||||
|
||||
class _FakeReport:
|
||||
_next_id = 1000
|
||||
|
||||
|
|
@ -108,7 +149,7 @@ async def test_generate_resume_defaults_to_one_page_target(monkeypatch) -> None:
|
|||
monkeypatch.setattr(resume_tool, "_count_pdf_pages", lambda _pdf: 1)
|
||||
|
||||
tool = resume_tool.create_generate_resume_tool(search_space_id=1, thread_id=1)
|
||||
result = await tool.ainvoke({"user_info": "Jane Doe experience"})
|
||||
result = await _invoke(tool, {"user_info": "Jane Doe experience"})
|
||||
|
||||
assert result["status"] == "ready"
|
||||
assert prompts
|
||||
|
|
@ -138,7 +179,7 @@ async def test_generate_resume_compresses_when_over_limit(monkeypatch) -> None:
|
|||
monkeypatch.setattr(resume_tool, "_count_pdf_pages", lambda _pdf: next(page_counts))
|
||||
|
||||
tool = resume_tool.create_generate_resume_tool(search_space_id=1, thread_id=1)
|
||||
result = await tool.ainvoke({"user_info": "Jane Doe experience", "max_pages": 1})
|
||||
result = await _invoke(tool, {"user_info": "Jane Doe experience", "max_pages": 1})
|
||||
|
||||
assert result["status"] == "ready"
|
||||
assert write_session.added, "Expected successful report write"
|
||||
|
|
@ -173,7 +214,7 @@ async def test_generate_resume_returns_ready_when_target_not_met(monkeypatch) ->
|
|||
monkeypatch.setattr(resume_tool, "_count_pdf_pages", lambda _pdf: next(page_counts))
|
||||
|
||||
tool = resume_tool.create_generate_resume_tool(search_space_id=1, thread_id=1)
|
||||
result = await tool.ainvoke({"user_info": "Jane Doe experience", "max_pages": 1})
|
||||
result = await _invoke(tool, {"user_info": "Jane Doe experience", "max_pages": 1})
|
||||
|
||||
assert result["status"] == "ready"
|
||||
assert "could not fit the target" in (result["message"] or "").lower()
|
||||
|
|
@ -206,7 +247,7 @@ async def test_generate_resume_fails_when_hard_limit_exceeded(monkeypatch) -> No
|
|||
monkeypatch.setattr(resume_tool, "_count_pdf_pages", lambda _pdf: next(page_counts))
|
||||
|
||||
tool = resume_tool.create_generate_resume_tool(search_space_id=1, thread_id=1)
|
||||
result = await tool.ainvoke({"user_info": "Jane Doe experience", "max_pages": 1})
|
||||
result = await _invoke(tool, {"user_info": "Jane Doe experience", "max_pages": 1})
|
||||
|
||||
assert result["status"] == "failed"
|
||||
assert "hard page limit" in (result["error"] or "").lower()
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ from __future__ import annotations
|
|||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from langchain.tools import ToolRuntime
|
||||
|
||||
pytestmark = pytest.mark.unit
|
||||
|
||||
|
|
@ -90,7 +91,9 @@ async def test_global_openrouter_image_gen_sets_api_base_when_config_empty():
|
|||
async def test_generate_image_tool_global_sets_api_base_when_config_empty():
|
||||
"""Same defense at the agent tool entry point — both surfaces share
|
||||
the same OpenRouter config payloads."""
|
||||
from app.agents.shared.tools import generate_image as gi_module
|
||||
from app.agents.multi_agent_chat.subagents.builtins.deliverables.tools import (
|
||||
generate_image as gi_module,
|
||||
)
|
||||
|
||||
cfg = {
|
||||
"id": -20_001,
|
||||
|
|
@ -150,7 +153,19 @@ async def test_generate_image_tool_global_sets_api_base_when_config_empty():
|
|||
tool = gi_module.create_generate_image_tool(
|
||||
search_space_id=1, db_session=MagicMock()
|
||||
)
|
||||
await tool.ainvoke({"prompt": "a cat", "n": 1})
|
||||
# The live tool takes an injected ToolRuntime and returns a Command;
|
||||
# drive the raw coroutine with a minimal runtime (the tool only reads
|
||||
# ``tool_call_id``). We assert on what was forwarded to litellm, not
|
||||
# on the return value.
|
||||
runtime = ToolRuntime(
|
||||
state={},
|
||||
context=None,
|
||||
config={},
|
||||
stream_writer=None,
|
||||
tool_call_id="call-1",
|
||||
store=None,
|
||||
)
|
||||
await tool.coroutine(prompt="a cat", n=1, runtime=runtime)
|
||||
|
||||
assert captured.get("api_base") == "https://openrouter.ai/api/v1"
|
||||
assert captured["model"] == "openrouter/openai/gpt-image-1"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue