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.
Files changed (276) hide show
  1. realtimex_deeptutor/__init__.py +67 -0
  2. realtimex_deeptutor-0.5.0.post1.dist-info/METADATA +1612 -0
  3. realtimex_deeptutor-0.5.0.post1.dist-info/RECORD +276 -0
  4. realtimex_deeptutor-0.5.0.post1.dist-info/WHEEL +5 -0
  5. realtimex_deeptutor-0.5.0.post1.dist-info/entry_points.txt +2 -0
  6. realtimex_deeptutor-0.5.0.post1.dist-info/licenses/LICENSE +661 -0
  7. realtimex_deeptutor-0.5.0.post1.dist-info/top_level.txt +2 -0
  8. src/__init__.py +40 -0
  9. src/agents/__init__.py +24 -0
  10. src/agents/base_agent.py +657 -0
  11. src/agents/chat/__init__.py +24 -0
  12. src/agents/chat/chat_agent.py +435 -0
  13. src/agents/chat/prompts/en/chat_agent.yaml +35 -0
  14. src/agents/chat/prompts/zh/chat_agent.yaml +35 -0
  15. src/agents/chat/session_manager.py +311 -0
  16. src/agents/co_writer/__init__.py +0 -0
  17. src/agents/co_writer/edit_agent.py +260 -0
  18. src/agents/co_writer/narrator_agent.py +423 -0
  19. src/agents/co_writer/prompts/en/edit_agent.yaml +113 -0
  20. src/agents/co_writer/prompts/en/narrator_agent.yaml +88 -0
  21. src/agents/co_writer/prompts/zh/edit_agent.yaml +113 -0
  22. src/agents/co_writer/prompts/zh/narrator_agent.yaml +88 -0
  23. src/agents/guide/__init__.py +16 -0
  24. src/agents/guide/agents/__init__.py +11 -0
  25. src/agents/guide/agents/chat_agent.py +104 -0
  26. src/agents/guide/agents/interactive_agent.py +223 -0
  27. src/agents/guide/agents/locate_agent.py +149 -0
  28. src/agents/guide/agents/summary_agent.py +150 -0
  29. src/agents/guide/guide_manager.py +500 -0
  30. src/agents/guide/prompts/en/chat_agent.yaml +41 -0
  31. src/agents/guide/prompts/en/interactive_agent.yaml +202 -0
  32. src/agents/guide/prompts/en/locate_agent.yaml +68 -0
  33. src/agents/guide/prompts/en/summary_agent.yaml +157 -0
  34. src/agents/guide/prompts/zh/chat_agent.yaml +41 -0
  35. src/agents/guide/prompts/zh/interactive_agent.yaml +626 -0
  36. src/agents/guide/prompts/zh/locate_agent.yaml +68 -0
  37. src/agents/guide/prompts/zh/summary_agent.yaml +157 -0
  38. src/agents/ideagen/__init__.py +12 -0
  39. src/agents/ideagen/idea_generation_workflow.py +426 -0
  40. src/agents/ideagen/material_organizer_agent.py +173 -0
  41. src/agents/ideagen/prompts/en/idea_generation.yaml +187 -0
  42. src/agents/ideagen/prompts/en/material_organizer.yaml +69 -0
  43. src/agents/ideagen/prompts/zh/idea_generation.yaml +187 -0
  44. src/agents/ideagen/prompts/zh/material_organizer.yaml +69 -0
  45. src/agents/question/__init__.py +24 -0
  46. src/agents/question/agents/__init__.py +18 -0
  47. src/agents/question/agents/generate_agent.py +381 -0
  48. src/agents/question/agents/relevance_analyzer.py +207 -0
  49. src/agents/question/agents/retrieve_agent.py +239 -0
  50. src/agents/question/coordinator.py +718 -0
  51. src/agents/question/example.py +109 -0
  52. src/agents/question/prompts/en/coordinator.yaml +75 -0
  53. src/agents/question/prompts/en/generate_agent.yaml +77 -0
  54. src/agents/question/prompts/en/relevance_analyzer.yaml +41 -0
  55. src/agents/question/prompts/en/retrieve_agent.yaml +32 -0
  56. src/agents/question/prompts/zh/coordinator.yaml +75 -0
  57. src/agents/question/prompts/zh/generate_agent.yaml +77 -0
  58. src/agents/question/prompts/zh/relevance_analyzer.yaml +39 -0
  59. src/agents/question/prompts/zh/retrieve_agent.yaml +30 -0
  60. src/agents/research/agents/__init__.py +23 -0
  61. src/agents/research/agents/decompose_agent.py +507 -0
  62. src/agents/research/agents/manager_agent.py +228 -0
  63. src/agents/research/agents/note_agent.py +180 -0
  64. src/agents/research/agents/rephrase_agent.py +263 -0
  65. src/agents/research/agents/reporting_agent.py +1333 -0
  66. src/agents/research/agents/research_agent.py +714 -0
  67. src/agents/research/data_structures.py +451 -0
  68. src/agents/research/main.py +188 -0
  69. src/agents/research/prompts/en/decompose_agent.yaml +89 -0
  70. src/agents/research/prompts/en/manager_agent.yaml +24 -0
  71. src/agents/research/prompts/en/note_agent.yaml +121 -0
  72. src/agents/research/prompts/en/rephrase_agent.yaml +58 -0
  73. src/agents/research/prompts/en/reporting_agent.yaml +380 -0
  74. src/agents/research/prompts/en/research_agent.yaml +173 -0
  75. src/agents/research/prompts/zh/decompose_agent.yaml +89 -0
  76. src/agents/research/prompts/zh/manager_agent.yaml +24 -0
  77. src/agents/research/prompts/zh/note_agent.yaml +121 -0
  78. src/agents/research/prompts/zh/rephrase_agent.yaml +58 -0
  79. src/agents/research/prompts/zh/reporting_agent.yaml +380 -0
  80. src/agents/research/prompts/zh/research_agent.yaml +173 -0
  81. src/agents/research/research_pipeline.py +1309 -0
  82. src/agents/research/utils/__init__.py +60 -0
  83. src/agents/research/utils/citation_manager.py +799 -0
  84. src/agents/research/utils/json_utils.py +98 -0
  85. src/agents/research/utils/token_tracker.py +297 -0
  86. src/agents/solve/__init__.py +80 -0
  87. src/agents/solve/analysis_loop/__init__.py +14 -0
  88. src/agents/solve/analysis_loop/investigate_agent.py +414 -0
  89. src/agents/solve/analysis_loop/note_agent.py +190 -0
  90. src/agents/solve/main_solver.py +862 -0
  91. src/agents/solve/memory/__init__.py +34 -0
  92. src/agents/solve/memory/citation_memory.py +353 -0
  93. src/agents/solve/memory/investigate_memory.py +226 -0
  94. src/agents/solve/memory/solve_memory.py +340 -0
  95. src/agents/solve/prompts/en/analysis_loop/investigate_agent.yaml +55 -0
  96. src/agents/solve/prompts/en/analysis_loop/note_agent.yaml +54 -0
  97. src/agents/solve/prompts/en/solve_loop/manager_agent.yaml +67 -0
  98. src/agents/solve/prompts/en/solve_loop/precision_answer_agent.yaml +62 -0
  99. src/agents/solve/prompts/en/solve_loop/response_agent.yaml +90 -0
  100. src/agents/solve/prompts/en/solve_loop/solve_agent.yaml +75 -0
  101. src/agents/solve/prompts/en/solve_loop/tool_agent.yaml +38 -0
  102. src/agents/solve/prompts/zh/analysis_loop/investigate_agent.yaml +53 -0
  103. src/agents/solve/prompts/zh/analysis_loop/note_agent.yaml +54 -0
  104. src/agents/solve/prompts/zh/solve_loop/manager_agent.yaml +66 -0
  105. src/agents/solve/prompts/zh/solve_loop/precision_answer_agent.yaml +62 -0
  106. src/agents/solve/prompts/zh/solve_loop/response_agent.yaml +90 -0
  107. src/agents/solve/prompts/zh/solve_loop/solve_agent.yaml +76 -0
  108. src/agents/solve/prompts/zh/solve_loop/tool_agent.yaml +41 -0
  109. src/agents/solve/solve_loop/__init__.py +22 -0
  110. src/agents/solve/solve_loop/citation_manager.py +74 -0
  111. src/agents/solve/solve_loop/manager_agent.py +274 -0
  112. src/agents/solve/solve_loop/precision_answer_agent.py +96 -0
  113. src/agents/solve/solve_loop/response_agent.py +301 -0
  114. src/agents/solve/solve_loop/solve_agent.py +325 -0
  115. src/agents/solve/solve_loop/tool_agent.py +470 -0
  116. src/agents/solve/utils/__init__.py +64 -0
  117. src/agents/solve/utils/config_validator.py +313 -0
  118. src/agents/solve/utils/display_manager.py +223 -0
  119. src/agents/solve/utils/error_handler.py +363 -0
  120. src/agents/solve/utils/json_utils.py +98 -0
  121. src/agents/solve/utils/performance_monitor.py +407 -0
  122. src/agents/solve/utils/token_tracker.py +541 -0
  123. src/api/__init__.py +0 -0
  124. src/api/main.py +240 -0
  125. src/api/routers/__init__.py +1 -0
  126. src/api/routers/agent_config.py +69 -0
  127. src/api/routers/chat.py +296 -0
  128. src/api/routers/co_writer.py +337 -0
  129. src/api/routers/config.py +627 -0
  130. src/api/routers/dashboard.py +18 -0
  131. src/api/routers/guide.py +337 -0
  132. src/api/routers/ideagen.py +436 -0
  133. src/api/routers/knowledge.py +821 -0
  134. src/api/routers/notebook.py +247 -0
  135. src/api/routers/question.py +537 -0
  136. src/api/routers/research.py +394 -0
  137. src/api/routers/settings.py +164 -0
  138. src/api/routers/solve.py +305 -0
  139. src/api/routers/system.py +252 -0
  140. src/api/run_server.py +61 -0
  141. src/api/utils/history.py +172 -0
  142. src/api/utils/log_interceptor.py +21 -0
  143. src/api/utils/notebook_manager.py +415 -0
  144. src/api/utils/progress_broadcaster.py +72 -0
  145. src/api/utils/task_id_manager.py +100 -0
  146. src/config/__init__.py +0 -0
  147. src/config/accessors.py +18 -0
  148. src/config/constants.py +34 -0
  149. src/config/defaults.py +18 -0
  150. src/config/schema.py +38 -0
  151. src/config/settings.py +50 -0
  152. src/core/errors.py +62 -0
  153. src/knowledge/__init__.py +23 -0
  154. src/knowledge/add_documents.py +606 -0
  155. src/knowledge/config.py +65 -0
  156. src/knowledge/example_add_documents.py +236 -0
  157. src/knowledge/extract_numbered_items.py +1039 -0
  158. src/knowledge/initializer.py +621 -0
  159. src/knowledge/kb.py +22 -0
  160. src/knowledge/manager.py +782 -0
  161. src/knowledge/progress_tracker.py +182 -0
  162. src/knowledge/start_kb.py +535 -0
  163. src/logging/__init__.py +103 -0
  164. src/logging/adapters/__init__.py +17 -0
  165. src/logging/adapters/lightrag.py +184 -0
  166. src/logging/adapters/llamaindex.py +141 -0
  167. src/logging/config.py +80 -0
  168. src/logging/handlers/__init__.py +20 -0
  169. src/logging/handlers/console.py +75 -0
  170. src/logging/handlers/file.py +201 -0
  171. src/logging/handlers/websocket.py +127 -0
  172. src/logging/logger.py +709 -0
  173. src/logging/stats/__init__.py +16 -0
  174. src/logging/stats/llm_stats.py +179 -0
  175. src/services/__init__.py +56 -0
  176. src/services/config/__init__.py +61 -0
  177. src/services/config/knowledge_base_config.py +210 -0
  178. src/services/config/loader.py +260 -0
  179. src/services/config/unified_config.py +603 -0
  180. src/services/embedding/__init__.py +45 -0
  181. src/services/embedding/adapters/__init__.py +22 -0
  182. src/services/embedding/adapters/base.py +106 -0
  183. src/services/embedding/adapters/cohere.py +127 -0
  184. src/services/embedding/adapters/jina.py +99 -0
  185. src/services/embedding/adapters/ollama.py +116 -0
  186. src/services/embedding/adapters/openai_compatible.py +96 -0
  187. src/services/embedding/client.py +159 -0
  188. src/services/embedding/config.py +156 -0
  189. src/services/embedding/provider.py +119 -0
  190. src/services/llm/__init__.py +152 -0
  191. src/services/llm/capabilities.py +313 -0
  192. src/services/llm/client.py +302 -0
  193. src/services/llm/cloud_provider.py +530 -0
  194. src/services/llm/config.py +200 -0
  195. src/services/llm/error_mapping.py +103 -0
  196. src/services/llm/exceptions.py +152 -0
  197. src/services/llm/factory.py +450 -0
  198. src/services/llm/local_provider.py +347 -0
  199. src/services/llm/providers/anthropic.py +95 -0
  200. src/services/llm/providers/base_provider.py +93 -0
  201. src/services/llm/providers/open_ai.py +83 -0
  202. src/services/llm/registry.py +71 -0
  203. src/services/llm/telemetry.py +40 -0
  204. src/services/llm/types.py +27 -0
  205. src/services/llm/utils.py +333 -0
  206. src/services/prompt/__init__.py +25 -0
  207. src/services/prompt/manager.py +206 -0
  208. src/services/rag/__init__.py +64 -0
  209. src/services/rag/components/__init__.py +29 -0
  210. src/services/rag/components/base.py +59 -0
  211. src/services/rag/components/chunkers/__init__.py +18 -0
  212. src/services/rag/components/chunkers/base.py +34 -0
  213. src/services/rag/components/chunkers/fixed.py +71 -0
  214. src/services/rag/components/chunkers/numbered_item.py +94 -0
  215. src/services/rag/components/chunkers/semantic.py +97 -0
  216. src/services/rag/components/embedders/__init__.py +14 -0
  217. src/services/rag/components/embedders/base.py +32 -0
  218. src/services/rag/components/embedders/openai.py +63 -0
  219. src/services/rag/components/indexers/__init__.py +18 -0
  220. src/services/rag/components/indexers/base.py +35 -0
  221. src/services/rag/components/indexers/graph.py +172 -0
  222. src/services/rag/components/indexers/lightrag.py +156 -0
  223. src/services/rag/components/indexers/vector.py +146 -0
  224. src/services/rag/components/parsers/__init__.py +18 -0
  225. src/services/rag/components/parsers/base.py +35 -0
  226. src/services/rag/components/parsers/markdown.py +52 -0
  227. src/services/rag/components/parsers/pdf.py +115 -0
  228. src/services/rag/components/parsers/text.py +86 -0
  229. src/services/rag/components/retrievers/__init__.py +18 -0
  230. src/services/rag/components/retrievers/base.py +34 -0
  231. src/services/rag/components/retrievers/dense.py +200 -0
  232. src/services/rag/components/retrievers/hybrid.py +164 -0
  233. src/services/rag/components/retrievers/lightrag.py +169 -0
  234. src/services/rag/components/routing.py +286 -0
  235. src/services/rag/factory.py +234 -0
  236. src/services/rag/pipeline.py +215 -0
  237. src/services/rag/pipelines/__init__.py +32 -0
  238. src/services/rag/pipelines/academic.py +44 -0
  239. src/services/rag/pipelines/lightrag.py +43 -0
  240. src/services/rag/pipelines/llamaindex.py +313 -0
  241. src/services/rag/pipelines/raganything.py +384 -0
  242. src/services/rag/service.py +244 -0
  243. src/services/rag/types.py +73 -0
  244. src/services/search/__init__.py +284 -0
  245. src/services/search/base.py +87 -0
  246. src/services/search/consolidation.py +398 -0
  247. src/services/search/providers/__init__.py +128 -0
  248. src/services/search/providers/baidu.py +188 -0
  249. src/services/search/providers/exa.py +194 -0
  250. src/services/search/providers/jina.py +161 -0
  251. src/services/search/providers/perplexity.py +153 -0
  252. src/services/search/providers/serper.py +209 -0
  253. src/services/search/providers/tavily.py +161 -0
  254. src/services/search/types.py +114 -0
  255. src/services/setup/__init__.py +34 -0
  256. src/services/setup/init.py +285 -0
  257. src/services/tts/__init__.py +16 -0
  258. src/services/tts/config.py +99 -0
  259. src/tools/__init__.py +91 -0
  260. src/tools/code_executor.py +536 -0
  261. src/tools/paper_search_tool.py +171 -0
  262. src/tools/query_item_tool.py +310 -0
  263. src/tools/question/__init__.py +15 -0
  264. src/tools/question/exam_mimic.py +616 -0
  265. src/tools/question/pdf_parser.py +211 -0
  266. src/tools/question/question_extractor.py +397 -0
  267. src/tools/rag_tool.py +173 -0
  268. src/tools/tex_chunker.py +339 -0
  269. src/tools/tex_downloader.py +253 -0
  270. src/tools/web_search.py +71 -0
  271. src/utils/config_manager.py +206 -0
  272. src/utils/document_validator.py +168 -0
  273. src/utils/error_rate_tracker.py +111 -0
  274. src/utils/error_utils.py +82 -0
  275. src/utils/json_parser.py +110 -0
  276. src/utils/network/circuit_breaker.py +79 -0
@@ -0,0 +1,106 @@
1
+ """
2
+ Base Embedding Adapter
3
+ =======================
4
+
5
+ Abstract base class for all embedding adapters.
6
+ Defines the contract that all embedding providers must implement.
7
+ """
8
+
9
+ from abc import ABC, abstractmethod
10
+ from dataclasses import dataclass
11
+ from typing import Any, Dict, List, Optional
12
+
13
+
14
+ @dataclass
15
+ class EmbeddingRequest:
16
+ """
17
+ Standard embedding request structure.
18
+
19
+ Provider-agnostic request format. Different providers interpret fields differently:
20
+
21
+ Args:
22
+ texts: List of texts to embed
23
+ model: Model name to use
24
+ dimensions: Embedding vector dimensions (optional)
25
+ input_type: Input type hint for task-aware embeddings (optional)
26
+ - Cohere: Maps to 'input_type' ("search_document", "search_query", "classification", "clustering")
27
+ - Jina: Maps to 'task' ("retrieval.passage", "retrieval.query", etc.)
28
+ - OpenAI/Ollama: Ignored
29
+ encoding_format: Output format ("float" or "base64", default: "float")
30
+ truncate: Whether to truncate texts that exceed max tokens (default: True)
31
+ normalized: Whether to return L2-normalized embeddings (Jina/Ollama only)
32
+ late_chunking: Enable late chunking for long context (Jina v3 only)
33
+ """
34
+
35
+ texts: List[str]
36
+ model: str
37
+ dimensions: Optional[int] = None
38
+ input_type: Optional[str] = None
39
+ encoding_format: Optional[str] = "float"
40
+ truncate: Optional[bool] = True
41
+ normalized: Optional[bool] = True
42
+ late_chunking: Optional[bool] = False
43
+
44
+
45
+ @dataclass
46
+ class EmbeddingResponse:
47
+ """Standard embedding response structure."""
48
+
49
+ embeddings: List[List[float]]
50
+ model: str
51
+ dimensions: int
52
+ usage: Dict[str, Any]
53
+
54
+
55
+ class BaseEmbeddingAdapter(ABC):
56
+ """
57
+ Base class for all embedding adapters.
58
+
59
+ Each adapter implements the specific API interface for a provider
60
+ (OpenAI, Cohere, Ollama, etc.) while exposing a unified interface.
61
+ """
62
+
63
+ def __init__(self, config: Dict[str, Any]):
64
+ """
65
+ Initialize the adapter with configuration.
66
+
67
+ Args:
68
+ config: Dictionary containing:
69
+ - api_key: API authentication key (optional for local)
70
+ - base_url: API endpoint URL
71
+ - model: Model name to use
72
+ - dimensions: Embedding vector dimensions
73
+ - request_timeout: Request timeout in seconds
74
+ """
75
+ self.api_key = config.get("api_key")
76
+ self.base_url = config.get("base_url")
77
+ self.api_version = config.get("api_version")
78
+ self.model = config.get("model")
79
+ self.dimensions = config.get("dimensions")
80
+ self.request_timeout = config.get("request_timeout", 30)
81
+
82
+ @abstractmethod
83
+ async def embed(self, request: EmbeddingRequest) -> EmbeddingResponse:
84
+ """
85
+ Generate embeddings for a list of texts.
86
+
87
+ Args:
88
+ request: EmbeddingRequest with texts and parameters
89
+
90
+ Returns:
91
+ EmbeddingResponse with embeddings and metadata
92
+
93
+ Raises:
94
+ httpx.HTTPError: If the API request fails
95
+ """
96
+ pass
97
+
98
+ @abstractmethod
99
+ def get_model_info(self) -> Dict[str, Any]:
100
+ """
101
+ Return information about the configured model.
102
+
103
+ Returns:
104
+ Dictionary with model metadata (name, dimensions, etc.)
105
+ """
106
+ pass
@@ -0,0 +1,127 @@
1
+ """Cohere Embedding Adapter for v1 and v2 API."""
2
+
3
+ import logging
4
+ from typing import Any, Dict
5
+
6
+ import httpx
7
+
8
+ from .base import BaseEmbeddingAdapter, EmbeddingRequest, EmbeddingResponse
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class CohereEmbeddingAdapter(BaseEmbeddingAdapter):
14
+ """Adapter for Cohere Embed API (v1 and v2)."""
15
+
16
+ MODELS_INFO = {
17
+ "embed-v4.0": {
18
+ "dimensions": [256, 512, 1024, 1536],
19
+ "default": 1024,
20
+ "api_version": "v2",
21
+ },
22
+ "embed-english-v3.0": {
23
+ "dimensions": [1024],
24
+ "default": 1024,
25
+ "api_version": "v1",
26
+ },
27
+ "embed-multilingual-v3.0": {
28
+ "dimensions": [1024],
29
+ "default": 1024,
30
+ "api_version": "v1",
31
+ },
32
+ "embed-multilingual-light-v3.0": {
33
+ "dimensions": [384],
34
+ "default": 384,
35
+ "api_version": "v1",
36
+ },
37
+ "embed-english-light-v3.0": {
38
+ "dimensions": [384],
39
+ "default": 384,
40
+ "api_version": "v1",
41
+ },
42
+ }
43
+
44
+ async def embed(self, request: EmbeddingRequest) -> EmbeddingResponse:
45
+ headers = {
46
+ "Authorization": f"Bearer {self.api_key}",
47
+ "Content-Type": "application/json",
48
+ }
49
+
50
+ model_name = request.model or self.model
51
+ model_info = self.MODELS_INFO.get(model_name, {})
52
+ api_version = model_info.get("api_version", "v2")
53
+ dimension = request.dimensions or self.dimensions
54
+
55
+ input_type = request.input_type or "search_document"
56
+
57
+ if api_version == "v1":
58
+ payload = {
59
+ "texts": request.texts,
60
+ "model": model_name,
61
+ "input_type": input_type,
62
+ }
63
+
64
+ if not request.truncate:
65
+ payload["truncate"] = "NONE"
66
+ else:
67
+ payload = {
68
+ "texts": request.texts,
69
+ "model": model_name,
70
+ "embedding_types": ["float"],
71
+ "input_type": input_type,
72
+ }
73
+
74
+ supported_dims = model_info.get("dimensions", [])
75
+ if isinstance(supported_dims, list) and len(supported_dims) > 1:
76
+ payload["output_dimension"] = dimension or model_info.get("default")
77
+
78
+ if not request.truncate:
79
+ payload["truncate"] = "NONE"
80
+
81
+ url = f"{self.base_url}/{api_version}/embed"
82
+
83
+ logger.debug(f"Sending embedding request to {url} with {len(request.texts)} texts")
84
+
85
+ async with httpx.AsyncClient(timeout=self.request_timeout) as client:
86
+ response = await client.post(url, json=payload, headers=headers)
87
+
88
+ if response.status_code >= 400:
89
+ logger.error(f"HTTP {response.status_code} response body: {response.text}")
90
+
91
+ response.raise_for_status()
92
+ data = response.json()
93
+
94
+ if api_version == "v1":
95
+ embeddings = data["embeddings"]
96
+ else:
97
+ embeddings = data["embeddings"]["float"]
98
+
99
+ actual_dims = len(embeddings[0]) if embeddings else 0
100
+ expected_dims = request.dimensions or self.dimensions
101
+
102
+ if expected_dims and actual_dims != expected_dims:
103
+ logger.warning(f"Dimension mismatch: expected {expected_dims}, got {actual_dims}")
104
+
105
+ logger.info(
106
+ f"Successfully generated {len(embeddings)} embeddings "
107
+ f"(model: {data.get('model', self.model)}, dimensions: {actual_dims})"
108
+ )
109
+
110
+ return EmbeddingResponse(
111
+ embeddings=embeddings,
112
+ model=data.get("model", self.model),
113
+ dimensions=actual_dims,
114
+ usage=data.get("meta", {}).get("billed_units", {}),
115
+ )
116
+
117
+ def get_model_info(self) -> Dict[str, Any]:
118
+ model_info = self.MODELS_INFO.get(self.model, {})
119
+ dimensions_list = model_info.get("dimensions", [])
120
+ return {
121
+ "model": self.model,
122
+ "dimensions": model_info.get("default", self.dimensions),
123
+ "supports_variable_dimensions": len(dimensions_list) > 1
124
+ if isinstance(dimensions_list, list)
125
+ else False,
126
+ "provider": "cohere",
127
+ }
@@ -0,0 +1,99 @@
1
+ """Jina AI embedding adapter with task-aware embeddings and late chunking."""
2
+
3
+ import logging
4
+ from typing import Any, Dict
5
+
6
+ import httpx
7
+
8
+ from .base import BaseEmbeddingAdapter, EmbeddingRequest, EmbeddingResponse
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class JinaEmbeddingAdapter(BaseEmbeddingAdapter):
14
+ MODELS_INFO = {
15
+ "jina-embeddings-v3": {"default": 1024, "dimensions": [32, 64, 128, 256, 512, 768, 1024]},
16
+ "jina-embeddings-v4": {"default": 1024, "dimensions": [32, 64, 128, 256, 512, 768, 1024]},
17
+ }
18
+
19
+ INPUT_TYPE_TO_TASK = {
20
+ "search_document": "retrieval.passage",
21
+ "search_query": "retrieval.query",
22
+ "classification": "classification",
23
+ "clustering": "separation",
24
+ "text-matching": "text-matching",
25
+ }
26
+
27
+ async def embed(self, request: EmbeddingRequest) -> EmbeddingResponse:
28
+ headers = {
29
+ "Authorization": f"Bearer {self.api_key}",
30
+ "Content-Type": "application/json",
31
+ }
32
+
33
+ payload = {
34
+ "input": request.texts,
35
+ "model": request.model or self.model,
36
+ }
37
+
38
+ if request.dimensions:
39
+ payload["dimensions"] = request.dimensions
40
+ elif self.dimensions:
41
+ payload["dimensions"] = self.dimensions
42
+
43
+ if request.input_type:
44
+ task = self.INPUT_TYPE_TO_TASK.get(request.input_type, request.input_type)
45
+ payload["task"] = task
46
+ logger.debug(f"Using Jina task: {task}")
47
+
48
+ if request.normalized is not None:
49
+ payload["normalized"] = request.normalized
50
+
51
+ if request.late_chunking:
52
+ payload["late_chunking"] = True
53
+
54
+ url = f"{self.base_url}/embeddings"
55
+
56
+ logger.debug(f"Sending embedding request to {url} with {len(request.texts)} texts")
57
+
58
+ async with httpx.AsyncClient(timeout=self.request_timeout) as client:
59
+ response = await client.post(url, json=payload, headers=headers)
60
+
61
+ if response.status_code >= 400:
62
+ logger.error(f"HTTP {response.status_code} response body: {response.text}")
63
+
64
+ response.raise_for_status()
65
+ data = response.json()
66
+
67
+ embeddings = [item["embedding"] for item in data["data"]]
68
+ actual_dims = len(embeddings[0]) if embeddings else 0
69
+
70
+ logger.info(
71
+ f"Successfully generated {len(embeddings)} embeddings "
72
+ f"(model: {data['model']}, dimensions: {actual_dims})"
73
+ )
74
+
75
+ return EmbeddingResponse(
76
+ embeddings=embeddings,
77
+ model=data["model"],
78
+ dimensions=actual_dims,
79
+ usage=data.get("usage", {}),
80
+ )
81
+
82
+ def get_model_info(self) -> Dict[str, Any]:
83
+ model_info = self.MODELS_INFO.get(self.model, self.dimensions)
84
+
85
+ if isinstance(model_info, dict):
86
+ return {
87
+ "model": self.model,
88
+ "dimensions": model_info.get("default", self.dimensions),
89
+ "supported_dimensions": model_info.get("dimensions", []),
90
+ "supports_variable_dimensions": True,
91
+ "provider": "jina",
92
+ }
93
+ else:
94
+ return {
95
+ "model": self.model,
96
+ "dimensions": model_info or self.dimensions,
97
+ "supports_variable_dimensions": False,
98
+ "provider": "jina",
99
+ }
@@ -0,0 +1,116 @@
1
+ """Ollama Embedding Adapter for local embeddings."""
2
+
3
+ import logging
4
+ from typing import Any, Dict
5
+
6
+ import httpx
7
+
8
+ from .base import BaseEmbeddingAdapter, EmbeddingRequest, EmbeddingResponse
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class OllamaEmbeddingAdapter(BaseEmbeddingAdapter):
14
+ MODELS_INFO = {
15
+ "all-minilm": 384,
16
+ "all-mpnet-base-v2": 768,
17
+ "nomic-embed-text": 768,
18
+ "mxbai-embed-large": 1024,
19
+ "snowflake-arctic-embed": 1024,
20
+ }
21
+
22
+ async def embed(self, request: EmbeddingRequest) -> EmbeddingResponse:
23
+ payload = {
24
+ "model": request.model or self.model,
25
+ "input": request.texts,
26
+ }
27
+
28
+ if request.dimensions or self.dimensions:
29
+ payload["dimensions"] = request.dimensions or self.dimensions
30
+
31
+ if request.truncate is not None:
32
+ payload["truncate"] = request.truncate
33
+
34
+ payload["keep_alive"] = "5m"
35
+
36
+ url = f"{self.base_url}/api/embed"
37
+
38
+ logger.debug(f"Sending embedding request to {url} with {len(request.texts)} texts")
39
+
40
+ try:
41
+ async with httpx.AsyncClient(timeout=self.request_timeout) as client:
42
+ response = await client.post(url, json=payload)
43
+
44
+ if response.status_code == 404:
45
+ try:
46
+ health_check = await client.get(f"{self.base_url}/api/tags")
47
+ if health_check.status_code == 200:
48
+ available_models = [
49
+ m.get("name", "") for m in health_check.json().get("models", [])
50
+ ]
51
+ raise ValueError(
52
+ f"Model '{payload['model']}' not found in Ollama. "
53
+ f"Available models: {', '.join(available_models[:10])}. "
54
+ f"Download it with: ollama pull {payload['model']}"
55
+ )
56
+ except httpx.HTTPError:
57
+ pass
58
+
59
+ raise ValueError(
60
+ f"Model '{payload['model']}' not found. "
61
+ f"Download it with: ollama pull {payload['model']}"
62
+ )
63
+
64
+ response.raise_for_status()
65
+ data = response.json()
66
+
67
+ except httpx.ConnectError as e:
68
+ raise ConnectionError(
69
+ f"Cannot connect to Ollama at {self.base_url}. "
70
+ f"Make sure Ollama is running. Start it with: ollama serve"
71
+ ) from e
72
+
73
+ except httpx.TimeoutException as e:
74
+ raise TimeoutError(
75
+ f"Request to Ollama timed out after {self.request_timeout}s. "
76
+ f"The model might be too large or the server is overloaded."
77
+ ) from e
78
+
79
+ except httpx.HTTPError as e:
80
+ logger.error(f"Ollama API error: {e}")
81
+ raise
82
+
83
+ embeddings = data["embeddings"]
84
+
85
+ actual_dims = len(embeddings[0]) if embeddings else 0
86
+ expected_dims = request.dimensions or self.dimensions
87
+
88
+ if expected_dims and actual_dims != expected_dims:
89
+ logger.warning(
90
+ f"Dimension mismatch: expected {expected_dims}, got {actual_dims}. "
91
+ f"Model '{payload['model']}' may not support custom dimensions."
92
+ )
93
+
94
+ logger.info(
95
+ f"Successfully generated {len(embeddings)} embeddings "
96
+ f"(model: {data.get('model', self.model)}, dimensions: {actual_dims})"
97
+ )
98
+
99
+ return EmbeddingResponse(
100
+ embeddings=embeddings,
101
+ model=data.get("model", self.model),
102
+ dimensions=actual_dims,
103
+ usage={
104
+ "prompt_eval_count": data.get("prompt_eval_count", 0),
105
+ "total_duration": data.get("total_duration", 0),
106
+ },
107
+ )
108
+
109
+ def get_model_info(self) -> Dict[str, Any]:
110
+ return {
111
+ "model": self.model,
112
+ "dimensions": self.MODELS_INFO.get(self.model, self.dimensions),
113
+ "local": True,
114
+ "supports_variable_dimensions": False,
115
+ "provider": "ollama",
116
+ }
@@ -0,0 +1,96 @@
1
+ """OpenAI-compatible embedding adapter for OpenAI, Azure, HuggingFace, LM Studio, etc."""
2
+
3
+ import logging
4
+ from typing import Any, Dict
5
+
6
+ import httpx
7
+
8
+ from .base import BaseEmbeddingAdapter, EmbeddingRequest, EmbeddingResponse
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class OpenAICompatibleEmbeddingAdapter(BaseEmbeddingAdapter):
14
+ MODELS_INFO = {
15
+ "text-embedding-3-large": {"default": 3072, "dimensions": [256, 512, 1024, 3072]},
16
+ "text-embedding-3-small": {"default": 1536, "dimensions": [512, 1536]},
17
+ "text-embedding-ada-002": 1536,
18
+ }
19
+
20
+ async def embed(self, request: EmbeddingRequest) -> EmbeddingResponse:
21
+ headers = {
22
+ "Content-Type": "application/json",
23
+ }
24
+ if self.api_version:
25
+ headers["api-key"] = self.api_key
26
+ else:
27
+ headers["Authorization"] = f"Bearer {self.api_key}"
28
+
29
+ payload = {
30
+ "input": request.texts,
31
+ "model": request.model or self.model,
32
+ "encoding_format": request.encoding_format or "float",
33
+ }
34
+
35
+ if request.dimensions or self.dimensions:
36
+ payload["dimensions"] = request.dimensions or self.dimensions
37
+
38
+ url = f"{self.base_url.rstrip('/')}/embeddings"
39
+ if self.api_version:
40
+ if "?" not in url:
41
+ url += f"?api-version={self.api_version}"
42
+ else:
43
+ url += f"&api-version={self.api_version}"
44
+
45
+ logger.debug(f"Sending embedding request to {url} with {len(request.texts)} texts")
46
+
47
+ async with httpx.AsyncClient(timeout=self.request_timeout) as client:
48
+ response = await client.post(url, json=payload, headers=headers)
49
+
50
+ if response.status_code >= 400:
51
+ logger.error(f"HTTP {response.status_code} response body: {response.text}")
52
+
53
+ response.raise_for_status()
54
+ data = response.json()
55
+
56
+ embeddings = [item["embedding"] for item in data["data"]]
57
+
58
+ actual_dims = len(embeddings[0]) if embeddings else 0
59
+ expected_dims = request.dimensions or self.dimensions
60
+
61
+ if expected_dims and actual_dims != expected_dims:
62
+ logger.warning(
63
+ f"Dimension mismatch: expected {expected_dims}, got {actual_dims}. "
64
+ f"Model '{data['model']}' may not support custom dimensions."
65
+ )
66
+
67
+ logger.info(
68
+ f"Successfully generated {len(embeddings)} embeddings "
69
+ f"(model: {data['model']}, dimensions: {actual_dims})"
70
+ )
71
+
72
+ return EmbeddingResponse(
73
+ embeddings=embeddings,
74
+ model=data["model"],
75
+ dimensions=actual_dims,
76
+ usage=data.get("usage", {}),
77
+ )
78
+
79
+ def get_model_info(self) -> Dict[str, Any]:
80
+ model_info = self.MODELS_INFO.get(self.model, self.dimensions)
81
+
82
+ if isinstance(model_info, dict):
83
+ return {
84
+ "model": self.model,
85
+ "dimensions": model_info.get("default", self.dimensions),
86
+ "supported_dimensions": model_info.get("dimensions", []),
87
+ "supports_variable_dimensions": len(model_info.get("dimensions", [])) > 1,
88
+ "provider": "openai_compatible",
89
+ }
90
+ else:
91
+ return {
92
+ "model": self.model,
93
+ "dimensions": model_info or self.dimensions,
94
+ "supports_variable_dimensions": False,
95
+ "provider": "openai_compatible",
96
+ }