realtimex-deeptutor 0.5.0.post1__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.
- realtimex_deeptutor/__init__.py +67 -0
- realtimex_deeptutor-0.5.0.post1.dist-info/METADATA +1612 -0
- realtimex_deeptutor-0.5.0.post1.dist-info/RECORD +276 -0
- realtimex_deeptutor-0.5.0.post1.dist-info/WHEEL +5 -0
- realtimex_deeptutor-0.5.0.post1.dist-info/entry_points.txt +2 -0
- realtimex_deeptutor-0.5.0.post1.dist-info/licenses/LICENSE +661 -0
- realtimex_deeptutor-0.5.0.post1.dist-info/top_level.txt +2 -0
- src/__init__.py +40 -0
- src/agents/__init__.py +24 -0
- src/agents/base_agent.py +657 -0
- src/agents/chat/__init__.py +24 -0
- src/agents/chat/chat_agent.py +435 -0
- src/agents/chat/prompts/en/chat_agent.yaml +35 -0
- src/agents/chat/prompts/zh/chat_agent.yaml +35 -0
- src/agents/chat/session_manager.py +311 -0
- src/agents/co_writer/__init__.py +0 -0
- src/agents/co_writer/edit_agent.py +260 -0
- src/agents/co_writer/narrator_agent.py +423 -0
- src/agents/co_writer/prompts/en/edit_agent.yaml +113 -0
- src/agents/co_writer/prompts/en/narrator_agent.yaml +88 -0
- src/agents/co_writer/prompts/zh/edit_agent.yaml +113 -0
- src/agents/co_writer/prompts/zh/narrator_agent.yaml +88 -0
- src/agents/guide/__init__.py +16 -0
- src/agents/guide/agents/__init__.py +11 -0
- src/agents/guide/agents/chat_agent.py +104 -0
- src/agents/guide/agents/interactive_agent.py +223 -0
- src/agents/guide/agents/locate_agent.py +149 -0
- src/agents/guide/agents/summary_agent.py +150 -0
- src/agents/guide/guide_manager.py +500 -0
- src/agents/guide/prompts/en/chat_agent.yaml +41 -0
- src/agents/guide/prompts/en/interactive_agent.yaml +202 -0
- src/agents/guide/prompts/en/locate_agent.yaml +68 -0
- src/agents/guide/prompts/en/summary_agent.yaml +157 -0
- src/agents/guide/prompts/zh/chat_agent.yaml +41 -0
- src/agents/guide/prompts/zh/interactive_agent.yaml +626 -0
- src/agents/guide/prompts/zh/locate_agent.yaml +68 -0
- src/agents/guide/prompts/zh/summary_agent.yaml +157 -0
- src/agents/ideagen/__init__.py +12 -0
- src/agents/ideagen/idea_generation_workflow.py +426 -0
- src/agents/ideagen/material_organizer_agent.py +173 -0
- src/agents/ideagen/prompts/en/idea_generation.yaml +187 -0
- src/agents/ideagen/prompts/en/material_organizer.yaml +69 -0
- src/agents/ideagen/prompts/zh/idea_generation.yaml +187 -0
- src/agents/ideagen/prompts/zh/material_organizer.yaml +69 -0
- src/agents/question/__init__.py +24 -0
- src/agents/question/agents/__init__.py +18 -0
- src/agents/question/agents/generate_agent.py +381 -0
- src/agents/question/agents/relevance_analyzer.py +207 -0
- src/agents/question/agents/retrieve_agent.py +239 -0
- src/agents/question/coordinator.py +718 -0
- src/agents/question/example.py +109 -0
- src/agents/question/prompts/en/coordinator.yaml +75 -0
- src/agents/question/prompts/en/generate_agent.yaml +77 -0
- src/agents/question/prompts/en/relevance_analyzer.yaml +41 -0
- src/agents/question/prompts/en/retrieve_agent.yaml +32 -0
- src/agents/question/prompts/zh/coordinator.yaml +75 -0
- src/agents/question/prompts/zh/generate_agent.yaml +77 -0
- src/agents/question/prompts/zh/relevance_analyzer.yaml +39 -0
- src/agents/question/prompts/zh/retrieve_agent.yaml +30 -0
- src/agents/research/agents/__init__.py +23 -0
- src/agents/research/agents/decompose_agent.py +507 -0
- src/agents/research/agents/manager_agent.py +228 -0
- src/agents/research/agents/note_agent.py +180 -0
- src/agents/research/agents/rephrase_agent.py +263 -0
- src/agents/research/agents/reporting_agent.py +1333 -0
- src/agents/research/agents/research_agent.py +714 -0
- src/agents/research/data_structures.py +451 -0
- src/agents/research/main.py +188 -0
- src/agents/research/prompts/en/decompose_agent.yaml +89 -0
- src/agents/research/prompts/en/manager_agent.yaml +24 -0
- src/agents/research/prompts/en/note_agent.yaml +121 -0
- src/agents/research/prompts/en/rephrase_agent.yaml +58 -0
- src/agents/research/prompts/en/reporting_agent.yaml +380 -0
- src/agents/research/prompts/en/research_agent.yaml +173 -0
- src/agents/research/prompts/zh/decompose_agent.yaml +89 -0
- src/agents/research/prompts/zh/manager_agent.yaml +24 -0
- src/agents/research/prompts/zh/note_agent.yaml +121 -0
- src/agents/research/prompts/zh/rephrase_agent.yaml +58 -0
- src/agents/research/prompts/zh/reporting_agent.yaml +380 -0
- src/agents/research/prompts/zh/research_agent.yaml +173 -0
- src/agents/research/research_pipeline.py +1309 -0
- src/agents/research/utils/__init__.py +60 -0
- src/agents/research/utils/citation_manager.py +799 -0
- src/agents/research/utils/json_utils.py +98 -0
- src/agents/research/utils/token_tracker.py +297 -0
- src/agents/solve/__init__.py +80 -0
- src/agents/solve/analysis_loop/__init__.py +14 -0
- src/agents/solve/analysis_loop/investigate_agent.py +414 -0
- src/agents/solve/analysis_loop/note_agent.py +190 -0
- src/agents/solve/main_solver.py +862 -0
- src/agents/solve/memory/__init__.py +34 -0
- src/agents/solve/memory/citation_memory.py +353 -0
- src/agents/solve/memory/investigate_memory.py +226 -0
- src/agents/solve/memory/solve_memory.py +340 -0
- src/agents/solve/prompts/en/analysis_loop/investigate_agent.yaml +55 -0
- src/agents/solve/prompts/en/analysis_loop/note_agent.yaml +54 -0
- src/agents/solve/prompts/en/solve_loop/manager_agent.yaml +67 -0
- src/agents/solve/prompts/en/solve_loop/precision_answer_agent.yaml +62 -0
- src/agents/solve/prompts/en/solve_loop/response_agent.yaml +90 -0
- src/agents/solve/prompts/en/solve_loop/solve_agent.yaml +75 -0
- src/agents/solve/prompts/en/solve_loop/tool_agent.yaml +38 -0
- src/agents/solve/prompts/zh/analysis_loop/investigate_agent.yaml +53 -0
- src/agents/solve/prompts/zh/analysis_loop/note_agent.yaml +54 -0
- src/agents/solve/prompts/zh/solve_loop/manager_agent.yaml +66 -0
- src/agents/solve/prompts/zh/solve_loop/precision_answer_agent.yaml +62 -0
- src/agents/solve/prompts/zh/solve_loop/response_agent.yaml +90 -0
- src/agents/solve/prompts/zh/solve_loop/solve_agent.yaml +76 -0
- src/agents/solve/prompts/zh/solve_loop/tool_agent.yaml +41 -0
- src/agents/solve/solve_loop/__init__.py +22 -0
- src/agents/solve/solve_loop/citation_manager.py +74 -0
- src/agents/solve/solve_loop/manager_agent.py +274 -0
- src/agents/solve/solve_loop/precision_answer_agent.py +96 -0
- src/agents/solve/solve_loop/response_agent.py +301 -0
- src/agents/solve/solve_loop/solve_agent.py +325 -0
- src/agents/solve/solve_loop/tool_agent.py +470 -0
- src/agents/solve/utils/__init__.py +64 -0
- src/agents/solve/utils/config_validator.py +313 -0
- src/agents/solve/utils/display_manager.py +223 -0
- src/agents/solve/utils/error_handler.py +363 -0
- src/agents/solve/utils/json_utils.py +98 -0
- src/agents/solve/utils/performance_monitor.py +407 -0
- src/agents/solve/utils/token_tracker.py +541 -0
- src/api/__init__.py +0 -0
- src/api/main.py +240 -0
- src/api/routers/__init__.py +1 -0
- src/api/routers/agent_config.py +69 -0
- src/api/routers/chat.py +296 -0
- src/api/routers/co_writer.py +337 -0
- src/api/routers/config.py +627 -0
- src/api/routers/dashboard.py +18 -0
- src/api/routers/guide.py +337 -0
- src/api/routers/ideagen.py +436 -0
- src/api/routers/knowledge.py +821 -0
- src/api/routers/notebook.py +247 -0
- src/api/routers/question.py +537 -0
- src/api/routers/research.py +394 -0
- src/api/routers/settings.py +164 -0
- src/api/routers/solve.py +305 -0
- src/api/routers/system.py +252 -0
- src/api/run_server.py +61 -0
- src/api/utils/history.py +172 -0
- src/api/utils/log_interceptor.py +21 -0
- src/api/utils/notebook_manager.py +415 -0
- src/api/utils/progress_broadcaster.py +72 -0
- src/api/utils/task_id_manager.py +100 -0
- src/config/__init__.py +0 -0
- src/config/accessors.py +18 -0
- src/config/constants.py +34 -0
- src/config/defaults.py +18 -0
- src/config/schema.py +38 -0
- src/config/settings.py +50 -0
- src/core/errors.py +62 -0
- src/knowledge/__init__.py +23 -0
- src/knowledge/add_documents.py +606 -0
- src/knowledge/config.py +65 -0
- src/knowledge/example_add_documents.py +236 -0
- src/knowledge/extract_numbered_items.py +1039 -0
- src/knowledge/initializer.py +621 -0
- src/knowledge/kb.py +22 -0
- src/knowledge/manager.py +782 -0
- src/knowledge/progress_tracker.py +182 -0
- src/knowledge/start_kb.py +535 -0
- src/logging/__init__.py +103 -0
- src/logging/adapters/__init__.py +17 -0
- src/logging/adapters/lightrag.py +184 -0
- src/logging/adapters/llamaindex.py +141 -0
- src/logging/config.py +80 -0
- src/logging/handlers/__init__.py +20 -0
- src/logging/handlers/console.py +75 -0
- src/logging/handlers/file.py +201 -0
- src/logging/handlers/websocket.py +127 -0
- src/logging/logger.py +709 -0
- src/logging/stats/__init__.py +16 -0
- src/logging/stats/llm_stats.py +179 -0
- src/services/__init__.py +56 -0
- src/services/config/__init__.py +61 -0
- src/services/config/knowledge_base_config.py +210 -0
- src/services/config/loader.py +260 -0
- src/services/config/unified_config.py +603 -0
- src/services/embedding/__init__.py +45 -0
- src/services/embedding/adapters/__init__.py +22 -0
- src/services/embedding/adapters/base.py +106 -0
- src/services/embedding/adapters/cohere.py +127 -0
- src/services/embedding/adapters/jina.py +99 -0
- src/services/embedding/adapters/ollama.py +116 -0
- src/services/embedding/adapters/openai_compatible.py +96 -0
- src/services/embedding/client.py +159 -0
- src/services/embedding/config.py +156 -0
- src/services/embedding/provider.py +119 -0
- src/services/llm/__init__.py +152 -0
- src/services/llm/capabilities.py +313 -0
- src/services/llm/client.py +302 -0
- src/services/llm/cloud_provider.py +530 -0
- src/services/llm/config.py +200 -0
- src/services/llm/error_mapping.py +103 -0
- src/services/llm/exceptions.py +152 -0
- src/services/llm/factory.py +450 -0
- src/services/llm/local_provider.py +347 -0
- src/services/llm/providers/anthropic.py +95 -0
- src/services/llm/providers/base_provider.py +93 -0
- src/services/llm/providers/open_ai.py +83 -0
- src/services/llm/registry.py +71 -0
- src/services/llm/telemetry.py +40 -0
- src/services/llm/types.py +27 -0
- src/services/llm/utils.py +333 -0
- src/services/prompt/__init__.py +25 -0
- src/services/prompt/manager.py +206 -0
- src/services/rag/__init__.py +64 -0
- src/services/rag/components/__init__.py +29 -0
- src/services/rag/components/base.py +59 -0
- src/services/rag/components/chunkers/__init__.py +18 -0
- src/services/rag/components/chunkers/base.py +34 -0
- src/services/rag/components/chunkers/fixed.py +71 -0
- src/services/rag/components/chunkers/numbered_item.py +94 -0
- src/services/rag/components/chunkers/semantic.py +97 -0
- src/services/rag/components/embedders/__init__.py +14 -0
- src/services/rag/components/embedders/base.py +32 -0
- src/services/rag/components/embedders/openai.py +63 -0
- src/services/rag/components/indexers/__init__.py +18 -0
- src/services/rag/components/indexers/base.py +35 -0
- src/services/rag/components/indexers/graph.py +172 -0
- src/services/rag/components/indexers/lightrag.py +156 -0
- src/services/rag/components/indexers/vector.py +146 -0
- src/services/rag/components/parsers/__init__.py +18 -0
- src/services/rag/components/parsers/base.py +35 -0
- src/services/rag/components/parsers/markdown.py +52 -0
- src/services/rag/components/parsers/pdf.py +115 -0
- src/services/rag/components/parsers/text.py +86 -0
- src/services/rag/components/retrievers/__init__.py +18 -0
- src/services/rag/components/retrievers/base.py +34 -0
- src/services/rag/components/retrievers/dense.py +200 -0
- src/services/rag/components/retrievers/hybrid.py +164 -0
- src/services/rag/components/retrievers/lightrag.py +169 -0
- src/services/rag/components/routing.py +286 -0
- src/services/rag/factory.py +234 -0
- src/services/rag/pipeline.py +215 -0
- src/services/rag/pipelines/__init__.py +32 -0
- src/services/rag/pipelines/academic.py +44 -0
- src/services/rag/pipelines/lightrag.py +43 -0
- src/services/rag/pipelines/llamaindex.py +313 -0
- src/services/rag/pipelines/raganything.py +384 -0
- src/services/rag/service.py +244 -0
- src/services/rag/types.py +73 -0
- src/services/search/__init__.py +284 -0
- src/services/search/base.py +87 -0
- src/services/search/consolidation.py +398 -0
- src/services/search/providers/__init__.py +128 -0
- src/services/search/providers/baidu.py +188 -0
- src/services/search/providers/exa.py +194 -0
- src/services/search/providers/jina.py +161 -0
- src/services/search/providers/perplexity.py +153 -0
- src/services/search/providers/serper.py +209 -0
- src/services/search/providers/tavily.py +161 -0
- src/services/search/types.py +114 -0
- src/services/setup/__init__.py +34 -0
- src/services/setup/init.py +285 -0
- src/services/tts/__init__.py +16 -0
- src/services/tts/config.py +99 -0
- src/tools/__init__.py +91 -0
- src/tools/code_executor.py +536 -0
- src/tools/paper_search_tool.py +171 -0
- src/tools/query_item_tool.py +310 -0
- src/tools/question/__init__.py +15 -0
- src/tools/question/exam_mimic.py +616 -0
- src/tools/question/pdf_parser.py +211 -0
- src/tools/question/question_extractor.py +397 -0
- src/tools/rag_tool.py +173 -0
- src/tools/tex_chunker.py +339 -0
- src/tools/tex_downloader.py +253 -0
- src/tools/web_search.py +71 -0
- src/utils/config_manager.py +206 -0
- src/utils/document_validator.py +168 -0
- src/utils/error_rate_tracker.py +111 -0
- src/utils/error_utils.py +82 -0
- src/utils/json_parser.py +110 -0
- src/utils/network/circuit_breaker.py +79 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
LLM Configuration
|
|
4
|
+
=================
|
|
5
|
+
|
|
6
|
+
Configuration management for LLM services.
|
|
7
|
+
Simplified version - loads from unified config service or falls back to .env.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
import logging
|
|
12
|
+
import os
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
import re
|
|
15
|
+
from typing import Optional
|
|
16
|
+
|
|
17
|
+
from dotenv import load_dotenv
|
|
18
|
+
|
|
19
|
+
from .exceptions import LLMConfigError
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
# Load environment variables
|
|
24
|
+
PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent.parent
|
|
25
|
+
load_dotenv(PROJECT_ROOT / "DeepTutor.env", override=False)
|
|
26
|
+
load_dotenv(PROJECT_ROOT / ".env", override=False)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class LLMConfig:
|
|
31
|
+
"""LLM configuration dataclass."""
|
|
32
|
+
|
|
33
|
+
model: str
|
|
34
|
+
api_key: str
|
|
35
|
+
base_url: Optional[str] = None
|
|
36
|
+
binding: str = "openai"
|
|
37
|
+
api_version: Optional[str] = None
|
|
38
|
+
max_tokens: int = 4096
|
|
39
|
+
temperature: float = 0.7
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _strip_value(value: Optional[str]) -> Optional[str]:
|
|
43
|
+
"""Remove leading/trailing whitespace and quotes from string."""
|
|
44
|
+
if value is None:
|
|
45
|
+
return None
|
|
46
|
+
return value.strip().strip("\"'")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _get_llm_config_from_env() -> LLMConfig:
|
|
50
|
+
"""Get LLM configuration from environment variables."""
|
|
51
|
+
binding = _strip_value(os.getenv("LLM_BINDING", "openai"))
|
|
52
|
+
model = _strip_value(os.getenv("LLM_MODEL"))
|
|
53
|
+
api_key = _strip_value(os.getenv("LLM_API_KEY"))
|
|
54
|
+
base_url = _strip_value(os.getenv("LLM_HOST"))
|
|
55
|
+
api_version = _strip_value(os.getenv("LLM_API_VERSION"))
|
|
56
|
+
|
|
57
|
+
# Validate required configuration
|
|
58
|
+
if not model:
|
|
59
|
+
raise LLMConfigError(
|
|
60
|
+
"LLM_MODEL not set, please configure it in .env file or add a configuration in Settings"
|
|
61
|
+
)
|
|
62
|
+
if not base_url:
|
|
63
|
+
raise LLMConfigError(
|
|
64
|
+
"LLM_HOST not set, please configure it in .env file or add a configuration in Settings"
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
return LLMConfig(
|
|
68
|
+
binding=binding,
|
|
69
|
+
model=model,
|
|
70
|
+
api_key=api_key or "",
|
|
71
|
+
base_url=base_url,
|
|
72
|
+
api_version=api_version,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def get_llm_config() -> LLMConfig:
|
|
77
|
+
"""
|
|
78
|
+
Load LLM configuration.
|
|
79
|
+
|
|
80
|
+
Priority:
|
|
81
|
+
1. Active configuration from unified config service
|
|
82
|
+
2. Environment variables (.env)
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
LLMConfig: Configuration dataclass
|
|
86
|
+
|
|
87
|
+
Raises:
|
|
88
|
+
LLMConfigError: If required configuration is missing
|
|
89
|
+
"""
|
|
90
|
+
# 1. Try to get active config from unified config service
|
|
91
|
+
try:
|
|
92
|
+
from src.services.config import get_active_llm_config
|
|
93
|
+
|
|
94
|
+
config = get_active_llm_config()
|
|
95
|
+
if config:
|
|
96
|
+
return LLMConfig(
|
|
97
|
+
binding=config.get("provider", "openai"),
|
|
98
|
+
model=config["model"],
|
|
99
|
+
api_key=config.get("api_key", ""),
|
|
100
|
+
base_url=config.get("base_url"),
|
|
101
|
+
api_version=config.get("api_version"),
|
|
102
|
+
)
|
|
103
|
+
except ImportError:
|
|
104
|
+
# Unified config service not yet available, fall back to env
|
|
105
|
+
pass
|
|
106
|
+
except Exception as e:
|
|
107
|
+
logger.warning(f"Failed to load from unified config: {e}")
|
|
108
|
+
|
|
109
|
+
# 2. Fallback to environment variables
|
|
110
|
+
return _get_llm_config_from_env()
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
async def get_llm_config_async() -> LLMConfig:
|
|
114
|
+
"""
|
|
115
|
+
Async version of get_llm_config for non-blocking configuration loading.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
LLMConfig: Configuration dataclass
|
|
119
|
+
|
|
120
|
+
Raises:
|
|
121
|
+
LLMConfigError: If required configuration is missing
|
|
122
|
+
"""
|
|
123
|
+
# 1. Try to get active config from unified config service
|
|
124
|
+
try:
|
|
125
|
+
from src.services.config import get_active_llm_config
|
|
126
|
+
|
|
127
|
+
config = get_active_llm_config()
|
|
128
|
+
if config:
|
|
129
|
+
return LLMConfig(
|
|
130
|
+
binding=config.get("provider", "openai"),
|
|
131
|
+
model=config["model"],
|
|
132
|
+
api_key=config.get("api_key", ""),
|
|
133
|
+
base_url=config.get("base_url"),
|
|
134
|
+
api_version=config.get("api_version"),
|
|
135
|
+
)
|
|
136
|
+
except ImportError:
|
|
137
|
+
pass
|
|
138
|
+
except Exception as e:
|
|
139
|
+
logger.warning(f"Failed to load from unified config: {e}")
|
|
140
|
+
|
|
141
|
+
# 2. Fallback to environment variables
|
|
142
|
+
return _get_llm_config_from_env()
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def uses_max_completion_tokens(model: str) -> bool:
|
|
146
|
+
"""
|
|
147
|
+
Check if the model uses max_completion_tokens instead of max_tokens.
|
|
148
|
+
|
|
149
|
+
Newer OpenAI models (o1, o3, gpt-4o, gpt-5.x, etc.) require max_completion_tokens
|
|
150
|
+
while older models use max_tokens.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
model: The model name
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
True if the model requires max_completion_tokens, False otherwise
|
|
157
|
+
"""
|
|
158
|
+
model_lower = model.lower()
|
|
159
|
+
|
|
160
|
+
# Models that require max_completion_tokens:
|
|
161
|
+
# - o1, o3 series (reasoning models)
|
|
162
|
+
# - gpt-4o series
|
|
163
|
+
# - gpt-5.x and later
|
|
164
|
+
patterns = [
|
|
165
|
+
r"^o[13]", # o1, o3 models
|
|
166
|
+
r"^gpt-4o", # gpt-4o models
|
|
167
|
+
r"^gpt-[5-9]", # gpt-5.x and later
|
|
168
|
+
r"^gpt-\d{2,}", # gpt-10+ (future proofing)
|
|
169
|
+
]
|
|
170
|
+
|
|
171
|
+
for pattern in patterns:
|
|
172
|
+
if re.match(pattern, model_lower):
|
|
173
|
+
return True
|
|
174
|
+
|
|
175
|
+
return False
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def get_token_limit_kwargs(model: str, max_tokens: int) -> dict:
|
|
179
|
+
"""
|
|
180
|
+
Get the appropriate token limit parameter for the model.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
model: The model name
|
|
184
|
+
max_tokens: The desired token limit
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
Dictionary with either {"max_tokens": value} or {"max_completion_tokens": value}
|
|
188
|
+
"""
|
|
189
|
+
if uses_max_completion_tokens(model):
|
|
190
|
+
return {"max_completion_tokens": max_tokens}
|
|
191
|
+
return {"max_tokens": max_tokens}
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
__all__ = [
|
|
195
|
+
"LLMConfig",
|
|
196
|
+
"get_llm_config",
|
|
197
|
+
"get_llm_config_async",
|
|
198
|
+
"uses_max_completion_tokens",
|
|
199
|
+
"get_token_limit_kwargs",
|
|
200
|
+
]
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Error Mapping - Map provider-specific errors to unified exceptions.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
import logging
|
|
9
|
+
from typing import Callable, List, Optional, Type
|
|
10
|
+
|
|
11
|
+
# Import unified exceptions from exceptions.py
|
|
12
|
+
from .exceptions import (
|
|
13
|
+
LLMAPIError,
|
|
14
|
+
LLMAuthenticationError,
|
|
15
|
+
LLMError,
|
|
16
|
+
LLMRateLimitError,
|
|
17
|
+
ProviderContextWindowError,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
import openai # type: ignore
|
|
22
|
+
|
|
23
|
+
_HAS_OPENAI = True
|
|
24
|
+
except ImportError: # pragma: no cover
|
|
25
|
+
openai = None # type: ignore
|
|
26
|
+
_HAS_OPENAI = False
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
logger = logging.getLogger(__name__)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
ErrorClassifier = Callable[[Exception], bool]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(frozen=True)
|
|
36
|
+
class MappingRule:
|
|
37
|
+
classifier: ErrorClassifier
|
|
38
|
+
factory: Callable[[Exception, Optional[str]], LLMError]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _instance_of(*types: Type[BaseException]) -> ErrorClassifier:
|
|
42
|
+
return lambda exc: isinstance(exc, types)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _message_contains(*needles: str) -> ErrorClassifier:
|
|
46
|
+
def _classifier(exc: Exception) -> bool:
|
|
47
|
+
msg = str(exc).lower()
|
|
48
|
+
return any(needle in msg for needle in needles)
|
|
49
|
+
|
|
50
|
+
return _classifier
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
_GLOBAL_RULES: List[MappingRule] = [
|
|
54
|
+
MappingRule(
|
|
55
|
+
classifier=_message_contains("rate limit", "429", "quota"),
|
|
56
|
+
factory=lambda exc, provider: LLMRateLimitError(str(exc), provider=provider),
|
|
57
|
+
),
|
|
58
|
+
MappingRule(
|
|
59
|
+
classifier=_message_contains("context length", "maximum context"),
|
|
60
|
+
factory=lambda exc, provider: ProviderContextWindowError(str(exc), provider=provider),
|
|
61
|
+
),
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
if _HAS_OPENAI:
|
|
65
|
+
_GLOBAL_RULES[:0] = [
|
|
66
|
+
MappingRule(
|
|
67
|
+
classifier=_instance_of(openai.AuthenticationError),
|
|
68
|
+
factory=lambda exc, provider: LLMAuthenticationError(str(exc), provider=provider),
|
|
69
|
+
),
|
|
70
|
+
MappingRule(
|
|
71
|
+
classifier=_instance_of(openai.RateLimitError),
|
|
72
|
+
factory=lambda exc, provider: LLMRateLimitError(str(exc), provider=provider),
|
|
73
|
+
),
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
# Attempt to load Anthropic and Google rules if SDKs are present
|
|
77
|
+
try:
|
|
78
|
+
import anthropic
|
|
79
|
+
|
|
80
|
+
_GLOBAL_RULES.append(
|
|
81
|
+
MappingRule(
|
|
82
|
+
classifier=_instance_of(anthropic.RateLimitError),
|
|
83
|
+
factory=lambda exc, provider: LLMRateLimitError(str(exc), provider=provider),
|
|
84
|
+
)
|
|
85
|
+
)
|
|
86
|
+
except ImportError:
|
|
87
|
+
pass
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def map_error(exc: Exception, provider: Optional[str] = None) -> LLMError:
|
|
91
|
+
"""Map provider-specific errors to unified internal exceptions."""
|
|
92
|
+
# Heuristic check for status codes before rules
|
|
93
|
+
status_code = getattr(exc, "status_code", None)
|
|
94
|
+
if status_code == 401:
|
|
95
|
+
return LLMAuthenticationError(str(exc), provider=provider)
|
|
96
|
+
if status_code == 429:
|
|
97
|
+
return LLMRateLimitError(str(exc), provider=provider)
|
|
98
|
+
|
|
99
|
+
for rule in _GLOBAL_RULES:
|
|
100
|
+
if rule.classifier(exc):
|
|
101
|
+
return rule.factory(exc, provider)
|
|
102
|
+
|
|
103
|
+
return LLMAPIError(str(exc), status_code=status_code, provider=provider)
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"""
|
|
2
|
+
LLM Service Exceptions
|
|
3
|
+
======================
|
|
4
|
+
|
|
5
|
+
Custom exception classes for the LLM service.
|
|
6
|
+
Provides a consistent exception hierarchy for better error handling.
|
|
7
|
+
Maintains parity with upstream dev branch.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from typing import Any, Dict, Optional
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class LLMError(Exception):
|
|
14
|
+
"""Base exception for all LLM-related errors."""
|
|
15
|
+
|
|
16
|
+
def __init__(
|
|
17
|
+
self, message: str, details: Optional[Dict[str, Any]] = None, provider: Optional[str] = None
|
|
18
|
+
):
|
|
19
|
+
super().__init__(message)
|
|
20
|
+
self.message = message
|
|
21
|
+
self.details = details or {}
|
|
22
|
+
self.provider = provider
|
|
23
|
+
|
|
24
|
+
def __str__(self) -> str:
|
|
25
|
+
provider_prefix = f"[{self.provider}] " if self.provider else ""
|
|
26
|
+
if self.details:
|
|
27
|
+
return f"{provider_prefix}{self.message} (details: {self.details})"
|
|
28
|
+
return f"{provider_prefix}{self.message}"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class LLMConfigError(LLMError):
|
|
32
|
+
"""Raised when there's an error in LLM configuration."""
|
|
33
|
+
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class LLMProviderError(LLMError):
|
|
38
|
+
"""Raised when there's an error with the LLM provider."""
|
|
39
|
+
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class LLMAPIError(LLMError):
|
|
44
|
+
"""
|
|
45
|
+
Raised when an API call to an LLM provider fails.
|
|
46
|
+
Standardizes status_code and provider name.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
message: str,
|
|
52
|
+
status_code: Optional[int] = None,
|
|
53
|
+
provider: Optional[str] = None,
|
|
54
|
+
details: Optional[Dict[str, Any]] = None,
|
|
55
|
+
):
|
|
56
|
+
super().__init__(message, details, provider)
|
|
57
|
+
self.status_code = status_code
|
|
58
|
+
|
|
59
|
+
def __str__(self) -> str:
|
|
60
|
+
parts = []
|
|
61
|
+
if self.provider:
|
|
62
|
+
parts.append(f"[{self.provider}]")
|
|
63
|
+
if self.status_code:
|
|
64
|
+
parts.append(f"HTTP {self.status_code}")
|
|
65
|
+
parts.append(self.message)
|
|
66
|
+
return " ".join(parts)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class LLMTimeoutError(LLMAPIError):
|
|
70
|
+
"""Raised when an API call times out."""
|
|
71
|
+
|
|
72
|
+
def __init__(
|
|
73
|
+
self,
|
|
74
|
+
message: str = "Request timed out",
|
|
75
|
+
timeout: Optional[float] = None,
|
|
76
|
+
provider: Optional[str] = None,
|
|
77
|
+
):
|
|
78
|
+
super().__init__(message, status_code=408, provider=provider)
|
|
79
|
+
self.timeout = timeout
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class LLMRateLimitError(LLMAPIError):
|
|
83
|
+
"""Raised when rate limited by the API."""
|
|
84
|
+
|
|
85
|
+
def __init__(
|
|
86
|
+
self,
|
|
87
|
+
message: str = "Rate limit exceeded",
|
|
88
|
+
retry_after: Optional[float] = None,
|
|
89
|
+
provider: Optional[str] = None,
|
|
90
|
+
):
|
|
91
|
+
super().__init__(message, status_code=429, provider=provider)
|
|
92
|
+
self.retry_after = retry_after
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class LLMAuthenticationError(LLMAPIError):
|
|
96
|
+
"""Raised when authentication fails (invalid API key, etc.)."""
|
|
97
|
+
|
|
98
|
+
def __init__(
|
|
99
|
+
self,
|
|
100
|
+
message: str = "Authentication failed",
|
|
101
|
+
provider: Optional[str] = None,
|
|
102
|
+
):
|
|
103
|
+
super().__init__(message, status_code=401, provider=provider)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class LLMModelNotFoundError(LLMAPIError):
|
|
107
|
+
"""Raised when the requested model is not found."""
|
|
108
|
+
|
|
109
|
+
def __init__(
|
|
110
|
+
self,
|
|
111
|
+
message: str = "Model not found",
|
|
112
|
+
model: Optional[str] = None,
|
|
113
|
+
provider: Optional[str] = None,
|
|
114
|
+
):
|
|
115
|
+
super().__init__(message, status_code=404, provider=provider)
|
|
116
|
+
self.model = model
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class LLMParseError(LLMError):
|
|
120
|
+
"""Raised when parsing LLM output fails."""
|
|
121
|
+
|
|
122
|
+
def __init__(
|
|
123
|
+
self,
|
|
124
|
+
message: str = "Failed to parse LLM output",
|
|
125
|
+
provider: Optional[str] = None,
|
|
126
|
+
details: Optional[Dict[str, Any]] = None,
|
|
127
|
+
):
|
|
128
|
+
super().__init__(message, details=details, provider=provider)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
# Multi-provider specific aliases for mapping rules
|
|
132
|
+
class ProviderQuotaExceededError(LLMRateLimitError):
|
|
133
|
+
pass
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class ProviderContextWindowError(LLMAPIError):
|
|
137
|
+
pass
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
__all__ = [
|
|
141
|
+
"LLMError",
|
|
142
|
+
"LLMConfigError",
|
|
143
|
+
"LLMProviderError",
|
|
144
|
+
"LLMAPIError",
|
|
145
|
+
"LLMTimeoutError",
|
|
146
|
+
"LLMRateLimitError",
|
|
147
|
+
"LLMAuthenticationError",
|
|
148
|
+
"LLMModelNotFoundError",
|
|
149
|
+
"LLMParseError",
|
|
150
|
+
"ProviderQuotaExceededError",
|
|
151
|
+
"ProviderContextWindowError",
|
|
152
|
+
]
|