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
@@ -0,0 +1,253 @@
1
+ """Models for GraphRAG feature (Feature 113).
2
+
3
+ Defines Pydantic models for graph entities, relationships, and status.
4
+ All models are configured with frozen=True for immutability.
5
+ """
6
+
7
+ from datetime import datetime
8
+ from typing import Optional
9
+
10
+ from pydantic import BaseModel, ConfigDict, Field
11
+
12
+
13
+ class GraphTriple(BaseModel):
14
+ """Represents a subject-predicate-object triple in the knowledge graph.
15
+
16
+ Triples are the fundamental unit of knowledge representation in GraphRAG.
17
+ They capture relationships between entities extracted from documents.
18
+
19
+ Attributes:
20
+ subject: The subject entity (e.g., "FastAPI").
21
+ subject_type: Optional type classification (e.g., "Framework").
22
+ predicate: The relationship type (e.g., "uses").
23
+ object: The object entity (e.g., "Pydantic").
24
+ object_type: Optional type classification (e.g., "Library").
25
+ source_chunk_id: Optional ID of the source document chunk.
26
+ """
27
+
28
+ model_config = ConfigDict(
29
+ frozen=True,
30
+ json_schema_extra={
31
+ "examples": [
32
+ {
33
+ "subject": "FastAPI",
34
+ "subject_type": "Framework",
35
+ "predicate": "uses",
36
+ "object": "Pydantic",
37
+ "object_type": "Library",
38
+ "source_chunk_id": "chunk_abc123",
39
+ },
40
+ {
41
+ "subject": "UserController",
42
+ "subject_type": "Class",
43
+ "predicate": "calls",
44
+ "object": "authenticate_user",
45
+ "object_type": "Function",
46
+ "source_chunk_id": "chunk_def456",
47
+ },
48
+ ]
49
+ },
50
+ )
51
+
52
+ subject: str = Field(
53
+ ...,
54
+ min_length=1,
55
+ description="Subject entity in the triple",
56
+ )
57
+ subject_type: Optional[str] = Field(
58
+ default=None,
59
+ description="Type classification for subject entity",
60
+ )
61
+ predicate: str = Field(
62
+ ...,
63
+ min_length=1,
64
+ description="Relationship type connecting subject to object",
65
+ )
66
+ object: str = Field(
67
+ ...,
68
+ min_length=1,
69
+ description="Object entity in the triple",
70
+ )
71
+ object_type: Optional[str] = Field(
72
+ default=None,
73
+ description="Type classification for object entity",
74
+ )
75
+ source_chunk_id: Optional[str] = Field(
76
+ default=None,
77
+ description="ID of the source document chunk",
78
+ )
79
+
80
+
81
+ class GraphEntity(BaseModel):
82
+ """Represents an entity node in the knowledge graph.
83
+
84
+ Entities are the nodes in the graph, representing concepts,
85
+ code elements, or other named items extracted from documents.
86
+
87
+ Attributes:
88
+ name: Unique name/identifier of the entity.
89
+ entity_type: Classification type (e.g., "Class", "Function", "Concept").
90
+ description: Optional description of the entity.
91
+ source_chunk_ids: List of source chunk IDs where entity appears.
92
+ properties: Additional metadata properties.
93
+ """
94
+
95
+ model_config = ConfigDict(
96
+ frozen=True,
97
+ json_schema_extra={
98
+ "examples": [
99
+ {
100
+ "name": "VectorStoreManager",
101
+ "entity_type": "Class",
102
+ "description": "Manages Chroma vector store operations",
103
+ "source_chunk_ids": ["chunk_001", "chunk_002"],
104
+ "properties": {"module": "storage.vector_store"},
105
+ },
106
+ ]
107
+ },
108
+ )
109
+
110
+ name: str = Field(
111
+ ...,
112
+ min_length=1,
113
+ description="Unique name/identifier of the entity",
114
+ )
115
+ entity_type: Optional[str] = Field(
116
+ default=None,
117
+ description="Classification type for the entity",
118
+ )
119
+ description: Optional[str] = Field(
120
+ default=None,
121
+ description="Description of the entity",
122
+ )
123
+ source_chunk_ids: list[str] = Field(
124
+ default_factory=list,
125
+ description="List of source chunk IDs where entity appears",
126
+ )
127
+ properties: dict[str, str] = Field(
128
+ default_factory=dict,
129
+ description="Additional metadata properties",
130
+ )
131
+
132
+
133
+ class GraphIndexStatus(BaseModel):
134
+ """Status of the graph index.
135
+
136
+ Provides information about the graph index state,
137
+ including whether it's enabled, initialized, and statistics.
138
+
139
+ Attributes:
140
+ enabled: Whether graph indexing is enabled.
141
+ initialized: Whether the graph store is initialized.
142
+ entity_count: Number of entities in the graph.
143
+ relationship_count: Number of relationships in the graph.
144
+ last_updated: Timestamp of last graph update.
145
+ store_type: Type of graph store backend.
146
+ """
147
+
148
+ model_config = ConfigDict(
149
+ frozen=True,
150
+ json_schema_extra={
151
+ "examples": [
152
+ {
153
+ "enabled": True,
154
+ "initialized": True,
155
+ "entity_count": 150,
156
+ "relationship_count": 320,
157
+ "last_updated": "2024-12-15T10:30:00Z",
158
+ "store_type": "simple",
159
+ },
160
+ {
161
+ "enabled": False,
162
+ "initialized": False,
163
+ "entity_count": 0,
164
+ "relationship_count": 0,
165
+ "last_updated": None,
166
+ "store_type": "simple",
167
+ },
168
+ ]
169
+ },
170
+ )
171
+
172
+ enabled: bool = Field(
173
+ default=False,
174
+ description="Whether graph indexing is enabled",
175
+ )
176
+ initialized: bool = Field(
177
+ default=False,
178
+ description="Whether the graph store is initialized",
179
+ )
180
+ entity_count: int = Field(
181
+ default=0,
182
+ ge=0,
183
+ description="Number of entities in the graph",
184
+ )
185
+ relationship_count: int = Field(
186
+ default=0,
187
+ ge=0,
188
+ description="Number of relationships in the graph",
189
+ )
190
+ last_updated: Optional[datetime] = Field(
191
+ default=None,
192
+ description="Timestamp of last graph update",
193
+ )
194
+ store_type: str = Field(
195
+ default="simple",
196
+ description="Type of graph store backend (simple or kuzu)",
197
+ )
198
+
199
+
200
+ class GraphQueryContext(BaseModel):
201
+ """Context information from graph-based retrieval.
202
+
203
+ Contains additional context extracted from the knowledge graph
204
+ during query processing.
205
+
206
+ Attributes:
207
+ related_entities: List of related entity names.
208
+ relationship_paths: List of relationship paths as strings.
209
+ subgraph_triplets: Relevant triplets from the graph.
210
+ graph_score: Score from graph-based retrieval.
211
+ """
212
+
213
+ model_config = ConfigDict(
214
+ frozen=True,
215
+ json_schema_extra={
216
+ "examples": [
217
+ {
218
+ "related_entities": ["FastAPI", "Pydantic", "Uvicorn"],
219
+ "relationship_paths": [
220
+ "FastAPI -> uses -> Pydantic",
221
+ "FastAPI -> runs_on -> Uvicorn",
222
+ ],
223
+ "subgraph_triplets": [
224
+ {
225
+ "subject": "FastAPI",
226
+ "predicate": "uses",
227
+ "object": "Pydantic",
228
+ },
229
+ ],
230
+ "graph_score": 0.85,
231
+ },
232
+ ]
233
+ },
234
+ )
235
+
236
+ related_entities: list[str] = Field(
237
+ default_factory=list,
238
+ description="List of related entity names",
239
+ )
240
+ relationship_paths: list[str] = Field(
241
+ default_factory=list,
242
+ description="Relationship paths as formatted strings",
243
+ )
244
+ subgraph_triplets: list[GraphTriple] = Field(
245
+ default_factory=list,
246
+ description="Relevant triplets from the graph",
247
+ )
248
+ graph_score: float = Field(
249
+ default=0.0,
250
+ ge=0.0,
251
+ le=1.0,
252
+ description="Score from graph-based retrieval",
253
+ )
@@ -1,7 +1,7 @@
1
1
  """Health status models."""
2
2
 
3
3
  from datetime import datetime, timezone
4
- from typing import Literal, Optional
4
+ from typing import Any, Literal, Optional
5
5
 
6
6
  from pydantic import BaseModel, Field
7
7
 
@@ -22,7 +22,7 @@ class HealthStatus(BaseModel):
22
22
  description="Timestamp of the health check",
23
23
  )
24
24
  version: str = Field(
25
- default="1.2.0",
25
+ default="2.0.0",
26
26
  description="Server version",
27
27
  )
28
28
  mode: Optional[str] = Field(
@@ -49,7 +49,7 @@ class HealthStatus(BaseModel):
49
49
  "status": "healthy",
50
50
  "message": "Server is running and ready for queries",
51
51
  "timestamp": "2024-12-15T10:30:00Z",
52
- "version": "1.2.0",
52
+ "version": "2.0.0",
53
53
  }
54
54
  ]
55
55
  }
@@ -105,6 +105,26 @@ class IndexingStatus(BaseModel):
105
105
  default_factory=list,
106
106
  description="List of folders that have been indexed",
107
107
  )
108
+ # Graph index status (Feature 113)
109
+ graph_index: Optional[dict[str, Any]] = Field(
110
+ default=None,
111
+ description="Graph index status with entity_count, relationship_count, etc.",
112
+ )
113
+ # Queue status (Feature 115)
114
+ queue_pending: int = Field(
115
+ default=0,
116
+ ge=0,
117
+ description="Number of pending jobs in the queue",
118
+ )
119
+ queue_running: int = Field(
120
+ default=0,
121
+ ge=0,
122
+ description="Number of running jobs (0 or 1)",
123
+ )
124
+ current_job_running_time_ms: Optional[int] = Field(
125
+ None,
126
+ description="Running time of current job in milliseconds",
127
+ )
108
128
 
109
129
  model_config = {
110
130
  "json_schema_extra": {
@@ -120,6 +140,13 @@ class IndexingStatus(BaseModel):
120
140
  "last_indexed_at": "2024-12-15T10:30:00Z",
121
141
  "indexed_folders": ["/path/to/docs"],
122
142
  "supported_languages": ["python", "typescript", "java"],
143
+ "graph_index": {
144
+ "enabled": True,
145
+ "initialized": True,
146
+ "entity_count": 120,
147
+ "relationship_count": 250,
148
+ "store_type": "simple",
149
+ },
123
150
  }
124
151
  ]
125
152
  }
@@ -0,0 +1,289 @@
1
+ """Job queue models for indexing job management."""
2
+
3
+ import hashlib
4
+ from datetime import datetime, timezone
5
+ from enum import Enum
6
+ from pathlib import Path
7
+ from typing import Optional
8
+
9
+ from pydantic import BaseModel, Field, computed_field
10
+
11
+
12
+ class JobStatus(str, Enum):
13
+ """Status of an indexing job."""
14
+
15
+ PENDING = "pending"
16
+ RUNNING = "running"
17
+ DONE = "done"
18
+ FAILED = "failed"
19
+ CANCELLED = "cancelled"
20
+
21
+
22
+ class JobProgress(BaseModel):
23
+ """Progress tracking for an indexing job."""
24
+
25
+ files_processed: int = Field(default=0, ge=0, description="Files processed so far")
26
+ files_total: int = Field(default=0, ge=0, description="Total files to process")
27
+ chunks_created: int = Field(default=0, ge=0, description="Chunks created so far")
28
+ current_file: str = Field(default="", description="Currently processing file")
29
+ updated_at: datetime = Field(
30
+ default_factory=lambda: datetime.now(timezone.utc),
31
+ description="Last progress update timestamp",
32
+ )
33
+
34
+ @computed_field # type: ignore[prop-decorator]
35
+ @property
36
+ def percent_complete(self) -> float:
37
+ """Calculate completion percentage."""
38
+ if self.files_total == 0:
39
+ return 0.0
40
+ return round((self.files_processed / self.files_total) * 100, 1)
41
+
42
+
43
+ class JobRecord(BaseModel):
44
+ """Persistent job record for the queue."""
45
+
46
+ id: str = Field(..., description="Unique job identifier (job_<uuid12>)")
47
+ dedupe_key: str = Field(..., description="SHA256 hash for deduplication")
48
+
49
+ # Request parameters (normalized)
50
+ folder_path: str = Field(..., description="Resolved, normalized folder path")
51
+ include_code: bool = Field(default=False, description="Whether to index code files")
52
+ operation: str = Field(
53
+ default="index", description="Operation type: 'index' or 'add'"
54
+ )
55
+
56
+ # Optional request parameters
57
+ chunk_size: int = Field(default=512, description="Chunk size in tokens")
58
+ chunk_overlap: int = Field(default=50, description="Chunk overlap in tokens")
59
+ recursive: bool = Field(default=True, description="Recursive folder scan")
60
+ generate_summaries: bool = Field(
61
+ default=False, description="Generate LLM summaries"
62
+ )
63
+ supported_languages: Optional[list[str]] = Field(
64
+ default=None, description="Languages to index"
65
+ )
66
+ include_patterns: Optional[list[str]] = Field(
67
+ default=None, description="File patterns to include"
68
+ )
69
+ exclude_patterns: Optional[list[str]] = Field(
70
+ default=None, description="File patterns to exclude"
71
+ )
72
+
73
+ # Job state
74
+ status: JobStatus = Field(
75
+ default=JobStatus.PENDING, description="Current job status"
76
+ )
77
+ cancel_requested: bool = Field(
78
+ default=False, description="Flag for graceful cancellation"
79
+ )
80
+
81
+ # Timestamps
82
+ enqueued_at: datetime = Field(
83
+ default_factory=lambda: datetime.now(timezone.utc),
84
+ description="When the job was enqueued",
85
+ )
86
+ started_at: Optional[datetime] = Field(
87
+ default=None, description="When the job started running"
88
+ )
89
+ finished_at: Optional[datetime] = Field(
90
+ default=None, description="When the job finished (done, failed, or cancelled)"
91
+ )
92
+
93
+ # Results and metadata
94
+ error: Optional[str] = Field(default=None, description="Error message if failed")
95
+ retry_count: int = Field(default=0, ge=0, description="Number of retry attempts")
96
+ progress: Optional[JobProgress] = Field(
97
+ default=None, description="Progress tracking"
98
+ )
99
+ total_chunks: int = Field(default=0, ge=0, description="Total chunks indexed")
100
+ total_documents: int = Field(default=0, ge=0, description="Total documents indexed")
101
+
102
+ @computed_field # type: ignore[prop-decorator]
103
+ @property
104
+ def execution_time_ms(self) -> Optional[int]:
105
+ """Calculate execution time in milliseconds."""
106
+ if self.started_at is None:
107
+ return None
108
+ end_time = self.finished_at or datetime.now(timezone.utc)
109
+ delta = end_time - self.started_at
110
+ return int(delta.total_seconds() * 1000)
111
+
112
+ @staticmethod
113
+ def compute_dedupe_key(
114
+ folder_path: str,
115
+ include_code: bool,
116
+ operation: str,
117
+ include_patterns: Optional[list[str]] = None,
118
+ exclude_patterns: Optional[list[str]] = None,
119
+ ) -> str:
120
+ """Compute deduplication key from job parameters.
121
+
122
+ Args:
123
+ folder_path: Normalized, resolved folder path.
124
+ include_code: Whether to include code files.
125
+ operation: Operation type (index or add).
126
+ include_patterns: Optional include patterns.
127
+ exclude_patterns: Optional exclude patterns.
128
+
129
+ Returns:
130
+ SHA256 hash of normalized parameters.
131
+ """
132
+ # Normalize path (resolve and lowercase on case-insensitive systems)
133
+ resolved = str(Path(folder_path).resolve())
134
+
135
+ # Build dedupe string
136
+ parts = [
137
+ resolved,
138
+ str(include_code),
139
+ operation,
140
+ ",".join(sorted(include_patterns or [])),
141
+ ",".join(sorted(exclude_patterns or [])),
142
+ ]
143
+ dedupe_string = "|".join(parts)
144
+
145
+ return hashlib.sha256(dedupe_string.encode()).hexdigest()
146
+
147
+
148
+ class JobEnqueueResponse(BaseModel):
149
+ """Response when enqueueing a job."""
150
+
151
+ job_id: str = Field(..., description="Unique job identifier")
152
+ status: str = Field(default="pending", description="Job status")
153
+ queue_position: int = Field(
154
+ default=0, ge=0, description="Position in the queue (0 = first)"
155
+ )
156
+ queue_length: int = Field(default=0, ge=0, description="Total jobs in queue")
157
+ message: str = Field(..., description="Human-readable status message")
158
+ dedupe_hit: bool = Field(
159
+ default=False, description="True if this was a duplicate request"
160
+ )
161
+
162
+ model_config = {
163
+ "json_schema_extra": {
164
+ "examples": [
165
+ {
166
+ "job_id": "job_abc123def456",
167
+ "status": "pending",
168
+ "queue_position": 2,
169
+ "queue_length": 5,
170
+ "message": "Job queued for /path/to/docs",
171
+ "dedupe_hit": False,
172
+ }
173
+ ]
174
+ }
175
+ }
176
+
177
+
178
+ class JobListResponse(BaseModel):
179
+ """Response for listing jobs."""
180
+
181
+ jobs: list["JobSummary"] = Field(default_factory=list, description="List of jobs")
182
+ total: int = Field(default=0, ge=0, description="Total number of jobs")
183
+ pending: int = Field(default=0, ge=0, description="Number of pending jobs")
184
+ running: int = Field(default=0, ge=0, description="Number of running jobs")
185
+ completed: int = Field(default=0, ge=0, description="Number of completed jobs")
186
+ failed: int = Field(default=0, ge=0, description="Number of failed jobs")
187
+
188
+
189
+ class JobSummary(BaseModel):
190
+ """Summary view of a job for list responses."""
191
+
192
+ id: str = Field(..., description="Job identifier")
193
+ status: JobStatus = Field(..., description="Current status")
194
+ folder_path: str = Field(..., description="Folder being indexed")
195
+ operation: str = Field(..., description="Operation type")
196
+ include_code: bool = Field(..., description="Whether indexing code")
197
+ enqueued_at: datetime = Field(..., description="When queued")
198
+ started_at: Optional[datetime] = Field(default=None, description="When started")
199
+ finished_at: Optional[datetime] = Field(default=None, description="When finished")
200
+ progress_percent: float = Field(default=0.0, description="Completion percentage")
201
+ error: Optional[str] = Field(default=None, description="Error message if failed")
202
+
203
+ @classmethod
204
+ def from_record(cls, record: JobRecord) -> "JobSummary":
205
+ """Create a summary from a full job record."""
206
+ return cls(
207
+ id=record.id,
208
+ status=record.status,
209
+ folder_path=record.folder_path,
210
+ operation=record.operation,
211
+ include_code=record.include_code,
212
+ enqueued_at=record.enqueued_at,
213
+ started_at=record.started_at,
214
+ finished_at=record.finished_at,
215
+ progress_percent=(
216
+ record.progress.percent_complete if record.progress else 0.0
217
+ ),
218
+ error=record.error,
219
+ )
220
+
221
+
222
+ class JobDetailResponse(BaseModel):
223
+ """Detailed response for a single job."""
224
+
225
+ id: str = Field(..., description="Job identifier")
226
+ status: JobStatus = Field(..., description="Current status")
227
+ folder_path: str = Field(..., description="Folder being indexed")
228
+ operation: str = Field(..., description="Operation type")
229
+ include_code: bool = Field(..., description="Whether indexing code")
230
+
231
+ # Timestamps
232
+ enqueued_at: datetime = Field(..., description="When queued")
233
+ started_at: Optional[datetime] = Field(default=None, description="When started")
234
+ finished_at: Optional[datetime] = Field(default=None, description="When finished")
235
+ execution_time_ms: Optional[int] = Field(
236
+ default=None, description="Execution time in ms"
237
+ )
238
+
239
+ # Progress
240
+ progress: Optional[JobProgress] = Field(
241
+ default=None, description="Progress details"
242
+ )
243
+
244
+ # Results
245
+ total_documents: int = Field(default=0, description="Documents indexed")
246
+ total_chunks: int = Field(default=0, description="Chunks created")
247
+ error: Optional[str] = Field(default=None, description="Error message if failed")
248
+ retry_count: int = Field(default=0, description="Retry attempts")
249
+ cancel_requested: bool = Field(
250
+ default=False, description="Whether cancellation requested"
251
+ )
252
+
253
+ @classmethod
254
+ def from_record(cls, record: JobRecord) -> "JobDetailResponse":
255
+ """Create a detail response from a full job record."""
256
+ return cls(
257
+ id=record.id,
258
+ status=record.status,
259
+ folder_path=record.folder_path,
260
+ operation=record.operation,
261
+ include_code=record.include_code,
262
+ enqueued_at=record.enqueued_at,
263
+ started_at=record.started_at,
264
+ finished_at=record.finished_at,
265
+ execution_time_ms=record.execution_time_ms,
266
+ progress=record.progress,
267
+ total_documents=record.total_documents,
268
+ total_chunks=record.total_chunks,
269
+ error=record.error,
270
+ retry_count=record.retry_count,
271
+ cancel_requested=record.cancel_requested,
272
+ )
273
+
274
+
275
+ class QueueStats(BaseModel):
276
+ """Statistics about the job queue."""
277
+
278
+ pending: int = Field(default=0, ge=0, description="Pending jobs count")
279
+ running: int = Field(default=0, ge=0, description="Running jobs count")
280
+ completed: int = Field(default=0, ge=0, description="Completed jobs count")
281
+ failed: int = Field(default=0, ge=0, description="Failed jobs count")
282
+ cancelled: int = Field(default=0, ge=0, description="Cancelled jobs count")
283
+ total: int = Field(default=0, ge=0, description="Total jobs count")
284
+ current_job_id: Optional[str] = Field(
285
+ default=None, description="Currently running job ID"
286
+ )
287
+ current_job_running_time_ms: Optional[int] = Field(
288
+ default=None, description="Current job running time in ms"
289
+ )
@@ -14,6 +14,8 @@ class QueryMode(str, Enum):
14
14
  VECTOR = "vector"
15
15
  BM25 = "bm25"
16
16
  HYBRID = "hybrid"
17
+ GRAPH = "graph" # Graph-only retrieval (Feature 113)
18
+ MULTI = "multi" # Multi-retrieval: vector + BM25 + graph with RRF (Feature 113)
17
19
 
18
20
 
19
21
  class QueryRequest(BaseModel):
@@ -32,14 +34,14 @@ class QueryRequest(BaseModel):
32
34
  description="Number of results to return",
33
35
  )
34
36
  similarity_threshold: float = Field(
35
- default=0.7,
37
+ default=0.3,
36
38
  ge=0.0,
37
39
  le=1.0,
38
40
  description="Minimum similarity score (0-1)",
39
41
  )
40
42
  mode: QueryMode = Field(
41
43
  default=QueryMode.HYBRID,
42
- description="Retrieval mode (vector, bm25, hybrid)",
44
+ description="Retrieval mode (vector, bm25, hybrid, graph, multi)",
43
45
  )
44
46
  alpha: float = Field(
45
47
  default=0.5,
@@ -90,7 +92,7 @@ class QueryRequest(BaseModel):
90
92
  {
91
93
  "query": "How do I configure authentication?",
92
94
  "top_k": 5,
93
- "similarity_threshold": 0.7,
95
+ "similarity_threshold": 0.3,
94
96
  "mode": "hybrid",
95
97
  "alpha": 0.5,
96
98
  },
@@ -131,6 +133,17 @@ class QueryResult(BaseModel):
131
133
  default=None, description="Programming language for code files"
132
134
  )
133
135
 
136
+ # GraphRAG fields (Feature 113)
137
+ graph_score: float | None = Field(
138
+ default=None, description="Score from graph-based retrieval"
139
+ )
140
+ related_entities: list[str] | None = Field(
141
+ default=None, description="Related entities from knowledge graph"
142
+ )
143
+ relationship_path: list[str] | None = Field(
144
+ default=None, description="Relationship paths in the graph"
145
+ )
146
+
134
147
  # Additional metadata
135
148
  metadata: dict[str, Any] = Field(
136
149
  default_factory=dict, description="Additional metadata"
@@ -1,4 +1,4 @@
1
- """Project root resolution for per-project doc-serve instances."""
1
+ """Project root resolution for per-project Agent Brain instances."""
2
2
 
3
3
  import logging
4
4
  import subprocess