mirror of
https://github.com/tiennm99/DocsGPT.git
synced 2026-10-04 18:13:03 +00:00
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.
94 lines
3.0 KiB
Python
94 lines
3.0 KiB
Python
|
|
from contextlib import contextmanager
|
|
from unittest.mock import MagicMock
|
|
|
|
import pytest
|
|
|
|
|
|
@pytest.fixture()
|
|
def _no_db(monkeypatch):
|
|
"""Scoped-id resolution opens a read conn; synthetic ids never use it."""
|
|
|
|
@contextmanager
|
|
def fake_readonly():
|
|
yield MagicMock()
|
|
|
|
monkeypatch.setattr("docsgpt.agents.tool_executor.db_readonly", fake_readonly)
|
|
|
|
|
|
class _FakeExecutorBase:
|
|
"""Stands in for BaseAgent: provides the tool_executor the mixin scopes."""
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
from docsgpt.agents.tool_executor import ToolExecutor
|
|
|
|
self.tool_executor = ToolExecutor()
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestWorkflowNodeAgentFactory:
|
|
|
|
def test_raises_on_unsupported_type(self):
|
|
from docsgpt.agents.workflows.node_agent import WorkflowNodeAgentFactory
|
|
|
|
with pytest.raises(ValueError, match="Unsupported agent type"):
|
|
WorkflowNodeAgentFactory.create(
|
|
agent_type="nonexistent",
|
|
endpoint="http://example.com",
|
|
llm_name="openai",
|
|
model_id="gpt-4",
|
|
api_key="key",
|
|
)
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestWorkflowNodeMixinInit:
|
|
"""The mixin scopes the EXECUTOR: agents fetch tools via tool_executor.get_tools(),
|
|
so per-node tool filtering must live there (the old agent-method mixin was dead code)."""
|
|
|
|
def _mixed(self, **kwargs):
|
|
from docsgpt.agents.workflows.node_agent import _WorkflowNodeMixin
|
|
|
|
class TestMixin(_WorkflowNodeMixin, _FakeExecutorBase):
|
|
pass
|
|
|
|
return TestMixin(
|
|
endpoint="http://example.com",
|
|
llm_name="openai",
|
|
model_id="gpt-4",
|
|
api_key="key",
|
|
**kwargs,
|
|
)
|
|
|
|
def test_mixin_scopes_executor_to_tool_ids(self):
|
|
obj = self._mixed(tool_ids=["tool1", "tool2"])
|
|
assert obj.tool_executor.allowed_tool_ids == ["tool1", "tool2"]
|
|
|
|
def test_mixin_defaults_to_empty_scope_not_unscoped(self):
|
|
# No tool_ids means the node gets NO tools — never the user's full set.
|
|
obj = self._mixed()
|
|
assert obj.tool_executor.allowed_tool_ids == []
|
|
|
|
def test_empty_scope_yields_no_tools(self):
|
|
obj = self._mixed()
|
|
assert obj.tool_executor.get_tools() == {}
|
|
|
|
def test_scoped_executor_resolves_builtin_synthetic_ids(self, _no_db):
|
|
"""A node whose Tools picker selected a builtin (Artifact / Read Document)
|
|
must get that tool at runtime — the P0 this design replaced."""
|
|
from docsgpt.agents.default_tools import default_tool_id
|
|
|
|
obj = self._mixed(
|
|
tool_ids=[
|
|
default_tool_id("artifact_generator"),
|
|
default_tool_id("read_document"),
|
|
]
|
|
)
|
|
tools = obj.tool_executor.get_tools()
|
|
names = {t["name"] for t in tools.values()}
|
|
assert names == {"artifact_generator", "read_document"}
|
|
|
|
def test_scoped_executor_drops_unresolvable_ids(self, _no_db):
|
|
obj = self._mixed(tool_ids=["not-a-real-tool-id"])
|
|
assert obj.tool_executor.get_tools() == {}
|