164 lines
5.2 KiB
Python
164 lines
5.2 KiB
Python
from __future__ import annotations
|
|
|
|
import os
|
|
|
|
import pytest
|
|
import pytest_asyncio
|
|
from sqlalchemy import JSON, select
|
|
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
|
|
|
|
os.environ["DATABASE_URL"] = "postgresql+asyncpg://tester:tester@localhost:5432/pmflow_test"
|
|
|
|
from app.models.base import Base
|
|
from app.models.mounted_ai_runtime import (
|
|
MountedAgentTrace,
|
|
MountedConversationRun,
|
|
MountedTopicWorkspace,
|
|
)
|
|
from app.services.mounted_ai_runtime_write_service import MountedAIRuntimeWriteService
|
|
|
|
|
|
def _use_sqlite_json_types() -> None:
|
|
for table in (
|
|
MountedTopicWorkspace.__table__,
|
|
MountedConversationRun.__table__,
|
|
MountedAgentTrace.__table__,
|
|
):
|
|
for column in table.columns:
|
|
if column.name in {
|
|
"facts_json",
|
|
"goals_json",
|
|
"constraints_json",
|
|
"options_json",
|
|
"open_questions",
|
|
"pending_actions",
|
|
"bound_entities",
|
|
"metadata_json",
|
|
"context_layers_hit",
|
|
"tool_calls",
|
|
"execution_outcome",
|
|
"input_summary",
|
|
"output_summary",
|
|
"tool_trace",
|
|
}:
|
|
column.type = JSON()
|
|
|
|
|
|
@pytest_asyncio.fixture
|
|
async def db_session():
|
|
engine = create_async_engine("sqlite+aiosqlite:///:memory:")
|
|
_use_sqlite_json_types()
|
|
async with engine.begin() as conn:
|
|
await conn.run_sync(
|
|
lambda sync_conn: Base.metadata.create_all(
|
|
sync_conn,
|
|
tables=[
|
|
MountedTopicWorkspace.__table__,
|
|
MountedConversationRun.__table__,
|
|
MountedAgentTrace.__table__,
|
|
],
|
|
)
|
|
)
|
|
maker = async_sessionmaker(engine, expire_on_commit=False)
|
|
async with maker() as session:
|
|
yield session
|
|
await engine.dispose()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_begin_ingress_run_uses_thread_anchor_and_sets_runtime_context(db_session) -> None:
|
|
service = MountedAIRuntimeWriteService(db_session)
|
|
|
|
tracking = await service.begin_ingress_run(
|
|
tenant_id=1,
|
|
source_system="feishu",
|
|
source_message_id="om_message_1",
|
|
message_text="帮我复盘一下这个直播需求。",
|
|
sender="林宇航",
|
|
channel_ref="chat_1",
|
|
thread_context={
|
|
"thread_id": "thread_1",
|
|
"root_message_id": "om_root_1",
|
|
"reply_to_message_id": "om_reply_1",
|
|
},
|
|
)
|
|
await db_session.commit()
|
|
|
|
saved_topic = (
|
|
await db_session.execute(select(MountedTopicWorkspace))
|
|
).scalar_one()
|
|
saved_run = (
|
|
await db_session.execute(select(MountedConversationRun))
|
|
).scalar_one()
|
|
|
|
assert tracking.topic_workspace.id == saved_topic.id
|
|
assert saved_topic.topic_key == "feishu:chat_1:root:om_root_1"
|
|
assert saved_topic.thread_key == "thread_1"
|
|
assert saved_topic.last_message_id == "om_message_1"
|
|
assert saved_run.status == "running"
|
|
assert saved_run.mode_before is None
|
|
assert saved_run.context_layers_hit == {
|
|
"current_message": True,
|
|
"thread": True,
|
|
"topic": False,
|
|
}
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_record_decision_and_finalize_run_persist_topic_mode_and_traces(db_session) -> None:
|
|
service = MountedAIRuntimeWriteService(db_session)
|
|
|
|
tracking = await service.begin_ingress_run(
|
|
tenant_id=1,
|
|
source_system="feishu",
|
|
source_message_id="om_message_2",
|
|
message_text="分配给谁做比较合适?",
|
|
sender="林宇航",
|
|
channel_ref="chat_2",
|
|
thread_context={"root_message_id": "om_root_2"},
|
|
)
|
|
|
|
await service.record_orchestrator_decision(
|
|
tracking=tracking,
|
|
workflow_route="project_signal.create",
|
|
intent_name="signal_create",
|
|
intent_confidence=0.82,
|
|
trigger_reason="manual_review",
|
|
auto_execute=False,
|
|
work_item_id=321,
|
|
route_metadata={"workflow_family": "project_signal", "workflow_action": "create"},
|
|
)
|
|
await service.finalize_run(
|
|
tracking=tracking,
|
|
result={
|
|
"handled": True,
|
|
"status": "captured",
|
|
"classification": "command",
|
|
"work_item_id": 321,
|
|
"workflow_route": "project_signal.create",
|
|
"direct_reply_text": "我先帮你收敛需求,再给负责人建议。",
|
|
},
|
|
)
|
|
await db_session.commit()
|
|
|
|
saved_topic = (
|
|
await db_session.execute(select(MountedTopicWorkspace))
|
|
).scalar_one()
|
|
saved_run = (
|
|
await db_session.execute(select(MountedConversationRun))
|
|
).scalar_one()
|
|
traces = (
|
|
await db_session.execute(
|
|
select(MountedAgentTrace).order_by(MountedAgentTrace.id.asc())
|
|
)
|
|
).scalars().all()
|
|
|
|
assert saved_topic.mode == "plan"
|
|
assert saved_topic.metadata_json["last_workflow_route"] == "project_signal.create"
|
|
assert saved_run.mode_after == "plan"
|
|
assert saved_run.status == "completed"
|
|
assert saved_run.final_reply == "我先帮你收敛需求,再给负责人建议。"
|
|
assert len(traces) == 2
|
|
assert traces[0].agent_name == "ConversationOrchestrator"
|
|
assert traces[1].agent_name == "ReplyComposer"
|