Files
DocsGPT/tests/agents/test_agentic_agent.py
T
Alex 574f96341e refactor: rename the application package to docsgpt
The backend import package is now docsgpt, the name it will carry on PyPI;
application was far too generic to install into anyone's site-packages.
git mv plus a mechanical rewrite of every import, dotted string and path
reference: 734 Python files, the compose files, Dockerfile, workflows, docs,
setup scripts, devcontainer, k8s manifests, vscode config, pytest and coverage
config, .gitignore. Behaviour is unchanged.

Kept for one release:
- A top-level application package whose meta-path finder resolves
  application.x.y to the already-imported docsgpt.x.y object, so old imports
  and entry points (celery -A application.app.celery,
  uvicorn application.asgi:asgi_app) keep working with a FutureWarning.
- Celery registers every application.* task name as an alias of its
  docsgpt.* task on start-up, so messages queued by the previous release still
  run. The redbeat key prefix moves to redbeat:docsgpt:v2: so schedule entries
  the previous release wrote are left unread instead of firing twice.

The backend image builds from the repository root (docker build -f
docsgpt/Dockerfile .) so it can ship the alias package; a root .dockerignore
allow-lists docsgpt/ and application/ and keeps caches, local data, .env
files, the sample index files and the Dockerfile out. Compose and the image
workflows point at the new context.
2026-09-07 10:20:43 +01:00

148 lines
4.6 KiB
Python

"""Tests for AgenticAgent — LLM-controlled retrieval agent."""
from unittest.mock import Mock
import pytest
from docsgpt.agents.agentic_agent import AgenticAgent
@pytest.fixture
def _no_tools(monkeypatch):
monkeypatch.setattr(
"docsgpt.agents.tool_executor.ToolExecutor.get_tools",
lambda self: {},
)
@pytest.mark.unit
class TestAgenticAgentInit:
def test_initialization(
self, agent_base_params, mock_llm_creator, mock_llm_handler_creator
):
agent = AgenticAgent(**agent_base_params)
assert isinstance(agent, AgenticAgent)
assert agent.retriever_config == {}
def test_initialization_with_retriever_config(
self, agent_base_params, mock_llm_creator, mock_llm_handler_creator
):
rc = {"source": {"active_docs": ["abc"]}, "retriever_name": "classic"}
agent = AgenticAgent(retriever_config=rc, **agent_base_params)
assert agent.retriever_config == rc
def test_inherits_base_properties(
self, agent_base_params, mock_llm_creator, mock_llm_handler_creator
):
agent = AgenticAgent(**agent_base_params)
assert agent.endpoint == agent_base_params["endpoint"]
assert agent.llm_name == agent_base_params["llm_name"]
assert agent.model_id == agent_base_params["model_id"]
@pytest.mark.unit
class TestAgenticAgentGenInner:
def test_basic_flow_yields_sources_and_tool_calls(
self,
agent_base_params,
mock_llm,
mock_llm_handler,
mock_llm_creator,
mock_llm_handler_creator,
_no_tools,
log_context,
):
mock_llm.gen_stream = Mock(return_value=iter(["Answer"]))
def mock_handler(*args, **kwargs):
yield "Processed"
mock_llm_handler.process_message_flow = Mock(side_effect=mock_handler)
agent = AgenticAgent(**agent_base_params)
results = list(agent._gen_inner("Test query", log_context))
sources = [r for r in results if "sources" in r]
tool_calls = [r for r in results if "tool_calls" in r]
assert len(sources) == 1
assert len(tool_calls) == 1
def test_logs_agent_component(
self,
agent_base_params,
mock_llm,
mock_llm_handler,
mock_llm_creator,
mock_llm_handler_creator,
_no_tools,
log_context,
):
mock_llm.gen_stream = Mock(return_value=iter(["Answer"]))
def mock_handler(*args, **kwargs):
yield "Done"
mock_llm_handler.process_message_flow = Mock(side_effect=mock_handler)
agent = AgenticAgent(**agent_base_params)
list(agent._gen_inner("Query", log_context))
agent_logs = [s for s in log_context.stacks if s["component"] == "agent"]
assert len(agent_logs) == 1
assert "tool_calls" in agent_logs[0]["data"]
def test_no_pre_fetched_docs_in_messages(
self,
agent_base_params,
mock_llm,
mock_llm_handler,
mock_llm_creator,
mock_llm_handler_creator,
_no_tools,
log_context,
):
mock_llm.gen_stream = Mock(return_value=iter(["Answer"]))
def mock_handler(*args, **kwargs):
yield "Done"
mock_llm_handler.process_message_flow = Mock(side_effect=mock_handler)
agent = AgenticAgent(**agent_base_params)
list(agent._gen_inner("Query", log_context))
call_kwargs = mock_llm.gen_stream.call_args[1]
messages = call_kwargs["messages"]
# System prompt should not contain {summaries} replacement
assert messages[0]["role"] == "system"
assert messages[-1]["role"] == "user"
assert messages[-1]["content"] == "Query"
@pytest.mark.unit
class TestAgenticAgentCollectSources:
def test_collect_internal_sources_from_cache(
self, agent_base_params, mock_llm_creator, mock_llm_handler_creator
):
agent = AgenticAgent(**agent_base_params)
mock_tool = Mock()
mock_tool.retrieved_docs = [
{"text": "Found", "title": "Doc", "source": "test"},
]
cache_key = f"internal_search:internal:{agent.user or ''}"
agent.tool_executor._loaded_tools[cache_key] = mock_tool
agent._collect_internal_sources()
assert len(agent.retrieved_docs) == 1
assert agent.retrieved_docs[0]["title"] == "Doc"
def test_collect_internal_sources_no_cache(
self, agent_base_params, mock_llm_creator, mock_llm_handler_creator
):
agent = AgenticAgent(**agent_base_params)
agent._collect_internal_sources()
assert agent.retrieved_docs == []