mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-07-26 23:51:14 +02:00
Merge pull request #1619 from CREDO23/feat-run-citations
[Feat] Cite scraper runs as verifiable sources in chat
This commit is contained in:
commit
ca4f231577
17 changed files with 339 additions and 30 deletions
|
|
@ -37,6 +37,18 @@ def test_web_result_maps_to_url() -> None:
|
|||
assert to_frontend_payload(entry) == "https://example.com/a"
|
||||
|
||||
|
||||
def test_run_maps_to_run_handle() -> None:
|
||||
entry = _entry(CitationSourceType.RUN, {"run_id": "run_abc-123"})
|
||||
|
||||
assert to_frontend_payload(entry) == "run_abc-123"
|
||||
|
||||
|
||||
def test_run_without_handle_is_dropped() -> None:
|
||||
entry = _entry(CitationSourceType.RUN, {})
|
||||
|
||||
assert to_frontend_payload(entry) is None
|
||||
|
||||
|
||||
def test_not_yet_renderable_kind_is_dropped() -> None:
|
||||
entry = _entry(CitationSourceType.CHAT_TURN, {"thread_id": 1, "turn": 2})
|
||||
|
||||
|
|
|
|||
|
|
@ -69,6 +69,12 @@ def _verb_tool(tools, name: str):
|
|||
return next(t for t in tools if t.name == name)
|
||||
|
||||
|
||||
def _invoke(tool, text: str, *, state=None):
|
||||
"""Call the coroutine with a stand-in runtime (ToolNode injects it in prod)."""
|
||||
runtime = SimpleNamespace(state=state or {}, tool_call_id="tc_1", context=None)
|
||||
return tool.coroutine(runtime, text=text)
|
||||
|
||||
|
||||
async def test_registry_becomes_one_tool_per_verb_plus_readers(isolate):
|
||||
caps = [
|
||||
_capability(name="web.scrape", output=_EchoOutput(echoed="a")),
|
||||
|
|
@ -104,20 +110,51 @@ async def test_tool_runs_executor_and_returns_serialized_output(isolate):
|
|||
tools = isolate.module.build_capability_tools(workspace_id=7, capabilities=[cap])
|
||||
tool = _verb_tool(tools, "web_scrape")
|
||||
|
||||
result = await tool.ainvoke({"text": "ping"})
|
||||
result = await _invoke(tool, "ping")
|
||||
|
||||
# Fake session makes record_run fail -> no run_id key, plain serialized output.
|
||||
assert result == {"echoed": "hi there"}
|
||||
assert cap.executor.seen.text == "ping"
|
||||
|
||||
|
||||
async def test_tool_registers_run_citation_when_stored(isolate, monkeypatch):
|
||||
from langgraph.types import Command
|
||||
|
||||
cap = _capability(name="web.scrape", output=_EchoOutput(echoed="hi"))
|
||||
monkeypatch.setattr(isolate.module, "record_run", AsyncMock(return_value="abc-123"))
|
||||
tools = isolate.module.build_capability_tools(workspace_id=7, capabilities=[cap])
|
||||
tool = _verb_tool(tools, "web_scrape")
|
||||
|
||||
result = await _invoke(tool, "ping")
|
||||
|
||||
assert isinstance(result, Command)
|
||||
registry = result.update["citation_registry"]
|
||||
entry = registry.resolve(1)
|
||||
assert entry is not None
|
||||
assert entry.locator["run_id"] == "run_abc-123"
|
||||
message = result.update["messages"][0]
|
||||
assert "[1]" in message.content
|
||||
assert "run_abc-123" in message.content
|
||||
|
||||
|
||||
async def test_runtime_survives_langchain_arg_parsing(isolate):
|
||||
"""runtime must survive langchain arg parsing (else ToolNode drops it)."""
|
||||
cap = _capability(name="web.scrape", output=_EchoOutput(echoed="hi"))
|
||||
tools = isolate.module.build_capability_tools(workspace_id=7, capabilities=[cap])
|
||||
tool = _verb_tool(tools, "web_scrape")
|
||||
|
||||
parsed = tool._parse_input({"text": "x", "runtime": "RT"}, "tc_1")
|
||||
|
||||
assert parsed["runtime"] == "RT"
|
||||
|
||||
|
||||
async def test_tool_charges_owner(isolate):
|
||||
output = _EchoOutput(echoed="hi")
|
||||
cap = _capability(name="web.scrape", output=output)
|
||||
tools = isolate.module.build_capability_tools(workspace_id=7, capabilities=[cap])
|
||||
tool = _verb_tool(tools, "web_scrape")
|
||||
|
||||
await tool.ainvoke({"text": "ping"})
|
||||
await _invoke(tool, "ping")
|
||||
|
||||
isolate.charge.assert_awaited_once()
|
||||
(charged_output, unit, ctx), _ = isolate.charge.call_args
|
||||
|
|
@ -136,7 +173,7 @@ async def test_over_budget_returns_friendly_message(isolate):
|
|||
tools = isolate.module.build_capability_tools(workspace_id=7, capabilities=[cap])
|
||||
tool = _verb_tool(tools, "web_scrape")
|
||||
|
||||
result = await tool.ainvoke({"text": "ping"})
|
||||
result = await _invoke(tool, "ping")
|
||||
|
||||
assert isinstance(result, str)
|
||||
assert "credit" in result.lower()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
"""Unit tests for registering a scraper run as a citation."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from app.agents.chat.multi_agent_chat.shared.citations import (
|
||||
CitationRegistry,
|
||||
CitationSourceType,
|
||||
to_frontend_payload,
|
||||
)
|
||||
from app.capabilities.core.access.run_citation import attach_run_citation
|
||||
|
||||
pytestmark = pytest.mark.unit
|
||||
|
||||
|
||||
def test_attaches_run_and_returns_label_with_ordinal() -> None:
|
||||
registry = CitationRegistry()
|
||||
|
||||
n, label = attach_run_citation(
|
||||
registry, run_external_id="run_abc-123", capability="walmart.scrape"
|
||||
)
|
||||
|
||||
assert n == 1
|
||||
assert f"[{n}]" in label
|
||||
entry = registry.resolve(n)
|
||||
assert entry is not None
|
||||
assert entry.source_type is CitationSourceType.RUN
|
||||
assert to_frontend_payload(entry) == "run_abc-123"
|
||||
|
||||
|
||||
def test_same_run_dedups_to_one_label() -> None:
|
||||
registry = CitationRegistry()
|
||||
|
||||
first, _ = attach_run_citation(
|
||||
registry, run_external_id="run_x", capability="walmart.scrape"
|
||||
)
|
||||
again, _ = attach_run_citation(
|
||||
registry, run_external_id="run_x", capability="walmart.reviews"
|
||||
)
|
||||
|
||||
assert first == again
|
||||
assert len(registry.by_n) == 1
|
||||
Loading…
Add table
Add a link
Reference in a new issue