Implement AI orchestration wedding demo

This commit is contained in:
pelpanagiotis
2026-06-27 13:50:11 +03:00
commit 55d4fe95b4
67 changed files with 4736 additions and 0 deletions

View File

@@ -0,0 +1,29 @@
from fastapi.testclient import TestClient
from app.infrastructure.database import PendingAction, SessionLocal, init_db
from app.main import app
def test_websocket_ingestion_creates_pending_action():
init_db()
session = SessionLocal()
session.query(PendingAction).delete()
session.commit()
session.close()
with TestClient(app) as client:
with client.websocket_connect("/ws/transcript") as socket:
socket.send_json(
{
"speaker": "Planner",
"timestamp": "2026-06-26T12:00:00Z",
"text": "Create a task for Maria to confirm the photographer.",
}
)
response = socket.receive_json()
assert response["ok"] is True
assert response["pending_action_id"] is not None
dashboard = client.get("/dashboard").json()
assert any(action["tool"] == "create_task" for action in dashboard["pending_actions"])

View File

@@ -0,0 +1,71 @@
import pytest
from app.ai.tool_registry import ToolRegistry
from app.domain.schemas import ActionStatus
from app.domain.schemas import ToolDefinition
from app.executor.action_executor import ActionExecutor
from app.infrastructure.database import PendingAction
from app.memory.memory_manager import MemoryManager
def test_memory_retrieves_semantic_items():
memory = MemoryManager()
memory.upsert("preference", "Customer prefers white flowers")
results = memory.retrieve("white flower preference")
assert results[0]["type"] == "preference"
@pytest.mark.asyncio
async def test_executor_rejects_unapproved_action():
registry = ToolRegistry(
[
ToolDefinition(
name="create_task",
description="Create task",
method="POST",
path="/tasks",
schema={"type": "object", "properties": {"title": {"type": "string"}}},
)
]
)
action = PendingAction(tool="create_task", status="pending_review", payload={"title": "x"})
with pytest.raises(ValueError, match="Only approved"):
await ActionExecutor(registry).execute(None, action) # type: ignore[arg-type]
@pytest.mark.asyncio
async def test_executor_generates_call_without_http_request():
registry = ToolRegistry(
[
ToolDefinition(
name="update_reservation",
description="Update reservation",
method="PATCH",
path="/reservations/{reservation_id}",
path_params=["reservation_id"],
schema={"type": "object", "properties": {"reservation_id": {"type": "string"}, "status": {"type": "string"}}},
)
]
)
action = PendingAction(tool="update_reservation", status=ActionStatus.APPROVED, payload={"reservation_id": "abc", "status": "ACCEPTED"})
class FakeSession:
def add(self, _action):
return None
def commit(self):
return None
def refresh(self, _action):
return None
result = await ActionExecutor(registry).execute(FakeSession(), action) # type: ignore[arg-type]
assert result.status == ActionStatus.APPROVED
assert result.execution_result["generated_only"] is True
assert result.execution_result["method"] == "PATCH"
assert result.execution_result["path"] == "/reservations/abc"
assert result.execution_result["json"] == {"status": "ACCEPTED"}

View File

@@ -0,0 +1,59 @@
from datetime import datetime
import pytest
from app.ai.tool_registry import ToolRegistry
from app.domain.schemas import PlannerDecision, ToolDefinition, TranscriptChunk
from app.planner.llm_planner import HybridPlanner
from app.validation.action_validator import ActionValidator
from app.validation.policy_engine import PolicyEngine
@pytest.mark.asyncio
async def test_fallback_planner_extracts_task():
planner = HybridPlanner()
chunk = TranscriptChunk(speaker="Bride", timestamp=datetime(2026, 6, 26, 12, 0), text="Create a task for Maria to confirm the photographer.")
decision = await planner.plan(chunk, {}, [], [])
assert decision.tool == "create_task"
assert decision.extracted_entities["assignee"] == "Maria"
assert decision.arguments["is_completed"] is False
assert "Suggested owner: Maria" in decision.arguments["description"]
assert "photographer" in decision.arguments["title"].lower()
def test_validator_rejects_hallucinated_parameters():
registry = ToolRegistry(
[
ToolDefinition(
name="create_task",
description="Create task",
method="POST",
path="/tasks",
schema={"type": "object", "required": ["title"], "properties": {"title": {"type": "string"}}},
required=["title"],
)
]
)
decision = PlannerDecision(
intent="create_task",
confidence=0.9,
reasoning="test",
tool="create_task",
arguments={"title": "Confirm photographer", "unexpected": True},
)
result = ActionValidator(registry).validate(decision)
assert not result.valid
assert "Hallucinated" in result.errors[0]
def test_policy_rejects_low_confidence():
decision = PlannerDecision(intent="create_note", confidence=0.2, reasoning="weak", tool="create_note", arguments={"content": "x"})
result = PolicyEngine().evaluate(decision)
assert not result.allowed
assert "below threshold" in result.reasons[0]

View File

@@ -0,0 +1,17 @@
from app.infrastructure.soniox import group_by_speaker
def test_group_by_speaker_flushes_on_sentence_and_speaker_change():
segments = group_by_speaker(
[
{"speaker": "Bride", "text": "Create ", "start_ms": 0, "end_ms": 100},
{"speaker": "Bride", "text": "a task.", "start_ms": 100, "end_ms": 500},
{"speaker": "Planner", "text": "Add ", "start_ms": 600, "end_ms": 700},
{"speaker": "Planner", "text": "a note.", "start_ms": 700, "end_ms": 1000},
]
)
assert len(segments) == 2
assert segments[0].speaker == "Bride"
assert segments[0].edited_text == "Create a task."
assert segments[1].speaker == "Planner"

View File

@@ -0,0 +1,63 @@
from app.ai.tool_generator import OpenAPIToolGenerator
def test_generates_tools_from_openapi_spec():
spec = {
"paths": {
"/tasks": {
"post": {
"operationId": "create_task",
"summary": "Create task",
"requestBody": {
"content": {
"application/json": {
"schema": {"$ref": "#/components/schemas/TaskCreate"}
}
}
},
}
}
},
"components": {
"schemas": {
"TaskCreate": {
"type": "object",
"required": ["title"],
"properties": {"title": {"type": "string"}},
}
}
},
}
tools = OpenAPIToolGenerator(spec).generate()
assert tools[0].name == "create_task"
assert tools[0].required == ["title"]
assert tools[0].path == "/tasks"
def test_generates_path_params_for_patch_tools():
spec = {
"paths": {
"/reservations/{reservation_id}": {
"patch": {
"operationId": "update_reservation",
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {"status": {"type": "string"}},
}
}
}
},
}
}
}
}
tools = OpenAPIToolGenerator(spec).generate()
assert tools[0].path_params == ["reservation_id"]
assert "reservation_id" in tools[0].schema["required"]