atlas-chat 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- atlas/__init__.py +40 -0
- atlas/application/__init__.py +7 -0
- atlas/application/chat/__init__.py +7 -0
- atlas/application/chat/agent/__init__.py +10 -0
- atlas/application/chat/agent/act_loop.py +179 -0
- atlas/application/chat/agent/factory.py +142 -0
- atlas/application/chat/agent/protocols.py +46 -0
- atlas/application/chat/agent/react_loop.py +338 -0
- atlas/application/chat/agent/think_act_loop.py +171 -0
- atlas/application/chat/approval_manager.py +151 -0
- atlas/application/chat/elicitation_manager.py +191 -0
- atlas/application/chat/events/__init__.py +1 -0
- atlas/application/chat/events/agent_event_relay.py +112 -0
- atlas/application/chat/modes/__init__.py +1 -0
- atlas/application/chat/modes/agent.py +125 -0
- atlas/application/chat/modes/plain.py +74 -0
- atlas/application/chat/modes/rag.py +81 -0
- atlas/application/chat/modes/tools.py +179 -0
- atlas/application/chat/orchestrator.py +213 -0
- atlas/application/chat/policies/__init__.py +1 -0
- atlas/application/chat/policies/tool_authorization.py +99 -0
- atlas/application/chat/preprocessors/__init__.py +1 -0
- atlas/application/chat/preprocessors/message_builder.py +92 -0
- atlas/application/chat/preprocessors/prompt_override_service.py +104 -0
- atlas/application/chat/service.py +454 -0
- atlas/application/chat/utilities/__init__.py +6 -0
- atlas/application/chat/utilities/error_handler.py +367 -0
- atlas/application/chat/utilities/event_notifier.py +546 -0
- atlas/application/chat/utilities/file_processor.py +613 -0
- atlas/application/chat/utilities/tool_executor.py +789 -0
- atlas/atlas_chat_cli.py +347 -0
- atlas/atlas_client.py +238 -0
- atlas/core/__init__.py +0 -0
- atlas/core/auth.py +205 -0
- atlas/core/authorization_manager.py +27 -0
- atlas/core/capabilities.py +123 -0
- atlas/core/compliance.py +215 -0
- atlas/core/domain_whitelist.py +147 -0
- atlas/core/domain_whitelist_middleware.py +82 -0
- atlas/core/http_client.py +28 -0
- atlas/core/log_sanitizer.py +102 -0
- atlas/core/metrics_logger.py +59 -0
- atlas/core/middleware.py +131 -0
- atlas/core/otel_config.py +242 -0
- atlas/core/prompt_risk.py +200 -0
- atlas/core/rate_limit.py +0 -0
- atlas/core/rate_limit_middleware.py +64 -0
- atlas/core/security_headers_middleware.py +51 -0
- atlas/domain/__init__.py +37 -0
- atlas/domain/chat/__init__.py +1 -0
- atlas/domain/chat/dtos.py +85 -0
- atlas/domain/errors.py +96 -0
- atlas/domain/messages/__init__.py +12 -0
- atlas/domain/messages/models.py +160 -0
- atlas/domain/rag_mcp_service.py +664 -0
- atlas/domain/sessions/__init__.py +7 -0
- atlas/domain/sessions/models.py +36 -0
- atlas/domain/unified_rag_service.py +371 -0
- atlas/infrastructure/__init__.py +10 -0
- atlas/infrastructure/app_factory.py +135 -0
- atlas/infrastructure/events/__init__.py +1 -0
- atlas/infrastructure/events/cli_event_publisher.py +140 -0
- atlas/infrastructure/events/websocket_publisher.py +140 -0
- atlas/infrastructure/sessions/in_memory_repository.py +56 -0
- atlas/infrastructure/transport/__init__.py +7 -0
- atlas/infrastructure/transport/websocket_connection_adapter.py +33 -0
- atlas/init_cli.py +226 -0
- atlas/interfaces/__init__.py +15 -0
- atlas/interfaces/events.py +134 -0
- atlas/interfaces/llm.py +54 -0
- atlas/interfaces/rag.py +40 -0
- atlas/interfaces/sessions.py +75 -0
- atlas/interfaces/tools.py +57 -0
- atlas/interfaces/transport.py +24 -0
- atlas/main.py +564 -0
- atlas/mcp/api_key_demo/README.md +76 -0
- atlas/mcp/api_key_demo/main.py +172 -0
- atlas/mcp/api_key_demo/run.sh +56 -0
- atlas/mcp/basictable/main.py +147 -0
- atlas/mcp/calculator/main.py +149 -0
- atlas/mcp/code-executor/execution_engine.py +98 -0
- atlas/mcp/code-executor/execution_environment.py +95 -0
- atlas/mcp/code-executor/main.py +528 -0
- atlas/mcp/code-executor/result_processing.py +276 -0
- atlas/mcp/code-executor/script_generation.py +195 -0
- atlas/mcp/code-executor/security_checker.py +140 -0
- atlas/mcp/corporate_cars/main.py +437 -0
- atlas/mcp/csv_reporter/main.py +545 -0
- atlas/mcp/duckduckgo/main.py +182 -0
- atlas/mcp/elicitation_demo/README.md +171 -0
- atlas/mcp/elicitation_demo/main.py +262 -0
- atlas/mcp/env-demo/README.md +158 -0
- atlas/mcp/env-demo/main.py +199 -0
- atlas/mcp/file_size_test/main.py +284 -0
- atlas/mcp/filesystem/main.py +348 -0
- atlas/mcp/image_demo/main.py +113 -0
- atlas/mcp/image_demo/requirements.txt +4 -0
- atlas/mcp/logging_demo/README.md +72 -0
- atlas/mcp/logging_demo/main.py +103 -0
- atlas/mcp/many_tools_demo/main.py +50 -0
- atlas/mcp/order_database/__init__.py +0 -0
- atlas/mcp/order_database/main.py +369 -0
- atlas/mcp/order_database/signal_data.csv +1001 -0
- atlas/mcp/pdfbasic/main.py +394 -0
- atlas/mcp/pptx_generator/main.py +760 -0
- atlas/mcp/pptx_generator/requirements.txt +13 -0
- atlas/mcp/pptx_generator/run_test.sh +1 -0
- atlas/mcp/pptx_generator/test_pptx_generator_security.py +169 -0
- atlas/mcp/progress_demo/main.py +167 -0
- atlas/mcp/progress_updates_demo/QUICKSTART.md +273 -0
- atlas/mcp/progress_updates_demo/README.md +120 -0
- atlas/mcp/progress_updates_demo/main.py +497 -0
- atlas/mcp/prompts/main.py +222 -0
- atlas/mcp/public_demo/main.py +189 -0
- atlas/mcp/sampling_demo/README.md +169 -0
- atlas/mcp/sampling_demo/main.py +234 -0
- atlas/mcp/thinking/main.py +77 -0
- atlas/mcp/tool_planner/main.py +240 -0
- atlas/mcp/ui-demo/badmesh.png +0 -0
- atlas/mcp/ui-demo/main.py +383 -0
- atlas/mcp/ui-demo/templates/button_demo.html +32 -0
- atlas/mcp/ui-demo/templates/data_visualization.html +32 -0
- atlas/mcp/ui-demo/templates/form_demo.html +28 -0
- atlas/mcp/username-override-demo/README.md +320 -0
- atlas/mcp/username-override-demo/main.py +308 -0
- atlas/modules/__init__.py +0 -0
- atlas/modules/config/__init__.py +34 -0
- atlas/modules/config/cli.py +231 -0
- atlas/modules/config/config_manager.py +1096 -0
- atlas/modules/file_storage/__init__.py +22 -0
- atlas/modules/file_storage/cli.py +330 -0
- atlas/modules/file_storage/content_extractor.py +290 -0
- atlas/modules/file_storage/manager.py +295 -0
- atlas/modules/file_storage/mock_s3_client.py +402 -0
- atlas/modules/file_storage/s3_client.py +417 -0
- atlas/modules/llm/__init__.py +19 -0
- atlas/modules/llm/caller.py +287 -0
- atlas/modules/llm/litellm_caller.py +675 -0
- atlas/modules/llm/models.py +19 -0
- atlas/modules/mcp_tools/__init__.py +17 -0
- atlas/modules/mcp_tools/client.py +2123 -0
- atlas/modules/mcp_tools/token_storage.py +556 -0
- atlas/modules/prompts/prompt_provider.py +130 -0
- atlas/modules/rag/__init__.py +24 -0
- atlas/modules/rag/atlas_rag_client.py +336 -0
- atlas/modules/rag/client.py +129 -0
- atlas/routes/admin_routes.py +865 -0
- atlas/routes/config_routes.py +484 -0
- atlas/routes/feedback_routes.py +361 -0
- atlas/routes/files_routes.py +274 -0
- atlas/routes/health_routes.py +40 -0
- atlas/routes/mcp_auth_routes.py +223 -0
- atlas/server_cli.py +164 -0
- atlas/tests/conftest.py +20 -0
- atlas/tests/integration/test_mcp_auth_integration.py +152 -0
- atlas/tests/manual_test_sampling.py +87 -0
- atlas/tests/modules/mcp_tools/test_client_auth.py +226 -0
- atlas/tests/modules/mcp_tools/test_client_env.py +191 -0
- atlas/tests/test_admin_mcp_server_management_routes.py +141 -0
- atlas/tests/test_agent_roa.py +135 -0
- atlas/tests/test_app_factory_smoke.py +47 -0
- atlas/tests/test_approval_manager.py +439 -0
- atlas/tests/test_atlas_client.py +188 -0
- atlas/tests/test_atlas_rag_client.py +447 -0
- atlas/tests/test_atlas_rag_integration.py +224 -0
- atlas/tests/test_attach_file_flow.py +287 -0
- atlas/tests/test_auth_utils.py +165 -0
- atlas/tests/test_backend_public_url.py +185 -0
- atlas/tests/test_banner_logging.py +287 -0
- atlas/tests/test_capability_tokens_and_injection.py +203 -0
- atlas/tests/test_compliance_level.py +54 -0
- atlas/tests/test_compliance_manager.py +253 -0
- atlas/tests/test_config_manager.py +617 -0
- atlas/tests/test_config_manager_paths.py +12 -0
- atlas/tests/test_core_auth.py +18 -0
- atlas/tests/test_core_utils.py +190 -0
- atlas/tests/test_docker_env_sync.py +202 -0
- atlas/tests/test_domain_errors.py +329 -0
- atlas/tests/test_domain_whitelist.py +359 -0
- atlas/tests/test_elicitation_manager.py +408 -0
- atlas/tests/test_elicitation_routing.py +296 -0
- atlas/tests/test_env_demo_server.py +88 -0
- atlas/tests/test_error_classification.py +113 -0
- atlas/tests/test_error_flow_integration.py +116 -0
- atlas/tests/test_feedback_routes.py +333 -0
- atlas/tests/test_file_content_extraction.py +1134 -0
- atlas/tests/test_file_extraction_routes.py +158 -0
- atlas/tests/test_file_library.py +107 -0
- atlas/tests/test_file_manager_unit.py +18 -0
- atlas/tests/test_health_route.py +49 -0
- atlas/tests/test_http_client_stub.py +8 -0
- atlas/tests/test_imports_smoke.py +30 -0
- atlas/tests/test_interfaces_llm_response.py +9 -0
- atlas/tests/test_issue_access_denied_fix.py +136 -0
- atlas/tests/test_llm_env_expansion.py +836 -0
- atlas/tests/test_log_level_sensitive_data.py +285 -0
- atlas/tests/test_mcp_auth_routes.py +341 -0
- atlas/tests/test_mcp_client_auth.py +331 -0
- atlas/tests/test_mcp_data_injection.py +270 -0
- atlas/tests/test_mcp_get_authorized_servers.py +95 -0
- atlas/tests/test_mcp_hot_reload.py +512 -0
- atlas/tests/test_mcp_image_content.py +424 -0
- atlas/tests/test_mcp_logging.py +172 -0
- atlas/tests/test_mcp_progress_updates.py +313 -0
- atlas/tests/test_mcp_prompt_override_system_prompt.py +102 -0
- atlas/tests/test_mcp_prompts_server.py +39 -0
- atlas/tests/test_mcp_tool_result_parsing.py +296 -0
- atlas/tests/test_metrics_logger.py +56 -0
- atlas/tests/test_middleware_auth.py +379 -0
- atlas/tests/test_prompt_risk_and_acl.py +141 -0
- atlas/tests/test_rag_mcp_aggregator.py +204 -0
- atlas/tests/test_rag_mcp_service.py +224 -0
- atlas/tests/test_rate_limit_middleware.py +45 -0
- atlas/tests/test_routes_config_smoke.py +60 -0
- atlas/tests/test_routes_files_download_token.py +41 -0
- atlas/tests/test_routes_files_health.py +18 -0
- atlas/tests/test_runtime_imports.py +53 -0
- atlas/tests/test_sampling_integration.py +482 -0
- atlas/tests/test_security_admin_routes.py +61 -0
- atlas/tests/test_security_capability_tokens.py +65 -0
- atlas/tests/test_security_file_stats_scope.py +21 -0
- atlas/tests/test_security_header_injection.py +191 -0
- atlas/tests/test_security_headers_and_filename.py +63 -0
- atlas/tests/test_shared_session_repository.py +101 -0
- atlas/tests/test_system_prompt_loading.py +181 -0
- atlas/tests/test_token_storage.py +505 -0
- atlas/tests/test_tool_approval_config.py +93 -0
- atlas/tests/test_tool_approval_utils.py +356 -0
- atlas/tests/test_tool_authorization_group_filtering.py +223 -0
- atlas/tests/test_tool_details_in_config.py +108 -0
- atlas/tests/test_tool_planner.py +300 -0
- atlas/tests/test_unified_rag_service.py +398 -0
- atlas/tests/test_username_override_in_approval.py +258 -0
- atlas/tests/test_websocket_auth_header.py +168 -0
- atlas/version.py +6 -0
- atlas_chat-0.1.0.data/data/.env.example +253 -0
- atlas_chat-0.1.0.data/data/config/defaults/compliance-levels.json +44 -0
- atlas_chat-0.1.0.data/data/config/defaults/domain-whitelist.json +123 -0
- atlas_chat-0.1.0.data/data/config/defaults/file-extractors.json +74 -0
- atlas_chat-0.1.0.data/data/config/defaults/help-config.json +198 -0
- atlas_chat-0.1.0.data/data/config/defaults/llmconfig-buggy.yml +11 -0
- atlas_chat-0.1.0.data/data/config/defaults/llmconfig.yml +19 -0
- atlas_chat-0.1.0.data/data/config/defaults/mcp.json +138 -0
- atlas_chat-0.1.0.data/data/config/defaults/rag-sources.json +17 -0
- atlas_chat-0.1.0.data/data/config/defaults/splash-config.json +16 -0
- atlas_chat-0.1.0.dist-info/METADATA +236 -0
- atlas_chat-0.1.0.dist-info/RECORD +250 -0
- atlas_chat-0.1.0.dist-info/WHEEL +5 -0
- atlas_chat-0.1.0.dist-info/entry_points.txt +4 -0
- atlas_chat-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Error handling utilities - pure functions for exception handling patterns.
|
|
3
|
+
|
|
4
|
+
This module provides stateless utility functions for consistent error handling
|
|
5
|
+
across chat operations without maintaining any state.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from typing import Any, Awaitable, Callable, Dict, List, Optional, Tuple
|
|
10
|
+
|
|
11
|
+
from atlas.domain.errors import (
|
|
12
|
+
LLMAuthenticationError,
|
|
13
|
+
LLMServiceError,
|
|
14
|
+
LLMTimeoutError,
|
|
15
|
+
RateLimitError,
|
|
16
|
+
ValidationError,
|
|
17
|
+
)
|
|
18
|
+
from atlas.domain.messages.models import MessageType
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
# Type hint for update callback
|
|
23
|
+
UpdateCallback = Callable[[Dict[str, Any]], Awaitable[None]]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
async def safe_execute_with_tools(
|
|
27
|
+
execution_func: Callable,
|
|
28
|
+
*args,
|
|
29
|
+
**kwargs
|
|
30
|
+
) -> Dict[str, Any]:
|
|
31
|
+
"""
|
|
32
|
+
Safely execute tools mode with centralized exception handling.
|
|
33
|
+
|
|
34
|
+
Pure function that wraps any execution function with error handling.
|
|
35
|
+
"""
|
|
36
|
+
try:
|
|
37
|
+
return await execution_func(*args, **kwargs)
|
|
38
|
+
except ValidationError:
|
|
39
|
+
raise # Re-raise validation errors
|
|
40
|
+
except Exception as e:
|
|
41
|
+
logger.error(f"Error in tools mode execution: {e}", exc_info=True)
|
|
42
|
+
return {
|
|
43
|
+
"type": MessageType.ERROR.value,
|
|
44
|
+
"message": f"Tools execution failed: {str(e)}"
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
async def safe_get_tools_schema(
|
|
49
|
+
tool_manager,
|
|
50
|
+
selected_tools: List[str]
|
|
51
|
+
) -> List[Dict[str, Any]]:
|
|
52
|
+
"""
|
|
53
|
+
Safely get tools schema with error handling.
|
|
54
|
+
|
|
55
|
+
Pure function that handles tool schema retrieval errors.
|
|
56
|
+
"""
|
|
57
|
+
if not tool_manager:
|
|
58
|
+
raise ValidationError("Tool manager not configured")
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
tools_schema = tool_manager.get_tools_schema(selected_tools)
|
|
62
|
+
logger.info(f"Got {len(tools_schema)} tool schemas for selected tools: {selected_tools}")
|
|
63
|
+
return tools_schema
|
|
64
|
+
except Exception as e:
|
|
65
|
+
logger.error(f"Error getting tools schema: {e}", exc_info=True)
|
|
66
|
+
raise ValidationError(f"Failed to get tools schema: {str(e)}")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def classify_llm_error(error: Exception) -> Tuple[type, str, str]:
|
|
70
|
+
"""
|
|
71
|
+
Classify LLM errors and return appropriate error type, user message, and log message.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
Tuple of (error_class, user_message, log_message).
|
|
75
|
+
|
|
76
|
+
NOTE: user_message MUST NOT contain raw exception details or sensitive data.
|
|
77
|
+
"""
|
|
78
|
+
error_str = str(error)
|
|
79
|
+
error_type_name = type(error).__name__
|
|
80
|
+
|
|
81
|
+
# Check for rate limiting errors
|
|
82
|
+
if "RateLimitError" in error_type_name or "rate limit" in error_str.lower() or "high traffic" in error_str.lower():
|
|
83
|
+
user_msg = "The AI service is experiencing high traffic. Please try again in a moment."
|
|
84
|
+
log_msg = f"Rate limit error: {error_str}"
|
|
85
|
+
return (RateLimitError, user_msg, log_msg)
|
|
86
|
+
|
|
87
|
+
# Check for timeout errors
|
|
88
|
+
if "timeout" in error_str.lower() or "timed out" in error_str.lower():
|
|
89
|
+
user_msg = "The AI service request timed out. Please try again."
|
|
90
|
+
log_msg = f"Timeout error: {error_str}"
|
|
91
|
+
return (LLMTimeoutError, user_msg, log_msg)
|
|
92
|
+
|
|
93
|
+
# Check for authentication/authorization errors
|
|
94
|
+
if any(keyword in error_str.lower() for keyword in ["unauthorized", "authentication", "invalid api key", "invalid_api_key", "api key"]):
|
|
95
|
+
user_msg = "There was an authentication issue with the AI service. Please contact your administrator."
|
|
96
|
+
log_msg = f"Authentication error: {error_str}"
|
|
97
|
+
return (LLMAuthenticationError, user_msg, log_msg)
|
|
98
|
+
|
|
99
|
+
# Generic LLM service error (non-validation)
|
|
100
|
+
user_msg = "The AI service encountered an error. Please try again or contact support if the issue persists."
|
|
101
|
+
log_msg = f"LLM error: {error_str}"
|
|
102
|
+
return (LLMServiceError, user_msg, log_msg)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
async def safe_call_llm_with_tools(
|
|
106
|
+
llm_caller,
|
|
107
|
+
model: str,
|
|
108
|
+
messages: List[Dict[str, str]],
|
|
109
|
+
tools_schema: List[Dict[str, Any]],
|
|
110
|
+
data_sources: Optional[List[str]] = None,
|
|
111
|
+
user_email: Optional[str] = None,
|
|
112
|
+
tool_choice: str = "auto",
|
|
113
|
+
temperature: float = 0.7,
|
|
114
|
+
):
|
|
115
|
+
"""
|
|
116
|
+
Safely call LLM with tools and error handling.
|
|
117
|
+
|
|
118
|
+
Pure function that handles LLM calling errors with proper classification.
|
|
119
|
+
"""
|
|
120
|
+
try:
|
|
121
|
+
if data_sources and user_email:
|
|
122
|
+
llm_response = await llm_caller.call_with_rag_and_tools(
|
|
123
|
+
model, messages, data_sources, tools_schema, user_email, tool_choice, temperature=temperature
|
|
124
|
+
)
|
|
125
|
+
logger.info(f"LLM response received with RAG and tools for user {user_email}, has_tool_calls: {llm_response.has_tool_calls()}")
|
|
126
|
+
else:
|
|
127
|
+
llm_response = await llm_caller.call_with_tools(
|
|
128
|
+
model, messages, tools_schema, tool_choice, temperature=temperature
|
|
129
|
+
)
|
|
130
|
+
# Log metadata at INFO level, content only at DEBUG
|
|
131
|
+
if logger.isEnabledFor(logging.DEBUG):
|
|
132
|
+
logger.debug("LLM response received with tools only, llm_response: %s", llm_response)
|
|
133
|
+
else:
|
|
134
|
+
# Check if llm_response has the expected attributes before logging
|
|
135
|
+
has_tool_calls = llm_response.has_tool_calls() if hasattr(llm_response, 'has_tool_calls') else False
|
|
136
|
+
content_length = len(llm_response.content) if hasattr(llm_response, 'content') else 0
|
|
137
|
+
model_used = getattr(llm_response, 'model_used', 'unknown')
|
|
138
|
+
logger.info(
|
|
139
|
+
f"LLM response received with tools only, has_tool_calls: {has_tool_calls}, "
|
|
140
|
+
f"content_length: {content_length}, model: {model_used}"
|
|
141
|
+
)
|
|
142
|
+
return llm_response
|
|
143
|
+
except Exception as e:
|
|
144
|
+
# Classify the error and raise appropriate error type
|
|
145
|
+
error_class, user_msg, log_msg = classify_llm_error(e)
|
|
146
|
+
logger.error(log_msg, exc_info=True)
|
|
147
|
+
raise error_class(user_msg)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
async def safe_execute_single_tool(
|
|
151
|
+
tool_execution_func: Callable,
|
|
152
|
+
tool_call,
|
|
153
|
+
session_context: Dict[str, Any],
|
|
154
|
+
tool_manager,
|
|
155
|
+
update_callback: Optional[UpdateCallback] = None
|
|
156
|
+
):
|
|
157
|
+
"""
|
|
158
|
+
Safely execute a single tool with comprehensive error handling.
|
|
159
|
+
|
|
160
|
+
Pure function that wraps tool execution with error handling.
|
|
161
|
+
"""
|
|
162
|
+
try:
|
|
163
|
+
return await tool_execution_func(
|
|
164
|
+
tool_call=tool_call,
|
|
165
|
+
session_context=session_context,
|
|
166
|
+
tool_manager=tool_manager,
|
|
167
|
+
update_callback=update_callback
|
|
168
|
+
)
|
|
169
|
+
except Exception as e:
|
|
170
|
+
logger.error(f"Error executing tool {tool_call.function.name}: {e}")
|
|
171
|
+
|
|
172
|
+
# Send error notification if callback available
|
|
173
|
+
if update_callback:
|
|
174
|
+
try:
|
|
175
|
+
await update_callback({
|
|
176
|
+
"type": "tool_error",
|
|
177
|
+
"tool_call_id": tool_call.id,
|
|
178
|
+
"tool_name": tool_call.function.name,
|
|
179
|
+
"error": str(e)
|
|
180
|
+
})
|
|
181
|
+
except Exception:
|
|
182
|
+
pass # Don't let notification errors compound the problem
|
|
183
|
+
|
|
184
|
+
# Return error result instead of raising
|
|
185
|
+
from atlas.domain.messages.models import ToolResult
|
|
186
|
+
return ToolResult(
|
|
187
|
+
tool_call_id=tool_call.id,
|
|
188
|
+
content=f"Tool execution failed: {str(e)}",
|
|
189
|
+
success=False,
|
|
190
|
+
error=str(e)
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
async def safe_file_operation(
|
|
195
|
+
file_operation_func: Callable,
|
|
196
|
+
*args,
|
|
197
|
+
**kwargs
|
|
198
|
+
) -> Any:
|
|
199
|
+
"""
|
|
200
|
+
Safely execute file operations with error handling.
|
|
201
|
+
|
|
202
|
+
Pure function that wraps file operations with error handling.
|
|
203
|
+
"""
|
|
204
|
+
try:
|
|
205
|
+
return await file_operation_func(*args, **kwargs)
|
|
206
|
+
except Exception as e:
|
|
207
|
+
logger.error(f"Error in file operation: {e}", exc_info=True)
|
|
208
|
+
# Return original context if operation fails
|
|
209
|
+
if args and isinstance(args[0], dict):
|
|
210
|
+
return args[0] # Return original session_context
|
|
211
|
+
return None
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
async def safe_llm_call(
|
|
215
|
+
llm_call_func: Callable,
|
|
216
|
+
*args,
|
|
217
|
+
**kwargs
|
|
218
|
+
) -> Any:
|
|
219
|
+
"""
|
|
220
|
+
Safely execute LLM calls with error handling.
|
|
221
|
+
|
|
222
|
+
Pure function that wraps LLM calls with error handling.
|
|
223
|
+
"""
|
|
224
|
+
try:
|
|
225
|
+
return await llm_call_func(*args, **kwargs)
|
|
226
|
+
except Exception as e:
|
|
227
|
+
logger.error(f"Error in LLM call: {e}", exc_info=True)
|
|
228
|
+
raise ValidationError(f"LLM call failed: {str(e)}")
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def safe_sync_operation(
|
|
232
|
+
operation_func: Callable,
|
|
233
|
+
*args,
|
|
234
|
+
**kwargs
|
|
235
|
+
) -> Any:
|
|
236
|
+
"""
|
|
237
|
+
Safely execute synchronous operations with error handling.
|
|
238
|
+
|
|
239
|
+
Pure function that wraps sync operations with error handling.
|
|
240
|
+
"""
|
|
241
|
+
try:
|
|
242
|
+
return operation_func(*args, **kwargs)
|
|
243
|
+
except Exception as e:
|
|
244
|
+
logger.error(f"Error in sync operation: {e}", exc_info=True)
|
|
245
|
+
return None
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def create_error_response(
|
|
249
|
+
error_message: str,
|
|
250
|
+
error_type: str = "error"
|
|
251
|
+
) -> Dict[str, str]:
|
|
252
|
+
"""
|
|
253
|
+
Create standardized error response.
|
|
254
|
+
|
|
255
|
+
Pure function that creates consistent error responses.
|
|
256
|
+
"""
|
|
257
|
+
return {
|
|
258
|
+
"type": error_type,
|
|
259
|
+
"message": str(error_message)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def create_validation_error_response(
|
|
264
|
+
validation_message: str
|
|
265
|
+
) -> Dict[str, str]:
|
|
266
|
+
"""
|
|
267
|
+
Create standardized validation error response.
|
|
268
|
+
|
|
269
|
+
Pure function that creates consistent validation error responses.
|
|
270
|
+
"""
|
|
271
|
+
return {
|
|
272
|
+
"type": MessageType.ERROR.value,
|
|
273
|
+
"message": f"Validation error: {validation_message}"
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def log_and_suppress_error(
|
|
278
|
+
operation_name: str,
|
|
279
|
+
error: Exception,
|
|
280
|
+
level: str = "warning"
|
|
281
|
+
) -> None:
|
|
282
|
+
"""
|
|
283
|
+
Log an error and suppress it for non-critical operations.
|
|
284
|
+
|
|
285
|
+
Pure function that provides consistent error logging.
|
|
286
|
+
"""
|
|
287
|
+
log_func = getattr(logger, level, logger.warning)
|
|
288
|
+
log_func(f"Non-fatal error in {operation_name}: {error}")
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def handle_chat_message_error(
|
|
292
|
+
error: Exception,
|
|
293
|
+
context: str = "chat message handling"
|
|
294
|
+
) -> Dict[str, str]:
|
|
295
|
+
"""
|
|
296
|
+
Handle chat message errors with consistent logging and response.
|
|
297
|
+
|
|
298
|
+
Pure function that provides standard chat error handling.
|
|
299
|
+
"""
|
|
300
|
+
logger.error(f"Error in {context}: {error}", exc_info=True)
|
|
301
|
+
return {
|
|
302
|
+
"type": MessageType.ERROR.value,
|
|
303
|
+
"message": str(error)
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def should_retry_operation(
|
|
308
|
+
error: Exception,
|
|
309
|
+
retry_count: int,
|
|
310
|
+
max_retries: int = 3
|
|
311
|
+
) -> bool:
|
|
312
|
+
"""
|
|
313
|
+
Determine if an operation should be retried based on error type.
|
|
314
|
+
|
|
315
|
+
Pure function that implements retry logic.
|
|
316
|
+
"""
|
|
317
|
+
if retry_count >= max_retries:
|
|
318
|
+
return False
|
|
319
|
+
|
|
320
|
+
# Don't retry validation errors
|
|
321
|
+
if isinstance(error, ValidationError):
|
|
322
|
+
return False
|
|
323
|
+
|
|
324
|
+
# Retry for other types of errors
|
|
325
|
+
return True
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
async def with_retry(
|
|
329
|
+
operation_func: Callable,
|
|
330
|
+
max_retries: int = 3,
|
|
331
|
+
*args,
|
|
332
|
+
**kwargs
|
|
333
|
+
) -> Any:
|
|
334
|
+
"""
|
|
335
|
+
Execute operation with retry logic.
|
|
336
|
+
|
|
337
|
+
Pure function that provides retry capability for operations.
|
|
338
|
+
"""
|
|
339
|
+
last_error = None
|
|
340
|
+
|
|
341
|
+
for attempt in range(max_retries + 1):
|
|
342
|
+
try:
|
|
343
|
+
return await operation_func(*args, **kwargs)
|
|
344
|
+
except Exception as e:
|
|
345
|
+
last_error = e
|
|
346
|
+
if not should_retry_operation(e, attempt, max_retries):
|
|
347
|
+
break
|
|
348
|
+
logger.warning(f"Operation failed (attempt {attempt + 1}/{max_retries + 1}): {e}")
|
|
349
|
+
|
|
350
|
+
# If we get here, all retries failed
|
|
351
|
+
raise last_error
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def sanitize_kwargs_for_logging(kwargs: Dict[str, Any]) -> Dict[str, Any]:
|
|
355
|
+
"""
|
|
356
|
+
Sanitize kwargs for safe logging by replacing large objects with summaries.
|
|
357
|
+
|
|
358
|
+
Pure function that creates a sanitized copy for logging purposes.
|
|
359
|
+
Used to prevent large file contents from cluttering logs.
|
|
360
|
+
"""
|
|
361
|
+
try:
|
|
362
|
+
sanitized_kwargs = dict(kwargs)
|
|
363
|
+
if "files" in sanitized_kwargs and isinstance(sanitized_kwargs["files"], dict):
|
|
364
|
+
sanitized_kwargs["files"] = list(sanitized_kwargs["files"].keys())
|
|
365
|
+
return sanitized_kwargs
|
|
366
|
+
except Exception:
|
|
367
|
+
return {k: ("<error sanitizing>") for k in kwargs.keys()}
|