Add analysis feature
This commit is contained in:
@@ -9,6 +9,45 @@ from services.conversation_history import ConversationRecorder
|
||||
|
||||
|
||||
class ConversationRecorderTest(unittest.IsolatedAsyncioTestCase):
|
||||
async def test_completed_conversation_queues_enabled_analysis(self):
|
||||
conversation = SimpleNamespace(
|
||||
status="active",
|
||||
ended_at=None,
|
||||
message_count=0,
|
||||
analysis_status="none",
|
||||
analysis_data={},
|
||||
analysis_error="",
|
||||
)
|
||||
|
||||
class FakeSession:
|
||||
async def __aenter__(self):
|
||||
return self
|
||||
|
||||
async def __aexit__(self, *_args):
|
||||
return None
|
||||
|
||||
async def get(self, _model, _session_id):
|
||||
return conversation
|
||||
|
||||
async def commit(self):
|
||||
return None
|
||||
|
||||
plan = {
|
||||
"enabled": True,
|
||||
"model_resource_id": "model_001",
|
||||
"fields": [{"name": "customer_name", "type": "string"}],
|
||||
}
|
||||
recorder = ConversationRecorder("conv_test", plan)
|
||||
recorder._sequence = 1
|
||||
with patch(
|
||||
"services.conversation_history.SessionLocal",
|
||||
return_value=FakeSession(),
|
||||
):
|
||||
await recorder._finish(status="completed")
|
||||
|
||||
self.assertEqual(conversation.analysis_status, "pending")
|
||||
self.assertEqual(conversation.analysis_data, {"plan": plan})
|
||||
|
||||
async def test_finish_waits_for_database_cleanup_when_cancelled(self):
|
||||
recorder = ConversationRecorder("conv_test")
|
||||
started = asyncio.Event()
|
||||
|
||||
81
backend/tests/test_post_call_analysis.py
Normal file
81
backend/tests/test_post_call_analysis.py
Normal file
@@ -0,0 +1,81 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
|
||||
from pydantic import ValidationError
|
||||
from schemas import AnalysisConfig
|
||||
from services.post_call.analyzer import (
|
||||
_json_schema,
|
||||
_parse_json_content,
|
||||
_validated_result,
|
||||
)
|
||||
|
||||
|
||||
FIELDS = [
|
||||
{
|
||||
"id": "intent",
|
||||
"name": "customer_intent",
|
||||
"type": "enum",
|
||||
"description": "客户意向",
|
||||
"enum_values": ["high", "low"],
|
||||
},
|
||||
{
|
||||
"id": "follow_up",
|
||||
"name": "need_follow_up",
|
||||
"type": "boolean",
|
||||
"description": "是否需要跟进",
|
||||
"enum_values": [],
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
class AnalysisConfigTest(unittest.TestCase):
|
||||
def test_enabled_analysis_requires_model_and_fields(self):
|
||||
with self.assertRaises(ValidationError):
|
||||
AnalysisConfig(enabled=True)
|
||||
|
||||
def test_field_names_must_be_unique(self):
|
||||
field = {
|
||||
"id": "one",
|
||||
"name": "customer_name",
|
||||
"type": "string",
|
||||
"description": "客户姓名",
|
||||
}
|
||||
with self.assertRaises(ValidationError):
|
||||
AnalysisConfig(
|
||||
enabled=True,
|
||||
model_resource_id="model_001",
|
||||
fields=[field, {**field, "id": "two"}],
|
||||
)
|
||||
|
||||
|
||||
class StructuredExtractionTest(unittest.TestCase):
|
||||
def test_schema_marks_every_field_nullable_and_required(self):
|
||||
schema = _json_schema(FIELDS)
|
||||
self.assertEqual(
|
||||
schema["required"], ["customer_intent", "need_follow_up"]
|
||||
)
|
||||
self.assertEqual(
|
||||
schema["properties"]["customer_intent"]["enum"],
|
||||
["high", "low", None],
|
||||
)
|
||||
|
||||
def test_invalid_values_are_normalized_to_null(self):
|
||||
result = _validated_result(
|
||||
{"customer_intent": "medium", "need_follow_up": "yes"},
|
||||
FIELDS,
|
||||
)
|
||||
self.assertEqual(
|
||||
result,
|
||||
{"customer_intent": None, "need_follow_up": None},
|
||||
)
|
||||
|
||||
def test_json_parser_accepts_fenced_model_output(self):
|
||||
self.assertEqual(
|
||||
_parse_json_content('```json\n{"need_follow_up": true}\n```'),
|
||||
{"need_follow_up": True},
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user