feat(ai): phase B function calling in-app (agent loop + endpoint /agent)
CI / lint (push) Successful in 57s
CI / security (push) Successful in 39s
CI / test (push) Successful in 1m13s
CI / build (push) Successful in 36s
CI / e2e (push) Successful in 10m13s
Desktop Build / build-windows (push) Canceled after 0s
Desktop Build / build-linux (push) Canceled after 0s
CI / lint (push) Successful in 57s
CI / security (push) Successful in 39s
CI / test (push) Successful in 1m13s
CI / build (push) Successful in 36s
CI / e2e (push) Successful in 10m13s
Desktop Build / build-windows (push) Canceled after 0s
Desktop Build / build-linux (push) Canceled after 0s
- backend/ai_chat.py: chat_completion provider-agnostique (OpenAI-compat tools/tool_calls + Gemini functionDeclarations/functionCall), retry sans tools si rejete - backend/agent/loop.py: run_agent multi-etapes (limite 10, truncation, confirmation two-step), LLM injectable - endpoint opt-in POST /api/ai/bookslm/agent (events SSE tool/message/confirmation), extraction _resolve_system_prompt - tests: test_ai_chat.py, test_agent_loop.py + 3 tests endpoint (728 passed au total) - ROADMAP B1/B2/B3/B7 livres ; B4/B5/B6 restants
This commit is contained in:
@@ -746,3 +746,80 @@ class TestBooksLMChatModes:
|
||||
)
|
||||
assert resp.status_code in (200, 500), f"unexpected {resp.status_code}: {resp.text[:200]}"
|
||||
assert resp.status_code != 422
|
||||
|
||||
|
||||
# ── Tool-calling agent endpoint ───────────────────────────────────────
|
||||
|
||||
|
||||
class TestBooksLMAgentEndpoint:
|
||||
"""Tests for POST /api/ai/bookslm/agent (Phase B)."""
|
||||
|
||||
def test_agent_requires_auth(self, bookslm_client):
|
||||
resp = bookslm_client.post(
|
||||
"/api/ai/bookslm/agent",
|
||||
json={"vault": "TestVault", "directory": "", "message": "Hello"},
|
||||
)
|
||||
assert resp.status_code == 401
|
||||
|
||||
def test_agent_tool_call_flow(self, bookslm_client, monkeypatch):
|
||||
import backend.bookslm_routes as routes
|
||||
from backend.ai_chat import LLMResponse, ToolCall
|
||||
|
||||
responses = [
|
||||
LLMResponse(tool_calls=[ToolCall(id="1", name="list_directory", arguments={"vault": "TestVault"})]),
|
||||
LLMResponse(content="Il y a des fichiers."),
|
||||
]
|
||||
|
||||
async def fake_chat_completion(messages, **kwargs):
|
||||
return responses.pop(0)
|
||||
|
||||
monkeypatch.setattr(routes, "chat_completion", fake_chat_completion)
|
||||
monkeypatch.setattr(routes, "_resolve_provider_name", lambda requested: "deepseek")
|
||||
|
||||
token, _ = _login_bookslm(bookslm_client)
|
||||
resp = bookslm_client.post(
|
||||
"/api/ai/bookslm/agent",
|
||||
json={"vault": "TestVault", "directory": "", "message": "liste les fichiers", "mode": "directory"},
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
body = resp.text
|
||||
assert "event: tool" in body
|
||||
assert "list_directory" in body
|
||||
assert "event: message" in body
|
||||
assert "Il y a des fichiers." in body
|
||||
|
||||
def test_agent_pauses_for_confirmation(self, bookslm_client, monkeypatch):
|
||||
import backend.bookslm_routes as routes
|
||||
from backend.ai_chat import LLMResponse, ToolCall
|
||||
from backend.tools import registry
|
||||
from backend.tools.api import ToolRisk
|
||||
from backend.tools.registry import ToolSpec
|
||||
from backend.tools.schemas import ListVaultsInput
|
||||
|
||||
spec = ToolSpec(
|
||||
name="_agent_write",
|
||||
description="write for tests",
|
||||
input_model=ListVaultsInput,
|
||||
handler=lambda ctx, params: {"ok": True},
|
||||
risk=ToolRisk.WRITE,
|
||||
)
|
||||
monkeypatch.setitem(registry._REGISTRY, "_agent_write", spec)
|
||||
|
||||
responses = [LLMResponse(tool_calls=[ToolCall(id="1", name="_agent_write", arguments={})])]
|
||||
|
||||
async def fake_chat_completion(messages, **kwargs):
|
||||
return responses.pop(0)
|
||||
|
||||
monkeypatch.setattr(routes, "chat_completion", fake_chat_completion)
|
||||
monkeypatch.setattr(routes, "_resolve_provider_name", lambda requested: "deepseek")
|
||||
|
||||
token, _ = _login_bookslm(bookslm_client)
|
||||
resp = bookslm_client.post(
|
||||
"/api/ai/bookslm/agent",
|
||||
json={"vault": "TestVault", "directory": "", "message": "crée un fichier", "mode": "directory"},
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
assert "event: confirmation" in resp.text
|
||||
assert "_agent_write" in resp.text
|
||||
|
||||
Reference in New Issue
Block a user