mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-14 00:48:26 +08:00
Feat: Agent api (#14157)
### What problem does this PR solve?
1. **List agents**
**Prev API**:
- `/v1/canvas/list GET`
- `/api/v1/agents GET`
**Current API**: `/api/v2/agents GET`
2. **Get canvas template**
**Prev API**: `/v1/canvas/templates GET`
**Current API**: `/api/v2/agents/templates GET`
3. **Delete an agent**
**Prev API**:
- `/v1/canvas/rm POST`
- `/api/v1/agents/<agent_id> DELETE`
**Current API**: `/api/v2/agents/<agent_id> DELETE`
4. **Update an agent**
**Prev API**:
- `/api/v1/agents/<agent_id> PUT`
- `/v1/canvas/setting POST `
**Current API**: `/api/v2/agents/<agent_id> PATCH`
5. **Create an agent**
**Prev API**:
- `/v1/canvas/set POST`
- `/api/v1/agents POST`
**Current API**: `/api/v2/agents POST`
6. **Get an agent**
**Prev API**:
- `/v1/canvas/get/<canvas_id> GET `
**Current API**: `/api/v2/agents/<agent_id> GET`
7. **Reset an agent**
**Prev API**:
- `/v1/canvas/reset POST`
**Current API**: `/api/v2/agents/<agent_id>/reset POST`
8. **Upload a file to an agent**
**Prev API**:
- `/v1/canvas/upload/<canvas_id> POST`
**Current API**: `/api/v2/agents/<agent_id>/upload POST`
9. **Input form**
**Prev API**:
- `/v1/canvas/input_form GET`
**Current API**:
`/api/v2/agents/<agent_id>/components/<component_id>/input-form GET`
10. **Debug an agent**
**Prev API**:
- `/v1/canvas/debug POST`
**Current API**:
`/api/v2/agents/<agent_id>/components/<component_id>/debug POST`
11. **Trace an agent**
**Prev API**:
- `/v1/canvas/trace GET`
**Current API**: `/api/v2/agents/<agent_id>/logs/<message_id> GET`
12. **Get an agent version list**
**Prev API**:
- `/v1/canvas/getlistversion/<canvas_id>`
**Current API**: `/api/v2/agents/<agent_id>/versions GET`
13. **Get a version of agent**
**Prev API**:
- `/v1/canvas/getversion/<version_id>`
**Current API**: `/api/v2/agents/<agent_id>/versions/<version_id> GET`
14. **Test db connection**
**Prev API**:
- `/v1/canvas/test_db_connect POST`
**Current API**: `/api/v2/agents/test_db_connection`
15. **Rerun the agent**
**Prev API**:
- `/v1/canvas/rerun POST`
**Current API**: `/api/v2/agents/rerun POST`
16. **Get prompts**
**Prev API**:
- `/v1/canvas/prompts GET`
**Current API**: `/api/v2/agents/prompts GET`
### Type of change
- [x] New Feature (non-breaking change which adds functionality)
---------
Co-authored-by: chanx <1243304602@qq.com>
This commit is contained in:
@@ -406,8 +406,11 @@ def delete_all_agent_sessions(auth, agent_id, *, page_size=1000):
|
||||
|
||||
|
||||
def agent_completions(auth, agent_id, payload=None):
|
||||
url = f"{HOST_ADDRESS}{AGENT_API_URL}/{agent_id}/completions"
|
||||
res = requests.post(url=url, headers=HEADERS, auth=auth, json=payload)
|
||||
url = f"{HOST_ADDRESS}{AGENT_API_URL}/chat/completion"
|
||||
body = {"agent_id": agent_id}
|
||||
if payload:
|
||||
body.update(payload)
|
||||
res = requests.post(url=url, headers=HEADERS, auth=auth, json=body)
|
||||
return res.json()
|
||||
|
||||
|
||||
|
||||
@@ -49,11 +49,18 @@ MINIMAL_DSL = {
|
||||
"variables": {},
|
||||
}
|
||||
|
||||
|
||||
def _agent_items(res):
|
||||
data = res.get("data", [])
|
||||
if isinstance(data, dict):
|
||||
return data.get("canvas", [])
|
||||
return data
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def agent_id(HttpApiAuth, request):
|
||||
res = list_agents(HttpApiAuth, {"page_size": 1000})
|
||||
assert res["code"] == 0, res
|
||||
for agent in res.get("data", []):
|
||||
for agent in _agent_items(res):
|
||||
if agent.get("title") == AGENT_TITLE:
|
||||
delete_agent(HttpApiAuth, agent["id"])
|
||||
|
||||
@@ -61,8 +68,9 @@ def agent_id(HttpApiAuth, request):
|
||||
assert res["code"] == 0, res
|
||||
res = list_agents(HttpApiAuth, {"title": AGENT_TITLE})
|
||||
assert res["code"] == 0, res
|
||||
assert res.get("data"), res
|
||||
agent_id = res["data"][0]["id"]
|
||||
agents = _agent_items(res)
|
||||
assert agents, res
|
||||
agent_id = agents[0]["id"]
|
||||
|
||||
def cleanup():
|
||||
delete_all_agent_sessions(HttpApiAuth, agent_id)
|
||||
@@ -82,7 +90,7 @@ class TestAgentCompletions:
|
||||
res = agent_completions(
|
||||
HttpApiAuth,
|
||||
agent_id,
|
||||
{"question": "hello", "stream": False, "session_id": session_id},
|
||||
{"query": "hello", "stream": False, "session_id": session_id},
|
||||
)
|
||||
assert res["code"] == 0, res
|
||||
if isinstance(res["data"], dict):
|
||||
|
||||
@@ -17,11 +17,8 @@ import pytest
|
||||
import requests
|
||||
from common import (
|
||||
create_agent,
|
||||
create_agent_session,
|
||||
delete_agent,
|
||||
delete_all_agent_sessions,
|
||||
delete_agent_sessions,
|
||||
list_agent_sessions,
|
||||
list_agents,
|
||||
)
|
||||
from configs import HOST_ADDRESS, VERSION
|
||||
@@ -52,11 +49,18 @@ MINIMAL_DSL = {
|
||||
"variables": {},
|
||||
}
|
||||
|
||||
|
||||
def _agent_items(res):
|
||||
data = res.get("data", [])
|
||||
if isinstance(data, dict):
|
||||
return data.get("canvas", [])
|
||||
return data
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def agent_id(HttpApiAuth, request):
|
||||
res = list_agents(HttpApiAuth, {"page_size": 1000})
|
||||
assert res["code"] == 0, res
|
||||
for agent in res.get("data", []):
|
||||
for agent in _agent_items(res):
|
||||
if agent.get("title") == AGENT_TITLE:
|
||||
delete_agent(HttpApiAuth, agent["id"])
|
||||
|
||||
@@ -64,8 +68,9 @@ def agent_id(HttpApiAuth, request):
|
||||
assert res["code"] == 0, res
|
||||
res = list_agents(HttpApiAuth, {"title": AGENT_TITLE})
|
||||
assert res["code"] == 0, res
|
||||
assert res.get("data"), res
|
||||
agent_id = res["data"][0]["id"]
|
||||
agents = _agent_items(res)
|
||||
assert agents, res
|
||||
agent_id = agents[0]["id"]
|
||||
|
||||
def cleanup():
|
||||
delete_all_agent_sessions(HttpApiAuth, agent_id)
|
||||
@@ -76,39 +81,14 @@ def agent_id(HttpApiAuth, request):
|
||||
|
||||
|
||||
class TestAgentSessions:
|
||||
@pytest.mark.p2
|
||||
def test_delete_agent_sessions_empty_ids_noop(self, HttpApiAuth, agent_id):
|
||||
res = create_agent_session(HttpApiAuth, agent_id, payload={})
|
||||
assert res["code"] == 0, res
|
||||
session_id = res["data"]["id"]
|
||||
|
||||
res = delete_agent_sessions(HttpApiAuth, agent_id, {"ids": []})
|
||||
assert res["code"] == 0, res
|
||||
|
||||
res = list_agent_sessions(HttpApiAuth, agent_id, params={"id": session_id})
|
||||
assert res["code"] == 0, res
|
||||
assert len(res["data"]) == 1, res
|
||||
|
||||
@pytest.mark.p2
|
||||
def test_create_list_delete_agent_sessions(self, HttpApiAuth, agent_id):
|
||||
res = create_agent_session(HttpApiAuth, agent_id, payload={})
|
||||
assert res["code"] == 0, res
|
||||
session_id = res["data"]["id"]
|
||||
assert res["data"]["agent_id"] == agent_id, res
|
||||
|
||||
res = list_agent_sessions(HttpApiAuth, agent_id, params={"id": session_id})
|
||||
assert res["code"] == 0, res
|
||||
assert len(res["data"]) == 1, res
|
||||
assert res["data"][0]["id"] == session_id, res
|
||||
|
||||
res = delete_agent_sessions(HttpApiAuth, agent_id, {"ids": [session_id]})
|
||||
assert res["code"] == 0, res
|
||||
|
||||
@pytest.mark.p2
|
||||
def test_agent_crud_validation_contract(self, HttpApiAuth, agent_id):
|
||||
res = list_agents(HttpApiAuth, {"id": "missing-agent-id", "title": "missing-agent-title"})
|
||||
assert res["code"] == 102, res
|
||||
assert "doesn't exist" in res["message"], res
|
||||
assert res["code"] == 0, res
|
||||
assert isinstance(res.get("data"), dict), res
|
||||
assert "canvas" in res["data"], res
|
||||
assert "total" in res["data"], res
|
||||
|
||||
res = list_agents(HttpApiAuth, {"title": AGENT_TITLE, "desc": "true", "page_size": 1})
|
||||
assert res["code"] == 0, res
|
||||
|
||||
@@ -498,6 +498,14 @@ def _load_session_module(monkeypatch):
|
||||
monkeypatch.setitem(sys.modules, "agent.canvas", agent_canvas_mod)
|
||||
monkeypatch.setitem(sys.modules, "agent.dsl_migration", agent_dsl_migration_mod)
|
||||
|
||||
quart_mod = ModuleType("quart")
|
||||
quart_mod.request = SimpleNamespace(args=_Args(), headers={}, files=_AwaitableValue({}), method="POST")
|
||||
quart_mod.Response = _StubResponse
|
||||
quart_mod.jsonify = lambda payload: payload
|
||||
quart_mod.current_app = SimpleNamespace()
|
||||
quart_mod.has_app_context = lambda: False
|
||||
monkeypatch.setitem(sys.modules, "quart", quart_mod)
|
||||
|
||||
module_path = repo_root / "api" / "apps" / "sdk" / "session.py"
|
||||
spec = importlib.util.spec_from_file_location("test_session_sdk_routes_unit_module", module_path)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
@@ -530,6 +538,134 @@ def _load_session_module(monkeypatch):
|
||||
return module
|
||||
|
||||
|
||||
def _load_agent_api_module(monkeypatch):
|
||||
_load_session_module(monkeypatch)
|
||||
repo_root = Path(__file__).resolve().parents[4]
|
||||
|
||||
agent_component_mod = ModuleType("agent.component")
|
||||
|
||||
class _StubAgentLLM:
|
||||
pass
|
||||
|
||||
agent_component_mod.LLM = _StubAgentLLM
|
||||
monkeypatch.setitem(sys.modules, "agent.component", agent_component_mod)
|
||||
|
||||
api_apps_mod = ModuleType("api.apps")
|
||||
api_apps_mod.__path__ = [str(repo_root / "api" / "apps")]
|
||||
api_apps_mod.login_required = lambda func: func
|
||||
monkeypatch.setitem(sys.modules, "api.apps", api_apps_mod)
|
||||
|
||||
api_apps_services_mod = ModuleType("api.apps.services")
|
||||
api_apps_services_mod.__path__ = [str(repo_root / "api" / "apps" / "services")]
|
||||
monkeypatch.setitem(sys.modules, "api.apps.services", api_apps_services_mod)
|
||||
|
||||
canvas_replica_mod = ModuleType("api.apps.services.canvas_replica_service")
|
||||
|
||||
class _StubCanvasReplicaService:
|
||||
@staticmethod
|
||||
def normalize_dsl(dsl):
|
||||
return dsl
|
||||
|
||||
@staticmethod
|
||||
def replace_for_set(**_kwargs):
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def bootstrap(**_kwargs):
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def load_for_run(**_kwargs):
|
||||
return {"dsl": {}, "title": "agent", "canvas_category": "agent"}
|
||||
|
||||
@staticmethod
|
||||
def commit_after_run(**_kwargs):
|
||||
return True
|
||||
|
||||
canvas_replica_mod.CanvasReplicaService = _StubCanvasReplicaService
|
||||
monkeypatch.setitem(sys.modules, "api.apps.services.canvas_replica_service", canvas_replica_mod)
|
||||
|
||||
file_service_mod = ModuleType("api.db.services.file_service")
|
||||
file_service_mod.FileService = SimpleNamespace(upload_info=lambda *_args, **_kwargs: {})
|
||||
monkeypatch.setitem(sys.modules, "api.db.services.file_service", file_service_mod)
|
||||
|
||||
api_service_mod = ModuleType("api.db.services.api_service")
|
||||
api_service_mod.API4ConversationService = SimpleNamespace(
|
||||
get_names=lambda *_args, **_kwargs: [],
|
||||
get_list=lambda *_args, **_kwargs: (0, []),
|
||||
save=lambda **_kwargs: True,
|
||||
get_by_id=lambda _session_id: (True, SimpleNamespace(to_dict=lambda: {"id": _session_id})),
|
||||
delete_by_id=lambda *_args, **_kwargs: True,
|
||||
)
|
||||
monkeypatch.setitem(sys.modules, "api.db.services.api_service", api_service_mod)
|
||||
|
||||
document_service_mod = ModuleType("api.db.services.document_service")
|
||||
document_service_mod.DocumentService = SimpleNamespace(
|
||||
clear_chunk_num_when_rerun=lambda *_args, **_kwargs: True,
|
||||
update_by_id=lambda *_args, **_kwargs: True,
|
||||
)
|
||||
monkeypatch.setitem(sys.modules, "api.db.services.document_service", document_service_mod)
|
||||
|
||||
knowledgebase_service_mod = ModuleType("api.db.services.knowledgebase_service")
|
||||
knowledgebase_service_mod.KnowledgebaseService = SimpleNamespace(query=lambda **_kwargs: [])
|
||||
monkeypatch.setitem(sys.modules, "api.db.services.knowledgebase_service", knowledgebase_service_mod)
|
||||
|
||||
task_service_mod = ModuleType("api.db.services.task_service")
|
||||
task_service_mod.CANVAS_DEBUG_DOC_ID = "debug-doc"
|
||||
task_service_mod.GRAPH_RAPTOR_FAKE_DOC_ID = "graph-raptor-fake-doc"
|
||||
task_service_mod.TaskService = SimpleNamespace(filter_delete=lambda *_args, **_kwargs: True)
|
||||
task_service_mod.queue_dataflow = lambda *_args, **_kwargs: (True, "")
|
||||
monkeypatch.setitem(sys.modules, "api.db.services.task_service", task_service_mod)
|
||||
|
||||
pipeline_operation_log_service_mod = ModuleType("api.db.services.pipeline_operation_log_service")
|
||||
pipeline_operation_log_service_mod.PipelineOperationLogService = SimpleNamespace(
|
||||
get_documents_info=lambda *_args, **_kwargs: [],
|
||||
update_by_id=lambda *_args, **_kwargs: True,
|
||||
)
|
||||
monkeypatch.setitem(
|
||||
sys.modules,
|
||||
"api.db.services.pipeline_operation_log_service",
|
||||
pipeline_operation_log_service_mod,
|
||||
)
|
||||
|
||||
user_service_mod = ModuleType("api.db.services.user_service")
|
||||
user_service_mod.TenantService = SimpleNamespace(get_joined_tenants_by_user_id=lambda *_args, **_kwargs: [])
|
||||
user_service_mod.UserService = SimpleNamespace(get_by_id=lambda *_args, **_kwargs: (False, None))
|
||||
user_service_mod.UserTenantService = SimpleNamespace(query=lambda **_kwargs: [])
|
||||
monkeypatch.setitem(sys.modules, "api.db.services.user_service", user_service_mod)
|
||||
|
||||
user_canvas_version_mod = ModuleType("api.db.services.user_canvas_version")
|
||||
user_canvas_version_mod.UserCanvasVersionService = SimpleNamespace(
|
||||
list_by_canvas_id=lambda *_args, **_kwargs: [],
|
||||
get_by_id=lambda *_args, **_kwargs: (False, None),
|
||||
get_latest_version_title=lambda *_args, **_kwargs: "",
|
||||
save_or_replace_latest=lambda **_kwargs: True,
|
||||
build_version_title=lambda *_args, **_kwargs: "v1",
|
||||
)
|
||||
monkeypatch.setitem(sys.modules, "api.db.services.user_canvas_version", user_canvas_version_mod)
|
||||
|
||||
rag_flow_pipeline_mod = ModuleType("rag.flow.pipeline")
|
||||
|
||||
class _StubPipeline:
|
||||
def __init__(self, *_args, **_kwargs):
|
||||
pass
|
||||
|
||||
rag_flow_pipeline_mod.Pipeline = _StubPipeline
|
||||
monkeypatch.setitem(sys.modules, "rag.flow.pipeline", rag_flow_pipeline_mod)
|
||||
|
||||
rag_redis_mod = ModuleType("rag.utils.redis_conn")
|
||||
rag_redis_mod.REDIS_CONN = SimpleNamespace(get=lambda *_args, **_kwargs: None)
|
||||
monkeypatch.setitem(sys.modules, "rag.utils.redis_conn", rag_redis_mod)
|
||||
|
||||
module_path = repo_root / "api" / "apps" / "restful_apis" / "agent_api.py"
|
||||
spec = importlib.util.spec_from_file_location("test_agent_api_unit_module", module_path)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
module.manager = _DummyManager()
|
||||
monkeypatch.setitem(sys.modules, "test_agent_api_unit_module", module)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
@pytest.mark.p2
|
||||
def test_create_and_update_guard_matrix(monkeypatch):
|
||||
module = _load_session_module(monkeypatch)
|
||||
@@ -734,34 +870,22 @@ def test_openai_nonstream_branch_unit(monkeypatch):
|
||||
|
||||
@pytest.mark.p2
|
||||
def test_agents_openai_compatibility_unit(monkeypatch):
|
||||
module = _load_session_module(monkeypatch)
|
||||
module = _load_agent_api_module(monkeypatch)
|
||||
|
||||
monkeypatch.setattr(module, "Response", _StubResponse)
|
||||
monkeypatch.setattr(module, "jsonify", lambda payload: payload)
|
||||
monkeypatch.setattr(module, "num_tokens_from_string", lambda text: len(text or ""))
|
||||
monkeypatch.setattr(module, "get_request_json", lambda: _AwaitableValue({"openai-compatible": True}))
|
||||
res = _run(inspect.unwrap(module.agent_chat_completion)("tenant-1"))
|
||||
assert "`agent_id` is required." in res["message"]
|
||||
|
||||
monkeypatch.setattr(module, "get_request_json", lambda: _AwaitableValue({"model": "model", "messages": []}))
|
||||
res = _run(inspect.unwrap(module.agents_completion_openai_compatibility)("tenant-1", "agent-1"))
|
||||
monkeypatch.setattr(
|
||||
module,
|
||||
"get_request_json",
|
||||
lambda: _AwaitableValue({"agent_id": "agent-1", "openai-compatible": True, "model": "model", "messages": []}),
|
||||
)
|
||||
res = _run(inspect.unwrap(module.agent_chat_completion)("tenant-1"))
|
||||
assert "at least one message" in res["message"]
|
||||
|
||||
monkeypatch.setattr(
|
||||
module,
|
||||
"get_request_json",
|
||||
lambda: _AwaitableValue({"model": "model", "messages": [{"role": "user", "content": "hello"}]}),
|
||||
)
|
||||
monkeypatch.setattr(module.UserCanvasService, "query", lambda **_kwargs: [])
|
||||
res = _run(inspect.unwrap(module.agents_completion_openai_compatibility)("tenant-1", "agent-1"))
|
||||
assert "don't own the agent" in res["message"]
|
||||
|
||||
monkeypatch.setattr(module.UserCanvasService, "query", lambda **_kwargs: [SimpleNamespace(id="agent-1")])
|
||||
monkeypatch.setattr(
|
||||
module,
|
||||
"get_request_json",
|
||||
lambda: _AwaitableValue({"model": "model", "messages": [{"role": "system", "content": "system only"}]}),
|
||||
)
|
||||
res = _run(inspect.unwrap(module.agents_completion_openai_compatibility)("tenant-1", "agent-1"))
|
||||
assert "No valid messages found" in json.dumps(res)
|
||||
|
||||
captured_calls = []
|
||||
|
||||
async def _completion_openai_stream(*args, **kwargs):
|
||||
@@ -774,6 +898,8 @@ def test_agents_openai_compatibility_unit(monkeypatch):
|
||||
"get_request_json",
|
||||
lambda: _AwaitableValue(
|
||||
{
|
||||
"agent_id": "agent-1",
|
||||
"openai-compatible": True,
|
||||
"model": "model",
|
||||
"messages": [
|
||||
{"role": "assistant", "content": "preface"},
|
||||
@@ -784,7 +910,7 @@ def test_agents_openai_compatibility_unit(monkeypatch):
|
||||
}
|
||||
),
|
||||
)
|
||||
resp = _run(inspect.unwrap(module.agents_completion_openai_compatibility)("tenant-1", "agent-1"))
|
||||
resp = _run(inspect.unwrap(module.agent_chat_completion)("tenant-1"))
|
||||
assert isinstance(resp, _StubResponse)
|
||||
assert resp.headers.get("Content-Type") == "text/event-stream; charset=utf-8"
|
||||
_run(_collect_stream(resp.body))
|
||||
@@ -795,11 +921,15 @@ def test_agents_openai_compatibility_unit(monkeypatch):
|
||||
yield {"id": "non-stream"}
|
||||
|
||||
monkeypatch.setattr(module, "completion_openai", _completion_openai_nonstream)
|
||||
monkeypatch.setattr(module.API4ConversationService, "get_by_id", lambda _session_id: (True, SimpleNamespace(dialog_id="agent-1")))
|
||||
monkeypatch.setattr(module.UserCanvasService, "accessible", lambda *_args, **_kwargs: True)
|
||||
monkeypatch.setattr(
|
||||
module,
|
||||
"get_request_json",
|
||||
lambda: _AwaitableValue(
|
||||
{
|
||||
"agent_id": "agent-1",
|
||||
"openai-compatible": True,
|
||||
"model": "model",
|
||||
"messages": [
|
||||
{"role": "user", "content": "first"},
|
||||
@@ -812,7 +942,7 @@ def test_agents_openai_compatibility_unit(monkeypatch):
|
||||
}
|
||||
),
|
||||
)
|
||||
res = _run(inspect.unwrap(module.agents_completion_openai_compatibility)("tenant-1", "agent-1"))
|
||||
res = _run(inspect.unwrap(module.agent_chat_completion)("tenant-1"))
|
||||
assert res["id"] == "non-stream"
|
||||
assert captured_calls[-1][0][2] == "final user"
|
||||
assert captured_calls[-1][1]["stream"] is False
|
||||
@@ -821,9 +951,11 @@ def test_agents_openai_compatibility_unit(monkeypatch):
|
||||
|
||||
@pytest.mark.p2
|
||||
def test_agent_completions_stream_and_nonstream_unit(monkeypatch):
|
||||
module = _load_session_module(monkeypatch)
|
||||
module = _load_agent_api_module(monkeypatch)
|
||||
|
||||
monkeypatch.setattr(module, "Response", _StubResponse)
|
||||
monkeypatch.setattr(module.API4ConversationService, "get_by_id", lambda _session_id: (True, SimpleNamespace(dialog_id="agent-1")))
|
||||
monkeypatch.setattr(module.UserCanvasService, "accessible", lambda *_args, **_kwargs: True)
|
||||
|
||||
async def _agent_stream(*_args, **_kwargs):
|
||||
yield "data:not-json"
|
||||
@@ -843,9 +975,20 @@ def test_agent_completions_stream_and_nonstream_unit(monkeypatch):
|
||||
yield "data:" + json.dumps({"event": "message", "data": {"content": "hello"}})
|
||||
|
||||
monkeypatch.setattr(module, "agent_completion", _agent_stream)
|
||||
monkeypatch.setattr(module, "get_request_json", lambda: _AwaitableValue({"stream": True, "return_trace": True}))
|
||||
monkeypatch.setattr(
|
||||
module,
|
||||
"get_request_json",
|
||||
lambda: _AwaitableValue(
|
||||
{
|
||||
"agent_id": "agent-1",
|
||||
"session_id": "session-1",
|
||||
"stream": True,
|
||||
"return_trace": True,
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
resp = _run(inspect.unwrap(module.agent_completions)("tenant-1", "agent-1"))
|
||||
resp = _run(inspect.unwrap(module.agent_chat_completion)("tenant-1"))
|
||||
chunks = _run(_collect_stream(resp.body))
|
||||
assert resp.headers.get("Content-Type") == "text/event-stream; charset=utf-8"
|
||||
assert any('"trace"' in chunk for chunk in chunks)
|
||||
@@ -874,8 +1017,19 @@ def test_agent_completions_stream_and_nonstream_unit(monkeypatch):
|
||||
)
|
||||
|
||||
monkeypatch.setattr(module, "agent_completion", _agent_nonstream)
|
||||
monkeypatch.setattr(module, "get_request_json", lambda: _AwaitableValue({"stream": False, "return_trace": True}))
|
||||
res = _run(inspect.unwrap(module.agent_completions)("tenant-1", "agent-1"))
|
||||
monkeypatch.setattr(
|
||||
module,
|
||||
"get_request_json",
|
||||
lambda: _AwaitableValue(
|
||||
{
|
||||
"agent_id": "agent-1",
|
||||
"session_id": "session-1",
|
||||
"stream": False,
|
||||
"return_trace": True,
|
||||
}
|
||||
),
|
||||
)
|
||||
res = _run(inspect.unwrap(module.agent_chat_completion)("tenant-1"))
|
||||
assert res["data"]["data"]["content"] == "A"
|
||||
assert res["data"]["data"]["reference"] == {"doc": "r"}
|
||||
assert res["data"]["data"]["structured"] == {
|
||||
@@ -884,64 +1038,7 @@ def test_agent_completions_stream_and_nonstream_unit(monkeypatch):
|
||||
"c4": {},
|
||||
}
|
||||
assert [item["component_id"] for item in res["data"]["data"]["trace"]] == ["c2", "c3", "c4"]
|
||||
|
||||
async def _agent_nonstream_broken(*_args, **_kwargs):
|
||||
yield "data:{"
|
||||
|
||||
monkeypatch.setattr(module, "agent_completion", _agent_nonstream_broken)
|
||||
monkeypatch.setattr(module, "get_request_json", lambda: _AwaitableValue({"stream": False, "return_trace": False}))
|
||||
res = _run(inspect.unwrap(module.agent_completions)("tenant-1", "agent-1"))
|
||||
assert res["data"].startswith("**ERROR**")
|
||||
|
||||
|
||||
@pytest.mark.p2
|
||||
def test_list_agent_session_projection_unit(monkeypatch):
|
||||
module = _load_session_module(monkeypatch)
|
||||
|
||||
monkeypatch.setattr(module, "request", SimpleNamespace(args=_Args({})))
|
||||
monkeypatch.setattr(module.UserCanvasService, "query", lambda **_kwargs: [SimpleNamespace(id="agent-1")])
|
||||
|
||||
conv_non_list_reference = {
|
||||
"id": "session-1",
|
||||
"dialog_id": "agent-1",
|
||||
"message": [{"role": "assistant", "content": "hello", "prompt": "internal"}],
|
||||
"reference": {"unexpected": "shape"},
|
||||
}
|
||||
monkeypatch.setattr(module.API4ConversationService, "get_list", lambda *_args, **_kwargs: (1, [conv_non_list_reference]))
|
||||
res = _run(inspect.unwrap(module.list_agent_session)("tenant-1", "agent-1"))
|
||||
assert res["data"][0]["agent_id"] == "agent-1"
|
||||
assert "prompt" not in res["data"][0]["messages"][0]
|
||||
|
||||
conv_with_chunks = {
|
||||
"id": "session-2",
|
||||
"dialog_id": "agent-1",
|
||||
"message": [
|
||||
{"role": "user", "content": "question"},
|
||||
{"role": "assistant", "content": "answer", "prompt": "internal"},
|
||||
],
|
||||
"reference": [
|
||||
{
|
||||
"chunks": [
|
||||
"not-a-dict",
|
||||
{
|
||||
"chunk_id": "chunk-2",
|
||||
"content_with_weight": "weighted",
|
||||
"doc_id": "doc-2",
|
||||
"docnm_kwd": "doc-name-2",
|
||||
"kb_id": "kb-2",
|
||||
"image_id": "img-2",
|
||||
"positions": [9],
|
||||
},
|
||||
]
|
||||
}
|
||||
],
|
||||
}
|
||||
monkeypatch.setattr(module.API4ConversationService, "get_list", lambda *_args, **_kwargs: (1, [conv_with_chunks]))
|
||||
res = _run(inspect.unwrap(module.list_agent_session)("tenant-1", "agent-1"))
|
||||
projected_chunk = res["data"][0]["messages"][1]["reference"][0]
|
||||
assert projected_chunk["image_id"] == "img-2"
|
||||
assert projected_chunk["positions"] == [9]
|
||||
|
||||
|
||||
|
||||
@pytest.mark.p2
|
||||
def test_delete_routes_partial_duplicate_unit(monkeypatch):
|
||||
|
||||
@@ -47,12 +47,12 @@ def test_list_agents_success_and_error(monkeypatch):
|
||||
captured["path"] = path
|
||||
captured["params"] = params
|
||||
captured["json"] = json
|
||||
return _DummyResponse({"code": 0, "data": [{"id": "agent-1", "title": "Agent One"}]})
|
||||
return _DummyResponse({"code": 0, "data": {"canvas": [{"id": "agent-1", "title": "Agent One"}], "total": 1}})
|
||||
|
||||
monkeypatch.setattr(client, "get", _ok_get)
|
||||
agents = client.list_agents(title="Agent One")
|
||||
agents = client.list_agents()
|
||||
assert captured["path"] == "/agents"
|
||||
assert captured["params"]["title"] == "Agent One"
|
||||
assert captured["params"] == {"page": 1, "page_size": 30, "orderby": "update_time", "desc": True}
|
||||
assert isinstance(agents[0], Agent), str(agents)
|
||||
assert agents[0].id == "agent-1", str(agents[0])
|
||||
assert agents[0].title == "Agent One", str(agents[0])
|
||||
|
||||
@@ -160,8 +160,10 @@ def test_session_module_streaming_and_helper_paths_unit(monkeypatch):
|
||||
assert calls[0][2]["session_id"] == "session-chat"
|
||||
assert calls[0][2]["temperature"] == 0.2
|
||||
assert calls[0][3] is True
|
||||
assert calls[1][1] == "/agents/agent-1/completions"
|
||||
assert calls[1][2]["question"] == "hello agent"
|
||||
assert calls[1][1] == "/agents/chat/completion"
|
||||
assert calls[1][2]["agent_id"] == "agent-1"
|
||||
assert calls[1][2]["query"] == "hello agent"
|
||||
assert calls[1][2]["session_id"] == "session-agent"
|
||||
assert calls[1][2]["openai-compatible"] is False
|
||||
assert calls[1][2]["top_p"] == 0.8
|
||||
assert calls[1][3] is True
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user