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,287 @@
|
|
|
1
|
+
# """
|
|
2
|
+
# LLM calling interface that handles all modes of LLM interaction.
|
|
3
|
+
|
|
4
|
+
# This module provides a clean interface for calling LLMs in different modes:
|
|
5
|
+
# - Plain LLM calls (no tools)
|
|
6
|
+
# - LLM calls with RAG integration
|
|
7
|
+
# - LLM calls with tool support
|
|
8
|
+
# - LLM calls with both RAG and tools
|
|
9
|
+
# """
|
|
10
|
+
|
|
11
|
+
# import asyncio
|
|
12
|
+
# import json
|
|
13
|
+
# import logging
|
|
14
|
+
# import os
|
|
15
|
+
# from typing import Any, Dict, List, Optional
|
|
16
|
+
# from dataclasses import dataclass
|
|
17
|
+
|
|
18
|
+
# import requests
|
|
19
|
+
# from .models import LLMResponse
|
|
20
|
+
|
|
21
|
+
# logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# class LLMCaller:
|
|
25
|
+
# """Clean interface for all LLM calling patterns."""
|
|
26
|
+
|
|
27
|
+
# def __init__(self, llm_config=None):
|
|
28
|
+
# """Initialize with optional config dependency injection."""
|
|
29
|
+
# if llm_config is None:
|
|
30
|
+
# from atlas.modules.config import config_manager
|
|
31
|
+
# self.llm_config = config_manager.llm_config
|
|
32
|
+
# else:
|
|
33
|
+
# self.llm_config = llm_config
|
|
34
|
+
|
|
35
|
+
# async def call_plain(self, model_name: str, messages: List[Dict[str, str]]) -> str:
|
|
36
|
+
# """Plain LLM call - no tools, no RAG."""
|
|
37
|
+
# if model_name not in self.llm_config.models:
|
|
38
|
+
# raise ValueError(f"Model {model_name} not found in configuration")
|
|
39
|
+
|
|
40
|
+
# model_config = self.llm_config.models[model_name]
|
|
41
|
+
# api_url = model_config.model_url
|
|
42
|
+
# api_key = os.path.expandvars(model_config.api_key)
|
|
43
|
+
# model_id = model_config.model_name
|
|
44
|
+
|
|
45
|
+
# headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
|
|
46
|
+
# # Inject any extra provider-specific headers (expand env vars, skip unresolved placeholders)
|
|
47
|
+
# if getattr(model_config, 'extra_headers', None):
|
|
48
|
+
# for h_key, h_val in model_config.extra_headers.items():
|
|
49
|
+
# if not h_val:
|
|
50
|
+
# continue
|
|
51
|
+
# expanded = os.path.expandvars(h_val)
|
|
52
|
+
# # Skip if still looks like an unresolved ${VAR}
|
|
53
|
+
# if expanded.startswith("${") and expanded.endswith("}"):
|
|
54
|
+
# continue
|
|
55
|
+
# headers[h_key] = expanded
|
|
56
|
+
# payload = {"model": model_id, "messages": messages, "max_tokens": model_config.max_tokens, "temperature": 0.7}
|
|
57
|
+
|
|
58
|
+
# VERBOSE = True
|
|
59
|
+
|
|
60
|
+
# try:
|
|
61
|
+
# total_chars = sum(len(str(msg.get('content', ''))) for msg in messages)
|
|
62
|
+
# logger.info(f"Plain LLM call: {len(messages)} messages, {total_chars} chars")
|
|
63
|
+
|
|
64
|
+
# # if verbose, just print the "message" list
|
|
65
|
+
# if VERBOSE:
|
|
66
|
+
# logging.info(f"Messages are: {[m for m in messages]}")
|
|
67
|
+
|
|
68
|
+
# loop = asyncio.get_event_loop()
|
|
69
|
+
# response = await loop.run_in_executor(
|
|
70
|
+
# None, lambda: requests.post(api_url, headers=headers, json=payload, timeout=30)
|
|
71
|
+
# )
|
|
72
|
+
|
|
73
|
+
# if response.status_code == 200:
|
|
74
|
+
# result = response.json()
|
|
75
|
+
# llm_response = result["choices"][0]["message"]["content"]
|
|
76
|
+
# logger.info(f"LLM response preview: '{llm_response[:200]}{'...' if len(llm_response) > 200 else ''}'")
|
|
77
|
+
# return llm_response
|
|
78
|
+
|
|
79
|
+
# logger.error("LLM API error %s: %s", response.status_code, response.text)
|
|
80
|
+
# raise Exception(f"LLM API error: {response.status_code}")
|
|
81
|
+
|
|
82
|
+
# except requests.RequestException as exc:
|
|
83
|
+
# logger.error("Request error calling LLM: %s", exc, exc_info=True)
|
|
84
|
+
# raise Exception(f"Failed to call LLM: {exc}")
|
|
85
|
+
# except KeyError as exc:
|
|
86
|
+
# logger.error("Invalid response format from LLM: %s", exc, exc_info=True)
|
|
87
|
+
# raise Exception("Invalid response format from LLM")
|
|
88
|
+
|
|
89
|
+
# async def call_with_rag(
|
|
90
|
+
# self,
|
|
91
|
+
# model_name: str,
|
|
92
|
+
# messages: List[Dict[str, str]],
|
|
93
|
+
# data_sources: List[str],
|
|
94
|
+
# user_email: str,
|
|
95
|
+
# rag_client=None
|
|
96
|
+
# ) -> str:
|
|
97
|
+
# """LLM call with RAG integration."""
|
|
98
|
+
# if not data_sources:
|
|
99
|
+
# return await self.call_plain(model_name, messages)
|
|
100
|
+
|
|
101
|
+
# # Import RAG client if not provided
|
|
102
|
+
# if rag_client is None:
|
|
103
|
+
# from atlas.modules.rag import rag_client as default_rag_client
|
|
104
|
+
# rag_client = default_rag_client
|
|
105
|
+
|
|
106
|
+
# # Use the first selected data source
|
|
107
|
+
# data_source = data_sources[0]
|
|
108
|
+
|
|
109
|
+
# try:
|
|
110
|
+
# # Query RAG for context
|
|
111
|
+
# rag_response = await rag_client.query_rag(
|
|
112
|
+
# user_email,
|
|
113
|
+
# data_source,
|
|
114
|
+
# messages
|
|
115
|
+
# )
|
|
116
|
+
|
|
117
|
+
# # Integrate RAG context into messages
|
|
118
|
+
# messages_with_rag = messages.copy()
|
|
119
|
+
# rag_context_message = {
|
|
120
|
+
# "role": "system",
|
|
121
|
+
# "content": f"Retrieved context from {data_source}:\n\n{rag_response.content}\n\nUse this context to inform your response."
|
|
122
|
+
# }
|
|
123
|
+
# messages_with_rag.insert(-1, rag_context_message)
|
|
124
|
+
|
|
125
|
+
# # Call LLM with enriched context
|
|
126
|
+
# llm_response = await self.call_plain(model_name, messages_with_rag)
|
|
127
|
+
|
|
128
|
+
# # Append metadata if available
|
|
129
|
+
# if rag_response.metadata:
|
|
130
|
+
# metadata_summary = self._format_rag_metadata(rag_response.metadata)
|
|
131
|
+
# llm_response += f"\n\n---\n**RAG Sources & Processing Info:**\n{metadata_summary}"
|
|
132
|
+
|
|
133
|
+
# return llm_response
|
|
134
|
+
|
|
135
|
+
# except Exception as exc:
|
|
136
|
+
# logger.error(f"Error in RAG-integrated query: {exc}")
|
|
137
|
+
# # Fallback to plain LLM call
|
|
138
|
+
# return await self.call_plain(model_name, messages)
|
|
139
|
+
|
|
140
|
+
# async def call_with_tools(
|
|
141
|
+
# self,
|
|
142
|
+
# model_name: str,
|
|
143
|
+
# messages: List[Dict[str, str]],
|
|
144
|
+
# tools_schema: List[Dict],
|
|
145
|
+
# tool_choice: str = "auto"
|
|
146
|
+
# ) -> LLMResponse:
|
|
147
|
+
# """LLM call with tool support."""
|
|
148
|
+
# if not tools_schema:
|
|
149
|
+
# content = await self.call_plain(model_name, messages)
|
|
150
|
+
# return LLMResponse(content=content, model_used=model_name)
|
|
151
|
+
|
|
152
|
+
# if model_name not in self.llm_config.models:
|
|
153
|
+
# raise ValueError(f"Model {model_name} not found in configuration")
|
|
154
|
+
|
|
155
|
+
# model_config = self.llm_config.models[model_name]
|
|
156
|
+
# api_url = model_config.model_url
|
|
157
|
+
# api_key = os.path.expandvars(model_config.api_key)
|
|
158
|
+
# model_id = model_config.model_name
|
|
159
|
+
|
|
160
|
+
# headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
|
|
161
|
+
# if getattr(model_config, 'extra_headers', None):
|
|
162
|
+
# for h_key, h_val in model_config.extra_headers.items():
|
|
163
|
+
# if not h_val:
|
|
164
|
+
# continue
|
|
165
|
+
# expanded = os.path.expandvars(h_val)
|
|
166
|
+
# if expanded.startswith("${") and expanded.endswith("}"):
|
|
167
|
+
# continue
|
|
168
|
+
# headers[h_key] = expanded
|
|
169
|
+
# payload = {
|
|
170
|
+
# "model": model_id,
|
|
171
|
+
# "messages": messages,
|
|
172
|
+
# "tools": tools_schema,
|
|
173
|
+
# "tool_choice": tool_choice,
|
|
174
|
+
# "max_tokens": model_config.max_tokens,
|
|
175
|
+
# "temperature": 0.7,
|
|
176
|
+
# }
|
|
177
|
+
|
|
178
|
+
# try:
|
|
179
|
+
# total_chars = sum(len(str(msg.get('content', ''))) for msg in messages)
|
|
180
|
+
# logger.info(f"LLM call with tools: {len(messages)} messages, {total_chars} chars, {len(tools_schema)} tools")
|
|
181
|
+
|
|
182
|
+
# loop = asyncio.get_event_loop()
|
|
183
|
+
# response = await loop.run_in_executor(
|
|
184
|
+
# None, lambda: requests.post(api_url, headers=headers, json=payload, timeout=30)
|
|
185
|
+
# )
|
|
186
|
+
|
|
187
|
+
# if response.status_code != 200:
|
|
188
|
+
# logger.error("LLM API error %s: %s", response.status_code, response.text)
|
|
189
|
+
# raise Exception(f"LLM API error: {response.status_code}")
|
|
190
|
+
|
|
191
|
+
# result = response.json()
|
|
192
|
+
# choice = result["choices"][0]
|
|
193
|
+
# message = choice["message"]
|
|
194
|
+
|
|
195
|
+
# return LLMResponse(
|
|
196
|
+
# content=message.get("content", ""),
|
|
197
|
+
# tool_calls=message.get("tool_calls"),
|
|
198
|
+
# model_used=model_name
|
|
199
|
+
# )
|
|
200
|
+
|
|
201
|
+
# except requests.RequestException as exc:
|
|
202
|
+
# logger.error("Request error calling LLM with tools: %s", exc, exc_info=True)
|
|
203
|
+
# raise Exception(f"Failed to call LLM: {exc}")
|
|
204
|
+
# except KeyError as exc:
|
|
205
|
+
# logger.error("Invalid response format from LLM: %s", exc, exc_info=True)
|
|
206
|
+
# raise Exception("Invalid response format from LLM")
|
|
207
|
+
|
|
208
|
+
# async def call_with_rag_and_tools(
|
|
209
|
+
# self,
|
|
210
|
+
# model_name: str,
|
|
211
|
+
# messages: List[Dict[str, str]],
|
|
212
|
+
# data_sources: List[str],
|
|
213
|
+
# tools_schema: List[Dict],
|
|
214
|
+
# user_email: str,
|
|
215
|
+
# tool_choice: str = "auto",
|
|
216
|
+
# rag_client=None
|
|
217
|
+
# ) -> LLMResponse:
|
|
218
|
+
# """Full integration: RAG + Tools."""
|
|
219
|
+
# if not data_sources:
|
|
220
|
+
# return await self.call_with_tools(model_name, messages, tools_schema, tool_choice)
|
|
221
|
+
|
|
222
|
+
# # Import RAG client if not provided
|
|
223
|
+
# if rag_client is None:
|
|
224
|
+
# from atlas.modules.rag import rag_client as default_rag_client
|
|
225
|
+
# rag_client = default_rag_client
|
|
226
|
+
|
|
227
|
+
# # Use the first selected data source
|
|
228
|
+
# data_source = data_sources[0]
|
|
229
|
+
|
|
230
|
+
# try:
|
|
231
|
+
# # Query RAG for context
|
|
232
|
+
# rag_response = await rag_client.query_rag(
|
|
233
|
+
# user_email,
|
|
234
|
+
# data_source,
|
|
235
|
+
# messages
|
|
236
|
+
# )
|
|
237
|
+
|
|
238
|
+
# # Integrate RAG context into messages
|
|
239
|
+
# messages_with_rag = messages.copy()
|
|
240
|
+
# rag_context_message = {
|
|
241
|
+
# "role": "system",
|
|
242
|
+
# "content": f"Retrieved context from {data_source}:\n\n{rag_response.content}\n\nUse this context to inform your response."
|
|
243
|
+
# }
|
|
244
|
+
# messages_with_rag.insert(-1, rag_context_message)
|
|
245
|
+
|
|
246
|
+
# # Call LLM with enriched context and tools
|
|
247
|
+
# llm_response = await self.call_with_tools(model_name, messages_with_rag, tools_schema, tool_choice)
|
|
248
|
+
|
|
249
|
+
# # Append metadata to content if available and no tool calls
|
|
250
|
+
# if rag_response.metadata and not llm_response.has_tool_calls():
|
|
251
|
+
# metadata_summary = self._format_rag_metadata(rag_response.metadata)
|
|
252
|
+
# llm_response.content += f"\n\n---\n**RAG Sources & Processing Info:**\n{metadata_summary}"
|
|
253
|
+
|
|
254
|
+
# return llm_response
|
|
255
|
+
|
|
256
|
+
# except Exception as exc:
|
|
257
|
+
# logger.error(f"Error in RAG+tools integrated query: {exc}")
|
|
258
|
+
# # Fallback to tools-only call
|
|
259
|
+
# return await self.call_with_tools(model_name, messages, tools_schema, tool_choice)
|
|
260
|
+
|
|
261
|
+
# def _format_rag_metadata(self, metadata) -> str:
|
|
262
|
+
# """Format RAG metadata into a user-friendly summary."""
|
|
263
|
+
# # Import here to avoid circular imports
|
|
264
|
+
# try:
|
|
265
|
+
# from atlas.modules.rag.models import RAGMetadata
|
|
266
|
+
# if not isinstance(metadata, RAGMetadata):
|
|
267
|
+
# return "Metadata unavailable"
|
|
268
|
+
# except ImportError:
|
|
269
|
+
# return "Metadata unavailable"
|
|
270
|
+
|
|
271
|
+
# summary_parts = []
|
|
272
|
+
# summary_parts.append(f" **Data Source:** {metadata.data_source_name}")
|
|
273
|
+
# summary_parts.append(f" **Processing Time:** {metadata.query_processing_time_ms}ms")
|
|
274
|
+
|
|
275
|
+
# if metadata.documents_found:
|
|
276
|
+
# summary_parts.append(f" **Documents Found:** {len(metadata.documents_found)} (searched {metadata.total_documents_searched})")
|
|
277
|
+
|
|
278
|
+
# for i, doc in enumerate(metadata.documents_found[:3]):
|
|
279
|
+
# confidence_percent = int(doc.confidence_score * 100)
|
|
280
|
+
# summary_parts.append(f" • {doc.source} ({confidence_percent}% relevance, {doc.content_type})")
|
|
281
|
+
|
|
282
|
+
# if len(metadata.documents_found) > 3:
|
|
283
|
+
# remaining = len(metadata.documents_found) - 3
|
|
284
|
+
# summary_parts.append(f" • ... and {remaining} more document(s)")
|
|
285
|
+
|
|
286
|
+
# summary_parts.append(f" **Retrieval Method:** {metadata.retrieval_method}")
|
|
287
|
+
# return "\n".join(summary_parts)
|