- Implementation of specialist execution api, including SSE protocol

- eveai_chat becomes deprecated and should be replaced with SSE
- Adaptation of STANDARD_RAG specialist
- Base class definition allowing to realise specialists with crewai framework
- Implementation of SPIN_SPECIALIST
- Implementation of test app for testing specialists (test_specialist_client). Also serves as an example for future SSE-based client
- Improvements to startup scripts to better handle and scale multiple connections
- Small improvements to the interaction forms and views
- Caching implementation improved and augmented with additional caches
This commit is contained in:
Josako
2025-02-20 05:50:16 +01:00
parent d106520d22
commit 25213f2004
79 changed files with 2791 additions and 347 deletions

View File

@@ -13,10 +13,9 @@ from common.extensions import db, cache_manager
from common.utils.celery_utils import current_celery
from common.utils.business_event import BusinessEvent
from common.utils.business_event_context import current_event
from config.type_defs.specialist_types import SPECIALIST_TYPES
from eveai_chat_workers.specialists.registry import SpecialistRegistry
from config.type_defs.retriever_types import RETRIEVER_TYPES
from eveai_chat_workers.specialists.specialist_typing import SpecialistArguments
from eveai_chat_workers.specialists.base_specialist import get_specialist_class
from common.utils.execution_progress import ExecutionProgressTracker
# Healthcheck task
@@ -30,18 +29,19 @@ class ArgumentPreparationError(Exception):
pass
def validate_specialist_arguments(specialist_type: str, arguments: Dict[str, Any]) -> None:
def validate_specialist_arguments(specialist_type: str, specialist_type_version:str, arguments: Dict[str, Any]) -> None:
"""
Validate specialist-specific arguments
Args:
specialist_type: Type of specialist
specialist_type_version: Version of specialist type
arguments: Arguments to validate (excluding retriever-specific arguments)
Raises:
ArgumentPreparationError: If validation fails
"""
specialist_config = SPECIALIST_TYPES.get(specialist_type)
specialist_config = cache_manager.specialists_config_cache.get_config(specialist_type, specialist_type_version)
if not specialist_config:
raise ArgumentPreparationError(f"Unknown specialist type: {specialist_type}")
@@ -61,20 +61,21 @@ def validate_specialist_arguments(specialist_type: str, arguments: Dict[str, Any
raise ArgumentPreparationError(f"Argument '{arg_name}' must be an integer")
def validate_retriever_arguments(retriever_type: str, arguments: Dict[str, Any],
def validate_retriever_arguments(retriever_type: str, retriever_type_version: str, arguments: Dict[str, Any],
catalog_config: Optional[Dict[str, Any]] = None) -> None:
"""
Validate retriever-specific arguments
Args:
retriever_type: Type of retriever
retriever_type_version: Version of retriever type
arguments: Arguments to validate
catalog_config: Optional catalog configuration for metadata validation
Raises:
ArgumentPreparationError: If validation fails
"""
retriever_config = RETRIEVER_TYPES.get(retriever_type)
retriever_config = cache_manager.retrievers_config_cache.get_config(retriever_type, retriever_type_version)
if not retriever_config:
raise ArgumentPreparationError(f"Unknown retriever type: {retriever_type}")
@@ -141,7 +142,7 @@ def prepare_arguments(specialist: Any, arguments: Dict[str, Any]) -> Dict[str, A
specialist_args[key] = value
# Validate specialist arguments
validate_specialist_arguments(specialist.type, specialist_args)
validate_specialist_arguments(specialist.type, specialist.type_version, specialist_args)
# Get all retrievers associated with this specialist
specialist_retrievers = (
@@ -177,10 +178,11 @@ def prepare_arguments(specialist: Any, arguments: Dict[str, Any]) -> Dict[str, A
# Always include the retriever type
inherited_args['type'] = retriever.type
inherited_args['type_version'] = retriever.type_version
# Validate the combined arguments
validate_retriever_arguments(
retriever.type,
retriever.type, retriever.type_version,
inherited_args,
catalog_config
)
@@ -202,9 +204,9 @@ def prepare_arguments(specialist: Any, arguments: Dict[str, Any]) -> Dict[str, A
raise ArgumentPreparationError(str(e))
@current_celery.task(name='execute_specialist', queue='llm_interactions')
def execute_specialist(tenant_id: int, specialist_id: int, arguments: Dict[str, Any],
session_id: str, user_timezone: str, room: str) -> dict:
@current_celery.task(name='execute_specialist', queue='llm_interactions', bind=True)
def execute_specialist(self, tenant_id: int, specialist_id: int, arguments: Dict[str, Any],
session_id: str, user_timezone: str) -> dict:
"""
Execute a specialist with given arguments
@@ -214,15 +216,16 @@ def execute_specialist(tenant_id: int, specialist_id: int, arguments: Dict[str,
arguments: Dictionary containing all required arguments for specialist and retrievers
session_id: Chat session ID
user_timezone: User's timezone
room: Socket.IO room for the response
Returns:
dict: {
'result': Dict - Specialist execution result
'interaction_id': int - Created interaction ID
'room': str - Socket.IO room
}
"""
task_id = self.request.id
ept = ExecutionProgressTracker()
ept.send_update(task_id, "EveAI Specialist Started", {})
with BusinessEvent("Execute Specialist", tenant_id=tenant_id, chat_session_id=session_id) as event:
current_app.logger.info(
f'execute_specialist: Processing request for tenant {tenant_id} using specialist {specialist_id}')
@@ -241,6 +244,10 @@ def execute_specialist(tenant_id: int, specialist_id: int, arguments: Dict[str,
session_id,
create_params={'timezone': user_timezone}
)
if cached_session:
current_app.logger.debug(f"Cached Session successfully retrieved for {session_id}: {cached_session.id}")
else:
current_app.logger.debug(f"No Cached Session retrieved for {session_id}")
# Get specialist from database
specialist = Specialist.query.get_or_404(specialist_id)
@@ -251,6 +258,7 @@ def execute_specialist(tenant_id: int, specialist_id: int, arguments: Dict[str,
# Convert the prepared arguments into a SpecialistArguments instance
complete_arguments = SpecialistArguments.create(
type_name=specialist.type,
type_version=specialist.type_version,
specialist_args={k: v for k, v in raw_arguments.items() if k != 'retriever_arguments'},
retriever_args=raw_arguments.get('retriever_arguments', {})
)
@@ -276,12 +284,14 @@ def execute_specialist(tenant_id: int, specialist_id: int, arguments: Dict[str,
raise
with current_event.create_span("Specialist invocation"):
ept.send_update(task_id, "EveAI Specialist Start", {})
# Initialize specialist instance
specialist_class = SpecialistRegistry.get_specialist_class(specialist.type)
specialist_class = get_specialist_class(specialist.type, specialist.type_version)
specialist_instance = specialist_class(
tenant_id=tenant_id,
specialist_id=specialist_id,
session_id=session_id,
task_id=task_id,
)
# Execute specialist
@@ -304,13 +314,14 @@ def execute_specialist(tenant_id: int, specialist_id: int, arguments: Dict[str,
# Prepare response
response = {
'result': result.model_dump(),
'interaction_id': new_interaction.id,
'room': room
'interaction_id': new_interaction.id
}
ept.send_update(task_id, "EveAI Specialist Complete", response)
return response
except Exception as e:
ept.send_update(task_id, "EveAI Specialist Error", {'Error': str(e)})
current_app.logger.error(f'execute_specialist: Error executing specialist: {e}')
raise