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.
Files changed (250) hide show
  1. atlas/__init__.py +40 -0
  2. atlas/application/__init__.py +7 -0
  3. atlas/application/chat/__init__.py +7 -0
  4. atlas/application/chat/agent/__init__.py +10 -0
  5. atlas/application/chat/agent/act_loop.py +179 -0
  6. atlas/application/chat/agent/factory.py +142 -0
  7. atlas/application/chat/agent/protocols.py +46 -0
  8. atlas/application/chat/agent/react_loop.py +338 -0
  9. atlas/application/chat/agent/think_act_loop.py +171 -0
  10. atlas/application/chat/approval_manager.py +151 -0
  11. atlas/application/chat/elicitation_manager.py +191 -0
  12. atlas/application/chat/events/__init__.py +1 -0
  13. atlas/application/chat/events/agent_event_relay.py +112 -0
  14. atlas/application/chat/modes/__init__.py +1 -0
  15. atlas/application/chat/modes/agent.py +125 -0
  16. atlas/application/chat/modes/plain.py +74 -0
  17. atlas/application/chat/modes/rag.py +81 -0
  18. atlas/application/chat/modes/tools.py +179 -0
  19. atlas/application/chat/orchestrator.py +213 -0
  20. atlas/application/chat/policies/__init__.py +1 -0
  21. atlas/application/chat/policies/tool_authorization.py +99 -0
  22. atlas/application/chat/preprocessors/__init__.py +1 -0
  23. atlas/application/chat/preprocessors/message_builder.py +92 -0
  24. atlas/application/chat/preprocessors/prompt_override_service.py +104 -0
  25. atlas/application/chat/service.py +454 -0
  26. atlas/application/chat/utilities/__init__.py +6 -0
  27. atlas/application/chat/utilities/error_handler.py +367 -0
  28. atlas/application/chat/utilities/event_notifier.py +546 -0
  29. atlas/application/chat/utilities/file_processor.py +613 -0
  30. atlas/application/chat/utilities/tool_executor.py +789 -0
  31. atlas/atlas_chat_cli.py +347 -0
  32. atlas/atlas_client.py +238 -0
  33. atlas/core/__init__.py +0 -0
  34. atlas/core/auth.py +205 -0
  35. atlas/core/authorization_manager.py +27 -0
  36. atlas/core/capabilities.py +123 -0
  37. atlas/core/compliance.py +215 -0
  38. atlas/core/domain_whitelist.py +147 -0
  39. atlas/core/domain_whitelist_middleware.py +82 -0
  40. atlas/core/http_client.py +28 -0
  41. atlas/core/log_sanitizer.py +102 -0
  42. atlas/core/metrics_logger.py +59 -0
  43. atlas/core/middleware.py +131 -0
  44. atlas/core/otel_config.py +242 -0
  45. atlas/core/prompt_risk.py +200 -0
  46. atlas/core/rate_limit.py +0 -0
  47. atlas/core/rate_limit_middleware.py +64 -0
  48. atlas/core/security_headers_middleware.py +51 -0
  49. atlas/domain/__init__.py +37 -0
  50. atlas/domain/chat/__init__.py +1 -0
  51. atlas/domain/chat/dtos.py +85 -0
  52. atlas/domain/errors.py +96 -0
  53. atlas/domain/messages/__init__.py +12 -0
  54. atlas/domain/messages/models.py +160 -0
  55. atlas/domain/rag_mcp_service.py +664 -0
  56. atlas/domain/sessions/__init__.py +7 -0
  57. atlas/domain/sessions/models.py +36 -0
  58. atlas/domain/unified_rag_service.py +371 -0
  59. atlas/infrastructure/__init__.py +10 -0
  60. atlas/infrastructure/app_factory.py +135 -0
  61. atlas/infrastructure/events/__init__.py +1 -0
  62. atlas/infrastructure/events/cli_event_publisher.py +140 -0
  63. atlas/infrastructure/events/websocket_publisher.py +140 -0
  64. atlas/infrastructure/sessions/in_memory_repository.py +56 -0
  65. atlas/infrastructure/transport/__init__.py +7 -0
  66. atlas/infrastructure/transport/websocket_connection_adapter.py +33 -0
  67. atlas/init_cli.py +226 -0
  68. atlas/interfaces/__init__.py +15 -0
  69. atlas/interfaces/events.py +134 -0
  70. atlas/interfaces/llm.py +54 -0
  71. atlas/interfaces/rag.py +40 -0
  72. atlas/interfaces/sessions.py +75 -0
  73. atlas/interfaces/tools.py +57 -0
  74. atlas/interfaces/transport.py +24 -0
  75. atlas/main.py +564 -0
  76. atlas/mcp/api_key_demo/README.md +76 -0
  77. atlas/mcp/api_key_demo/main.py +172 -0
  78. atlas/mcp/api_key_demo/run.sh +56 -0
  79. atlas/mcp/basictable/main.py +147 -0
  80. atlas/mcp/calculator/main.py +149 -0
  81. atlas/mcp/code-executor/execution_engine.py +98 -0
  82. atlas/mcp/code-executor/execution_environment.py +95 -0
  83. atlas/mcp/code-executor/main.py +528 -0
  84. atlas/mcp/code-executor/result_processing.py +276 -0
  85. atlas/mcp/code-executor/script_generation.py +195 -0
  86. atlas/mcp/code-executor/security_checker.py +140 -0
  87. atlas/mcp/corporate_cars/main.py +437 -0
  88. atlas/mcp/csv_reporter/main.py +545 -0
  89. atlas/mcp/duckduckgo/main.py +182 -0
  90. atlas/mcp/elicitation_demo/README.md +171 -0
  91. atlas/mcp/elicitation_demo/main.py +262 -0
  92. atlas/mcp/env-demo/README.md +158 -0
  93. atlas/mcp/env-demo/main.py +199 -0
  94. atlas/mcp/file_size_test/main.py +284 -0
  95. atlas/mcp/filesystem/main.py +348 -0
  96. atlas/mcp/image_demo/main.py +113 -0
  97. atlas/mcp/image_demo/requirements.txt +4 -0
  98. atlas/mcp/logging_demo/README.md +72 -0
  99. atlas/mcp/logging_demo/main.py +103 -0
  100. atlas/mcp/many_tools_demo/main.py +50 -0
  101. atlas/mcp/order_database/__init__.py +0 -0
  102. atlas/mcp/order_database/main.py +369 -0
  103. atlas/mcp/order_database/signal_data.csv +1001 -0
  104. atlas/mcp/pdfbasic/main.py +394 -0
  105. atlas/mcp/pptx_generator/main.py +760 -0
  106. atlas/mcp/pptx_generator/requirements.txt +13 -0
  107. atlas/mcp/pptx_generator/run_test.sh +1 -0
  108. atlas/mcp/pptx_generator/test_pptx_generator_security.py +169 -0
  109. atlas/mcp/progress_demo/main.py +167 -0
  110. atlas/mcp/progress_updates_demo/QUICKSTART.md +273 -0
  111. atlas/mcp/progress_updates_demo/README.md +120 -0
  112. atlas/mcp/progress_updates_demo/main.py +497 -0
  113. atlas/mcp/prompts/main.py +222 -0
  114. atlas/mcp/public_demo/main.py +189 -0
  115. atlas/mcp/sampling_demo/README.md +169 -0
  116. atlas/mcp/sampling_demo/main.py +234 -0
  117. atlas/mcp/thinking/main.py +77 -0
  118. atlas/mcp/tool_planner/main.py +240 -0
  119. atlas/mcp/ui-demo/badmesh.png +0 -0
  120. atlas/mcp/ui-demo/main.py +383 -0
  121. atlas/mcp/ui-demo/templates/button_demo.html +32 -0
  122. atlas/mcp/ui-demo/templates/data_visualization.html +32 -0
  123. atlas/mcp/ui-demo/templates/form_demo.html +28 -0
  124. atlas/mcp/username-override-demo/README.md +320 -0
  125. atlas/mcp/username-override-demo/main.py +308 -0
  126. atlas/modules/__init__.py +0 -0
  127. atlas/modules/config/__init__.py +34 -0
  128. atlas/modules/config/cli.py +231 -0
  129. atlas/modules/config/config_manager.py +1096 -0
  130. atlas/modules/file_storage/__init__.py +22 -0
  131. atlas/modules/file_storage/cli.py +330 -0
  132. atlas/modules/file_storage/content_extractor.py +290 -0
  133. atlas/modules/file_storage/manager.py +295 -0
  134. atlas/modules/file_storage/mock_s3_client.py +402 -0
  135. atlas/modules/file_storage/s3_client.py +417 -0
  136. atlas/modules/llm/__init__.py +19 -0
  137. atlas/modules/llm/caller.py +287 -0
  138. atlas/modules/llm/litellm_caller.py +675 -0
  139. atlas/modules/llm/models.py +19 -0
  140. atlas/modules/mcp_tools/__init__.py +17 -0
  141. atlas/modules/mcp_tools/client.py +2123 -0
  142. atlas/modules/mcp_tools/token_storage.py +556 -0
  143. atlas/modules/prompts/prompt_provider.py +130 -0
  144. atlas/modules/rag/__init__.py +24 -0
  145. atlas/modules/rag/atlas_rag_client.py +336 -0
  146. atlas/modules/rag/client.py +129 -0
  147. atlas/routes/admin_routes.py +865 -0
  148. atlas/routes/config_routes.py +484 -0
  149. atlas/routes/feedback_routes.py +361 -0
  150. atlas/routes/files_routes.py +274 -0
  151. atlas/routes/health_routes.py +40 -0
  152. atlas/routes/mcp_auth_routes.py +223 -0
  153. atlas/server_cli.py +164 -0
  154. atlas/tests/conftest.py +20 -0
  155. atlas/tests/integration/test_mcp_auth_integration.py +152 -0
  156. atlas/tests/manual_test_sampling.py +87 -0
  157. atlas/tests/modules/mcp_tools/test_client_auth.py +226 -0
  158. atlas/tests/modules/mcp_tools/test_client_env.py +191 -0
  159. atlas/tests/test_admin_mcp_server_management_routes.py +141 -0
  160. atlas/tests/test_agent_roa.py +135 -0
  161. atlas/tests/test_app_factory_smoke.py +47 -0
  162. atlas/tests/test_approval_manager.py +439 -0
  163. atlas/tests/test_atlas_client.py +188 -0
  164. atlas/tests/test_atlas_rag_client.py +447 -0
  165. atlas/tests/test_atlas_rag_integration.py +224 -0
  166. atlas/tests/test_attach_file_flow.py +287 -0
  167. atlas/tests/test_auth_utils.py +165 -0
  168. atlas/tests/test_backend_public_url.py +185 -0
  169. atlas/tests/test_banner_logging.py +287 -0
  170. atlas/tests/test_capability_tokens_and_injection.py +203 -0
  171. atlas/tests/test_compliance_level.py +54 -0
  172. atlas/tests/test_compliance_manager.py +253 -0
  173. atlas/tests/test_config_manager.py +617 -0
  174. atlas/tests/test_config_manager_paths.py +12 -0
  175. atlas/tests/test_core_auth.py +18 -0
  176. atlas/tests/test_core_utils.py +190 -0
  177. atlas/tests/test_docker_env_sync.py +202 -0
  178. atlas/tests/test_domain_errors.py +329 -0
  179. atlas/tests/test_domain_whitelist.py +359 -0
  180. atlas/tests/test_elicitation_manager.py +408 -0
  181. atlas/tests/test_elicitation_routing.py +296 -0
  182. atlas/tests/test_env_demo_server.py +88 -0
  183. atlas/tests/test_error_classification.py +113 -0
  184. atlas/tests/test_error_flow_integration.py +116 -0
  185. atlas/tests/test_feedback_routes.py +333 -0
  186. atlas/tests/test_file_content_extraction.py +1134 -0
  187. atlas/tests/test_file_extraction_routes.py +158 -0
  188. atlas/tests/test_file_library.py +107 -0
  189. atlas/tests/test_file_manager_unit.py +18 -0
  190. atlas/tests/test_health_route.py +49 -0
  191. atlas/tests/test_http_client_stub.py +8 -0
  192. atlas/tests/test_imports_smoke.py +30 -0
  193. atlas/tests/test_interfaces_llm_response.py +9 -0
  194. atlas/tests/test_issue_access_denied_fix.py +136 -0
  195. atlas/tests/test_llm_env_expansion.py +836 -0
  196. atlas/tests/test_log_level_sensitive_data.py +285 -0
  197. atlas/tests/test_mcp_auth_routes.py +341 -0
  198. atlas/tests/test_mcp_client_auth.py +331 -0
  199. atlas/tests/test_mcp_data_injection.py +270 -0
  200. atlas/tests/test_mcp_get_authorized_servers.py +95 -0
  201. atlas/tests/test_mcp_hot_reload.py +512 -0
  202. atlas/tests/test_mcp_image_content.py +424 -0
  203. atlas/tests/test_mcp_logging.py +172 -0
  204. atlas/tests/test_mcp_progress_updates.py +313 -0
  205. atlas/tests/test_mcp_prompt_override_system_prompt.py +102 -0
  206. atlas/tests/test_mcp_prompts_server.py +39 -0
  207. atlas/tests/test_mcp_tool_result_parsing.py +296 -0
  208. atlas/tests/test_metrics_logger.py +56 -0
  209. atlas/tests/test_middleware_auth.py +379 -0
  210. atlas/tests/test_prompt_risk_and_acl.py +141 -0
  211. atlas/tests/test_rag_mcp_aggregator.py +204 -0
  212. atlas/tests/test_rag_mcp_service.py +224 -0
  213. atlas/tests/test_rate_limit_middleware.py +45 -0
  214. atlas/tests/test_routes_config_smoke.py +60 -0
  215. atlas/tests/test_routes_files_download_token.py +41 -0
  216. atlas/tests/test_routes_files_health.py +18 -0
  217. atlas/tests/test_runtime_imports.py +53 -0
  218. atlas/tests/test_sampling_integration.py +482 -0
  219. atlas/tests/test_security_admin_routes.py +61 -0
  220. atlas/tests/test_security_capability_tokens.py +65 -0
  221. atlas/tests/test_security_file_stats_scope.py +21 -0
  222. atlas/tests/test_security_header_injection.py +191 -0
  223. atlas/tests/test_security_headers_and_filename.py +63 -0
  224. atlas/tests/test_shared_session_repository.py +101 -0
  225. atlas/tests/test_system_prompt_loading.py +181 -0
  226. atlas/tests/test_token_storage.py +505 -0
  227. atlas/tests/test_tool_approval_config.py +93 -0
  228. atlas/tests/test_tool_approval_utils.py +356 -0
  229. atlas/tests/test_tool_authorization_group_filtering.py +223 -0
  230. atlas/tests/test_tool_details_in_config.py +108 -0
  231. atlas/tests/test_tool_planner.py +300 -0
  232. atlas/tests/test_unified_rag_service.py +398 -0
  233. atlas/tests/test_username_override_in_approval.py +258 -0
  234. atlas/tests/test_websocket_auth_header.py +168 -0
  235. atlas/version.py +6 -0
  236. atlas_chat-0.1.0.data/data/.env.example +253 -0
  237. atlas_chat-0.1.0.data/data/config/defaults/compliance-levels.json +44 -0
  238. atlas_chat-0.1.0.data/data/config/defaults/domain-whitelist.json +123 -0
  239. atlas_chat-0.1.0.data/data/config/defaults/file-extractors.json +74 -0
  240. atlas_chat-0.1.0.data/data/config/defaults/help-config.json +198 -0
  241. atlas_chat-0.1.0.data/data/config/defaults/llmconfig-buggy.yml +11 -0
  242. atlas_chat-0.1.0.data/data/config/defaults/llmconfig.yml +19 -0
  243. atlas_chat-0.1.0.data/data/config/defaults/mcp.json +138 -0
  244. atlas_chat-0.1.0.data/data/config/defaults/rag-sources.json +17 -0
  245. atlas_chat-0.1.0.data/data/config/defaults/splash-config.json +16 -0
  246. atlas_chat-0.1.0.dist-info/METADATA +236 -0
  247. atlas_chat-0.1.0.dist-info/RECORD +250 -0
  248. atlas_chat-0.1.0.dist-info/WHEEL +5 -0
  249. atlas_chat-0.1.0.dist-info/entry_points.txt +4 -0
  250. 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()}