agent-brain-rag 1.2.0__py3-none-any.whl → 3.0.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 (51) hide show
  1. {agent_brain_rag-1.2.0.dist-info → agent_brain_rag-3.0.0.dist-info}/METADATA +55 -18
  2. agent_brain_rag-3.0.0.dist-info/RECORD +56 -0
  3. {agent_brain_rag-1.2.0.dist-info → agent_brain_rag-3.0.0.dist-info}/WHEEL +1 -1
  4. {agent_brain_rag-1.2.0.dist-info → agent_brain_rag-3.0.0.dist-info}/entry_points.txt +0 -1
  5. agent_brain_server/__init__.py +1 -1
  6. agent_brain_server/api/main.py +146 -45
  7. agent_brain_server/api/routers/__init__.py +2 -0
  8. agent_brain_server/api/routers/health.py +85 -21
  9. agent_brain_server/api/routers/index.py +108 -36
  10. agent_brain_server/api/routers/jobs.py +111 -0
  11. agent_brain_server/config/provider_config.py +352 -0
  12. agent_brain_server/config/settings.py +22 -5
  13. agent_brain_server/indexing/__init__.py +21 -0
  14. agent_brain_server/indexing/bm25_index.py +15 -2
  15. agent_brain_server/indexing/document_loader.py +45 -4
  16. agent_brain_server/indexing/embedding.py +86 -135
  17. agent_brain_server/indexing/graph_extractors.py +582 -0
  18. agent_brain_server/indexing/graph_index.py +536 -0
  19. agent_brain_server/job_queue/__init__.py +11 -0
  20. agent_brain_server/job_queue/job_service.py +317 -0
  21. agent_brain_server/job_queue/job_store.py +427 -0
  22. agent_brain_server/job_queue/job_worker.py +434 -0
  23. agent_brain_server/locking.py +101 -8
  24. agent_brain_server/models/__init__.py +28 -0
  25. agent_brain_server/models/graph.py +253 -0
  26. agent_brain_server/models/health.py +30 -3
  27. agent_brain_server/models/job.py +289 -0
  28. agent_brain_server/models/query.py +16 -3
  29. agent_brain_server/project_root.py +1 -1
  30. agent_brain_server/providers/__init__.py +64 -0
  31. agent_brain_server/providers/base.py +251 -0
  32. agent_brain_server/providers/embedding/__init__.py +23 -0
  33. agent_brain_server/providers/embedding/cohere.py +163 -0
  34. agent_brain_server/providers/embedding/ollama.py +150 -0
  35. agent_brain_server/providers/embedding/openai.py +118 -0
  36. agent_brain_server/providers/exceptions.py +95 -0
  37. agent_brain_server/providers/factory.py +157 -0
  38. agent_brain_server/providers/summarization/__init__.py +41 -0
  39. agent_brain_server/providers/summarization/anthropic.py +87 -0
  40. agent_brain_server/providers/summarization/gemini.py +96 -0
  41. agent_brain_server/providers/summarization/grok.py +95 -0
  42. agent_brain_server/providers/summarization/ollama.py +114 -0
  43. agent_brain_server/providers/summarization/openai.py +87 -0
  44. agent_brain_server/runtime.py +2 -2
  45. agent_brain_server/services/indexing_service.py +39 -0
  46. agent_brain_server/services/query_service.py +203 -0
  47. agent_brain_server/storage/__init__.py +18 -2
  48. agent_brain_server/storage/graph_store.py +519 -0
  49. agent_brain_server/storage/vector_store.py +35 -0
  50. agent_brain_server/storage_paths.py +5 -3
  51. agent_brain_rag-1.2.0.dist-info/RECORD +0 -31
@@ -1,13 +1,23 @@
1
- """Embedding generation using OpenAI's text-embedding models."""
1
+ """Embedding generation using pluggable providers.
2
+
3
+ This module provides embedding and summarization functionality using
4
+ the configurable provider system. Providers are selected based on
5
+ config.yaml or environment defaults.
6
+ """
2
7
 
3
8
  import logging
9
+ import re
4
10
  from collections.abc import Awaitable, Callable
5
- from typing import Optional
11
+ from typing import TYPE_CHECKING, Optional
6
12
 
7
- from anthropic import AsyncAnthropic
8
- from openai import AsyncOpenAI
13
+ from agent_brain_server.config.provider_config import load_provider_settings
14
+ from agent_brain_server.providers.factory import ProviderRegistry
9
15
 
10
- from agent_brain_server.config import settings
16
+ if TYPE_CHECKING:
17
+ from agent_brain_server.providers.base import (
18
+ EmbeddingProvider,
19
+ SummarizationProvider,
20
+ )
11
21
 
12
22
  from .chunking import TextChunk
13
23
 
@@ -15,54 +25,68 @@ logger = logging.getLogger(__name__)
15
25
 
16
26
 
17
27
  class EmbeddingGenerator:
18
- """
19
- Generates embeddings using OpenAI's embedding models.
28
+ """Generates embeddings and summaries using pluggable providers.
20
29
 
21
30
  Supports batch processing with configurable batch sizes
22
- and automatic retry on rate limits.
31
+ and automatic provider selection based on configuration.
23
32
  """
24
33
 
25
34
  def __init__(
26
35
  self,
27
- api_key: Optional[str] = None,
28
- model: Optional[str] = None,
29
- batch_size: Optional[int] = None,
36
+ embedding_provider: Optional["EmbeddingProvider"] = None,
37
+ summarization_provider: Optional["SummarizationProvider"] = None,
30
38
  ):
31
- """
32
- Initialize the embedding generator.
39
+ """Initialize the embedding generator.
33
40
 
34
41
  Args:
35
- api_key: OpenAI API key. Defaults to config value.
36
- model: Embedding model name. Defaults to config value.
37
- batch_size: Number of texts to embed per API call. Defaults to 100.
38
- """
39
- self.model = model or settings.EMBEDDING_MODEL
40
- self.batch_size = batch_size or settings.EMBEDDING_BATCH_SIZE
42
+ embedding_provider: Optional embedding provider. If not provided,
43
+ creates one from configuration.
44
+ summarization_provider: Optional summarization provider. If not
45
+ provided, creates one from configuration.
46
+ """
47
+ # Load configuration
48
+ settings = load_provider_settings()
49
+
50
+ # Initialize providers from config or use provided ones
51
+ if embedding_provider is not None:
52
+ self._embedding_provider = embedding_provider
53
+ else:
54
+ self._embedding_provider = ProviderRegistry.get_embedding_provider(
55
+ settings.embedding
56
+ )
41
57
 
42
- # Initialize OpenAI async client
43
- self.client = AsyncOpenAI(
44
- api_key=api_key or settings.OPENAI_API_KEY,
45
- )
58
+ if summarization_provider is not None:
59
+ self._summarization_provider = summarization_provider
60
+ else:
61
+ self._summarization_provider = ProviderRegistry.get_summarization_provider(
62
+ settings.summarization
63
+ )
46
64
 
47
- # Initialize Anthropic client for summarization
48
- self.anthropic_client = AsyncAnthropic(
49
- api_key=settings.ANTHROPIC_API_KEY,
65
+ logger.info(
66
+ f"EmbeddingGenerator initialized with "
67
+ f"{self._embedding_provider.provider_name} embeddings "
68
+ f"({self._embedding_provider.model_name}) and "
69
+ f"{self._summarization_provider.provider_name} summarization "
70
+ f"({self._summarization_provider.model_name})"
50
71
  )
51
72
 
52
- # Initialize prompt template
53
- self.summary_prompt_template = (
54
- "You are an expert software engineer analyzing source code. "
55
- "Provide a concise 1-2 sentence summary of what this code does. "
56
- "Focus on the functionality, purpose, and behavior. "
57
- "Be specific about inputs, outputs, and side effects. "
58
- "Ignore implementation details and focus on what the code accomplishes.\n\n"
59
- "Code to summarize:\n{context_str}\n\n"
60
- "Summary:"
61
- )
73
+ @property
74
+ def model(self) -> str:
75
+ """Get the embedding model name."""
76
+ return self._embedding_provider.model_name
77
+
78
+ @property
79
+ def embedding_provider(self) -> "EmbeddingProvider":
80
+ """Get the embedding provider."""
81
+ return self._embedding_provider
82
+
83
+ @property
84
+ def summarization_provider(self) -> "SummarizationProvider":
85
+ """Get the summarization provider."""
86
+ return self._summarization_provider
62
87
 
63
88
  async def embed_text(self, text: str) -> list[float]:
64
- """
65
- Generate embedding for a single text.
89
+ """Generate embedding for a single text.
66
90
 
67
91
  Args:
68
92
  text: Text to embed.
@@ -70,19 +94,14 @@ class EmbeddingGenerator:
70
94
  Returns:
71
95
  Embedding vector as list of floats.
72
96
  """
73
- response = await self.client.embeddings.create(
74
- model=self.model,
75
- input=text,
76
- )
77
- return response.data[0].embedding
97
+ return await self._embedding_provider.embed_text(text)
78
98
 
79
99
  async def embed_texts(
80
100
  self,
81
101
  texts: list[str],
82
102
  progress_callback: Optional[Callable[[int, int], Awaitable[None]]] = None,
83
103
  ) -> list[list[float]]:
84
- """
85
- Generate embeddings for multiple texts.
104
+ """Generate embeddings for multiple texts.
86
105
 
87
106
  Args:
88
107
  texts: List of texts to embed.
@@ -91,49 +110,14 @@ class EmbeddingGenerator:
91
110
  Returns:
92
111
  List of embedding vectors.
93
112
  """
94
- if not texts:
95
- return []
96
-
97
- all_embeddings: list[list[float]] = []
98
-
99
- # Process in batches to respect API limits
100
- for i in range(0, len(texts), self.batch_size):
101
- batch = texts[i : i + self.batch_size]
102
-
103
- try:
104
- response = await self.client.embeddings.create(
105
- model=self.model,
106
- input=batch,
107
- )
108
-
109
- # Extract embeddings in order
110
- batch_embeddings = [item.embedding for item in response.data]
111
- all_embeddings.extend(batch_embeddings)
112
-
113
- if progress_callback:
114
- await progress_callback(
115
- min(i + self.batch_size, len(texts)),
116
- len(texts),
117
- )
118
-
119
- logger.debug(
120
- f"Generated embeddings for batch {i // self.batch_size + 1} "
121
- f"({len(batch)} texts)"
122
- )
123
-
124
- except Exception as e:
125
- logger.error(f"Failed to generate embeddings for batch: {e}")
126
- raise
127
-
128
- return all_embeddings
113
+ return await self._embedding_provider.embed_texts(texts, progress_callback)
129
114
 
130
115
  async def embed_chunks(
131
116
  self,
132
117
  chunks: list[TextChunk],
133
118
  progress_callback: Optional[Callable[[int, int], Awaitable[None]]] = None,
134
119
  ) -> list[list[float]]:
135
- """
136
- Generate embeddings for a list of text chunks.
120
+ """Generate embeddings for a list of text chunks.
137
121
 
138
122
  Args:
139
123
  chunks: List of TextChunk objects.
@@ -146,8 +130,7 @@ class EmbeddingGenerator:
146
130
  return await self.embed_texts(texts, progress_callback)
147
131
 
148
132
  async def embed_query(self, query: str) -> list[float]:
149
- """
150
- Generate embedding for a search query.
133
+ """Generate embedding for a search query.
151
134
 
152
135
  This is a convenience wrapper around embed_text for queries.
153
136
 
@@ -160,41 +143,15 @@ class EmbeddingGenerator:
160
143
  return await self.embed_text(query)
161
144
 
162
145
  def get_embedding_dimensions(self) -> int:
163
- """
164
- Get the expected embedding dimensions for the current model.
146
+ """Get the expected embedding dimensions for the current model.
165
147
 
166
148
  Returns:
167
149
  Number of dimensions in the embedding vector.
168
150
  """
169
- # Known dimensions for OpenAI models
170
- model_dimensions = {
171
- "text-embedding-3-large": 3072,
172
- "text-embedding-3-small": 1536,
173
- "text-embedding-ada-002": 1536,
174
- }
175
- return model_dimensions.get(self.model, settings.EMBEDDING_DIMENSIONS)
176
-
177
- def _get_summary_prompt_template(self) -> str:
178
- """
179
- Get the prompt template for code summarization.
180
-
181
- Returns:
182
- Prompt template string.
183
- """
184
- template = (
185
- "You are an expert software engineer analyzing source code. "
186
- "Provide a concise 1-2 sentence summary of what this code does. "
187
- "Focus on the functionality, purpose, and behavior. "
188
- "Be specific about inputs, outputs, and side effects. "
189
- "Ignore implementation details and focus on what the code accomplishes.\n\n"
190
- "Code to summarize:\n{context_str}\n\n"
191
- "Summary:"
192
- )
193
- return template
151
+ return self._embedding_provider.get_dimensions()
194
152
 
195
153
  async def generate_summary(self, code_text: str) -> str:
196
- """
197
- Generate a natural language summary of code using Claude.
154
+ """Generate a natural language summary of code.
198
155
 
199
156
  Args:
200
157
  code_text: The source code to summarize.
@@ -203,33 +160,23 @@ class EmbeddingGenerator:
203
160
  Natural language summary of the code's functionality.
204
161
  """
205
162
  try:
206
- # Use Claude directly with custom prompt
207
- prompt = self.summary_prompt_template.format(context_str=code_text)
208
-
209
- response = await self.anthropic_client.messages.create(
210
- model=settings.CLAUDE_MODEL,
211
- max_tokens=300,
212
- temperature=0.1, # Low temperature for consistent summaries
213
- messages=[{"role": "user", "content": prompt}],
214
- )
163
+ summary = await self._summarization_provider.summarize(code_text)
215
164
 
216
- # Extract text from Claude response
217
- summary = response.content[0].text # type: ignore
218
-
219
- if summary and len(summary) > 10: # Ensure we got a meaningful summary
165
+ if summary and len(summary) > 10:
220
166
  return summary
221
167
  else:
222
- logger.warning("Claude returned empty or too short summary")
168
+ logger.warning(
169
+ f"{self._summarization_provider.provider_name} "
170
+ "returned empty or too short summary"
171
+ )
223
172
  return self._extract_fallback_summary(code_text)
224
173
 
225
174
  except Exception as e:
226
175
  logger.error(f"Failed to generate code summary: {e}")
227
- # Fallback: try to extract from docstrings/comments
228
176
  return self._extract_fallback_summary(code_text)
229
177
 
230
178
  def _extract_fallback_summary(self, code_text: str) -> str:
231
- """
232
- Extract summary from docstrings or comments as fallback.
179
+ """Extract summary from docstrings or comments as fallback.
233
180
 
234
181
  Args:
235
182
  code_text: Source code to analyze.
@@ -237,13 +184,11 @@ class EmbeddingGenerator:
237
184
  Returns:
238
185
  Extracted summary or empty string.
239
186
  """
240
- import re
241
-
242
187
  # Try to find Python docstrings
243
188
  docstring_match = re.search(r'""".*?"""', code_text, re.DOTALL)
244
189
  if docstring_match:
245
- docstring = docstring_match.group(0)[3:-3] # Remove leading/trailing """
246
- if len(docstring) > 10: # Only use if substantial
190
+ docstring = docstring_match.group(0)[3:-3]
191
+ if len(docstring) > 10:
247
192
  return docstring[:200] + "..." if len(docstring) > 200 else docstring
248
193
 
249
194
  # Try to find function/class comments
@@ -259,7 +204,7 @@ class EmbeddingGenerator:
259
204
  if first_line.startswith(("#", "//", "/*")):
260
205
  return first_line.lstrip("#/*").strip()
261
206
 
262
- return "" # No summary available
207
+ return ""
263
208
 
264
209
 
265
210
  # Singleton instance
@@ -272,3 +217,9 @@ def get_embedding_generator() -> EmbeddingGenerator:
272
217
  if _embedding_generator is None:
273
218
  _embedding_generator = EmbeddingGenerator()
274
219
  return _embedding_generator
220
+
221
+
222
+ def reset_embedding_generator() -> None:
223
+ """Reset the global embedding generator (for testing)."""
224
+ global _embedding_generator
225
+ _embedding_generator = None