kodit 0.5.0__py3-none-any.whl → 0.5.2__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.

Potentially problematic release.


This version of kodit might be problematic. Click here for more details.

Files changed (70) hide show
  1. kodit/_version.py +2 -2
  2. kodit/app.py +10 -12
  3. kodit/application/factories/server_factory.py +78 -11
  4. kodit/application/services/commit_indexing_application_service.py +188 -31
  5. kodit/application/services/enrichment_query_service.py +95 -0
  6. kodit/config.py +3 -3
  7. kodit/domain/enrichments/__init__.py +1 -0
  8. kodit/domain/enrichments/architecture/__init__.py +1 -0
  9. kodit/domain/enrichments/architecture/architecture.py +20 -0
  10. kodit/domain/enrichments/architecture/physical/__init__.py +1 -0
  11. kodit/domain/enrichments/architecture/physical/discovery_notes.py +14 -0
  12. kodit/domain/enrichments/architecture/physical/formatter.py +11 -0
  13. kodit/domain/enrichments/architecture/physical/physical.py +17 -0
  14. kodit/domain/enrichments/development/__init__.py +1 -0
  15. kodit/domain/enrichments/development/development.py +18 -0
  16. kodit/domain/enrichments/development/snippet/__init__.py +1 -0
  17. kodit/domain/enrichments/development/snippet/snippet.py +21 -0
  18. kodit/domain/enrichments/enricher.py +17 -0
  19. kodit/domain/enrichments/enrichment.py +39 -0
  20. kodit/domain/enrichments/request.py +12 -0
  21. kodit/domain/enrichments/response.py +11 -0
  22. kodit/domain/enrichments/usage/__init__.py +1 -0
  23. kodit/domain/enrichments/usage/api_docs.py +19 -0
  24. kodit/domain/enrichments/usage/usage.py +18 -0
  25. kodit/domain/protocols.py +7 -6
  26. kodit/domain/services/enrichment_service.py +9 -30
  27. kodit/domain/services/physical_architecture_service.py +182 -0
  28. kodit/domain/tracking/__init__.py +1 -0
  29. kodit/domain/tracking/resolution_service.py +81 -0
  30. kodit/domain/tracking/trackable.py +21 -0
  31. kodit/domain/value_objects.py +6 -23
  32. kodit/infrastructure/api/v1/dependencies.py +15 -0
  33. kodit/infrastructure/api/v1/routers/commits.py +81 -0
  34. kodit/infrastructure/api/v1/routers/repositories.py +99 -0
  35. kodit/infrastructure/api/v1/schemas/enrichment.py +29 -0
  36. kodit/infrastructure/cloning/git/git_python_adaptor.py +71 -4
  37. kodit/infrastructure/enricher/__init__.py +1 -0
  38. kodit/infrastructure/enricher/enricher_factory.py +53 -0
  39. kodit/infrastructure/{enrichment/litellm_enrichment_provider.py → enricher/litellm_enricher.py} +20 -33
  40. kodit/infrastructure/{enrichment/local_enrichment_provider.py → enricher/local_enricher.py} +19 -24
  41. kodit/infrastructure/enricher/null_enricher.py +36 -0
  42. kodit/infrastructure/mappers/enrichment_mapper.py +83 -0
  43. kodit/infrastructure/mappers/snippet_mapper.py +20 -22
  44. kodit/infrastructure/physical_architecture/__init__.py +1 -0
  45. kodit/infrastructure/physical_architecture/detectors/__init__.py +1 -0
  46. kodit/infrastructure/physical_architecture/detectors/docker_compose_detector.py +336 -0
  47. kodit/infrastructure/physical_architecture/formatters/__init__.py +1 -0
  48. kodit/infrastructure/physical_architecture/formatters/narrative_formatter.py +149 -0
  49. kodit/infrastructure/slicing/api_doc_extractor.py +836 -0
  50. kodit/infrastructure/slicing/ast_analyzer.py +1128 -0
  51. kodit/infrastructure/slicing/slicer.py +56 -391
  52. kodit/infrastructure/sqlalchemy/enrichment_v2_repository.py +118 -0
  53. kodit/infrastructure/sqlalchemy/entities.py +46 -38
  54. kodit/infrastructure/sqlalchemy/git_branch_repository.py +22 -11
  55. kodit/infrastructure/sqlalchemy/git_commit_repository.py +23 -14
  56. kodit/infrastructure/sqlalchemy/git_repository.py +27 -17
  57. kodit/infrastructure/sqlalchemy/git_tag_repository.py +22 -11
  58. kodit/infrastructure/sqlalchemy/snippet_v2_repository.py +101 -106
  59. kodit/migrations/versions/19f8c7faf8b9_add_generic_enrichment_type.py +260 -0
  60. kodit/utils/dump_config.py +361 -0
  61. kodit/utils/dump_openapi.py +5 -6
  62. {kodit-0.5.0.dist-info → kodit-0.5.2.dist-info}/METADATA +1 -1
  63. {kodit-0.5.0.dist-info → kodit-0.5.2.dist-info}/RECORD +67 -32
  64. kodit/infrastructure/enrichment/__init__.py +0 -1
  65. kodit/infrastructure/enrichment/enrichment_factory.py +0 -52
  66. kodit/infrastructure/enrichment/null_enrichment_provider.py +0 -19
  67. /kodit/infrastructure/{enrichment → enricher}/utils.py +0 -0
  68. {kodit-0.5.0.dist-info → kodit-0.5.2.dist-info}/WHEEL +0 -0
  69. {kodit-0.5.0.dist-info → kodit-0.5.2.dist-info}/entry_points.txt +0 -0
  70. {kodit-0.5.0.dist-info → kodit-0.5.2.dist-info}/licenses/LICENSE +0 -0
@@ -13,6 +13,9 @@ from kodit.application.services.code_search_application_service import (
13
13
  from kodit.application.services.commit_indexing_application_service import (
14
14
  CommitIndexingApplicationService,
15
15
  )
16
+ from kodit.application.services.enrichment_query_service import (
17
+ EnrichmentQueryService,
18
+ )
16
19
  from kodit.application.services.queue_service import QueueService
17
20
  from kodit.config import AppContext
18
21
  from kodit.domain.protocols import (
@@ -155,3 +158,15 @@ async def get_code_search_app_service(
155
158
  CodeSearchAppServiceDep = Annotated[
156
159
  CodeSearchApplicationService, Depends(get_code_search_app_service)
157
160
  ]
161
+
162
+
163
+ async def get_enrichment_query_service(
164
+ server_factory: ServerFactoryDep,
165
+ ) -> EnrichmentQueryService:
166
+ """Get enrichment query service dependency."""
167
+ return server_factory.enrichment_query_service()
168
+
169
+
170
+ EnrichmentQueryServiceDep = Annotated[
171
+ EnrichmentQueryService, Depends(get_enrichment_query_service)
172
+ ]
@@ -22,6 +22,11 @@ from kodit.infrastructure.api.v1.schemas.commit import (
22
22
  FileListResponse,
23
23
  FileResponse,
24
24
  )
25
+ from kodit.infrastructure.api.v1.schemas.enrichment import (
26
+ EnrichmentAttributes,
27
+ EnrichmentData,
28
+ EnrichmentListResponse,
29
+ )
25
30
  from kodit.infrastructure.api.v1.schemas.snippet import (
26
31
  EnrichmentSchema,
27
32
  GitFileSchema,
@@ -269,3 +274,79 @@ async def list_commit_embeddings(
269
274
  for embedding in embeddings
270
275
  ]
271
276
  )
277
+
278
+
279
+ @router.get(
280
+ "/{repo_id}/commits/{commit_sha}/enrichments",
281
+ summary="List commit enrichments",
282
+ responses={404: {"description": "Repository or commit not found"}},
283
+ )
284
+ async def list_commit_enrichments(
285
+ repo_id: str, # noqa: ARG001
286
+ commit_sha: str,
287
+ server_factory: ServerFactoryDep,
288
+ ) -> EnrichmentListResponse:
289
+ """List all enrichments for a specific commit."""
290
+ # TODO(Phil): Should use repo too, it's confusing to the user when they specify the
291
+ # wrong commit and another repo. It's like they are seeing results from the other
292
+ # repo.
293
+ enrichment_v2_repository = server_factory.enrichment_v2_repository()
294
+ enrichments = await enrichment_v2_repository.enrichments_for_entity_type(
295
+ entity_type="git_commit",
296
+ entity_ids=[commit_sha],
297
+ )
298
+
299
+ return EnrichmentListResponse(
300
+ data=[
301
+ EnrichmentData(
302
+ type="enrichment",
303
+ id=str(enrichment.id),
304
+ attributes=EnrichmentAttributes(
305
+ type=enrichment.type,
306
+ subtype=enrichment.subtype,
307
+ content=enrichment.content,
308
+ created_at=enrichment.created_at,
309
+ updated_at=enrichment.updated_at,
310
+ ),
311
+ )
312
+ for enrichment in enrichments
313
+ ]
314
+ )
315
+
316
+
317
+ @router.delete(
318
+ "/{repo_id}/commits/{commit_sha}/enrichments",
319
+ summary="Delete all commit enrichments",
320
+ responses={404: {"description": "Commit not found"}},
321
+ status_code=204,
322
+ )
323
+ async def delete_all_commit_enrichments(
324
+ repo_id: str, # noqa: ARG001
325
+ commit_sha: str,
326
+ server_factory: ServerFactoryDep,
327
+ ) -> None:
328
+ """Delete all enrichments for a specific commit."""
329
+ enrichment_v2_repository = server_factory.enrichment_v2_repository()
330
+ await enrichment_v2_repository.bulk_delete_enrichments(
331
+ entity_type="git_commit",
332
+ entity_ids=[commit_sha],
333
+ )
334
+
335
+
336
+ @router.delete(
337
+ "/{repo_id}/commits/{commit_sha}/enrichments/{enrichment_id}",
338
+ summary="Delete commit enrichment",
339
+ responses={404: {"description": "Enrichment not found"}},
340
+ status_code=204,
341
+ )
342
+ async def delete_commit_enrichment(
343
+ repo_id: str, # noqa: ARG001
344
+ commit_sha: str, # noqa: ARG001
345
+ enrichment_id: int,
346
+ server_factory: ServerFactoryDep,
347
+ ) -> None:
348
+ """Delete a specific enrichment for a commit."""
349
+ enrichment_v2_repository = server_factory.enrichment_v2_repository()
350
+ deleted = await enrichment_v2_repository.delete_enrichment(enrichment_id)
351
+ if not deleted:
352
+ raise HTTPException(status_code=404, detail="Enrichment not found")
@@ -2,15 +2,22 @@
2
2
 
3
3
  from fastapi import APIRouter, Depends, HTTPException
4
4
 
5
+ from kodit.domain.tracking.trackable import Trackable, TrackableReferenceType
5
6
  from kodit.infrastructure.api.middleware.auth import api_key_auth
6
7
  from kodit.infrastructure.api.v1.dependencies import (
7
8
  CommitIndexingAppServiceDep,
9
+ EnrichmentQueryServiceDep,
8
10
  GitBranchRepositoryDep,
9
11
  GitCommitRepositoryDep,
10
12
  GitRepositoryDep,
11
13
  GitTagRepositoryDep,
12
14
  TaskStatusQueryServiceDep,
13
15
  )
16
+ from kodit.infrastructure.api.v1.schemas.enrichment import (
17
+ EnrichmentAttributes,
18
+ EnrichmentData,
19
+ EnrichmentListResponse,
20
+ )
14
21
  from kodit.infrastructure.api.v1.schemas.repository import (
15
22
  RepositoryBranchData,
16
23
  RepositoryCommitData,
@@ -259,6 +266,98 @@ async def get_repository_tag(
259
266
  )
260
267
 
261
268
 
269
+ @router.get(
270
+ "/{repo_id}/enrichments",
271
+ summary="List latest repository enrichments",
272
+ responses={404: {"description": "Repository not found"}},
273
+ )
274
+ async def list_repository_enrichments( # noqa: PLR0913
275
+ repo_id: str,
276
+ git_repository: GitRepositoryDep,
277
+ enrichment_query_service: EnrichmentQueryServiceDep,
278
+ ref_type: str = "branch",
279
+ ref_name: str | None = None,
280
+ enrichment_type: str | None = None,
281
+ limit: int = 10,
282
+ ) -> EnrichmentListResponse:
283
+ """List the most recent enrichments for a repository.
284
+
285
+ Query parameters:
286
+ - ref_type: Type of reference (branch, tag, or commit_sha). Defaults to "branch".
287
+ - ref_name: Name of the reference. For branches, defaults to the tracking branch.
288
+ - enrichment_type: Optional filter for specific enrichment type.
289
+ - limit: Maximum number of enrichments to return. Defaults to 10.
290
+ """
291
+ # Get repository
292
+ repo = await git_repository.get_by_id(int(repo_id))
293
+ if not repo:
294
+ raise HTTPException(status_code=404, detail="Repository not found")
295
+
296
+ # Determine the reference to track
297
+ if ref_name is None:
298
+ if ref_type == "branch":
299
+ # Default to tracking branch
300
+ if not repo.tracking_branch:
301
+ raise HTTPException(
302
+ status_code=400, detail="No tracking branch configured"
303
+ )
304
+ ref_name = repo.tracking_branch.name
305
+ else:
306
+ raise HTTPException(
307
+ status_code=400,
308
+ detail="ref_name is required for tag and commit_sha references",
309
+ )
310
+
311
+ # Parse ref_type
312
+ try:
313
+ trackable_type = TrackableReferenceType(ref_type)
314
+ except ValueError:
315
+ raise HTTPException(
316
+ status_code=400,
317
+ detail=f"Invalid ref_type: {ref_type}. Must be branch, tag, or commit_sha",
318
+ ) from None
319
+
320
+ # Create trackable
321
+ trackable = Trackable(
322
+ type=trackable_type, identifier=ref_name, repo_id=int(repo_id)
323
+ )
324
+
325
+ # Find the latest enriched commit
326
+ enriched_commit = await enrichment_query_service.find_latest_enriched_commit(
327
+ trackable=trackable,
328
+ enrichment_type=enrichment_type,
329
+ max_commits_to_check=limit * 10, # Check more commits to find enriched ones
330
+ )
331
+
332
+ # If no enriched commit found, return empty list
333
+ if not enriched_commit:
334
+ return EnrichmentListResponse(data=[])
335
+
336
+ # Get enrichments for the commit
337
+ enrichments = await enrichment_query_service.get_enrichments_for_commit(
338
+ commit_sha=enriched_commit,
339
+ enrichment_type=enrichment_type,
340
+ )
341
+
342
+ # Map enrichments to API response format
343
+ enrichment_data = [
344
+ EnrichmentData(
345
+ type="enrichment",
346
+ id=str(enrichment.id) if enrichment.id else "0",
347
+ attributes=EnrichmentAttributes(
348
+ type=enrichment.type,
349
+ subtype=enrichment.subtype,
350
+ content=enrichment.content,
351
+ created_at=enrichment.created_at,
352
+ updated_at=enrichment.updated_at,
353
+ ),
354
+ )
355
+ for enrichment in enrichments
356
+ ]
357
+
358
+ return EnrichmentListResponse(data=enrichment_data)
359
+
360
+
262
361
  @router.delete(
263
362
  "/{repo_id}",
264
363
  status_code=204,
@@ -0,0 +1,29 @@
1
+ """Enrichment JSON-API schemas."""
2
+
3
+ from datetime import datetime
4
+
5
+ from pydantic import BaseModel
6
+
7
+
8
+ class EnrichmentAttributes(BaseModel):
9
+ """Enrichment attributes following JSON-API spec."""
10
+
11
+ type: str
12
+ subtype: str | None
13
+ content: str
14
+ created_at: datetime | None
15
+ updated_at: datetime | None
16
+
17
+
18
+ class EnrichmentData(BaseModel):
19
+ """Enrichment data following JSON-API spec."""
20
+
21
+ type: str = "enrichment"
22
+ id: str
23
+ attributes: EnrichmentAttributes
24
+
25
+
26
+ class EnrichmentListResponse(BaseModel):
27
+ """Enrichment list response following JSON-API spec."""
28
+
29
+ data: list[EnrichmentData]
@@ -101,8 +101,11 @@ class GitPythonAdapter(GitAdapter):
101
101
 
102
102
  await asyncio.get_event_loop().run_in_executor(self.executor, _clone)
103
103
 
104
- async def checkout_commit(self, local_path: Path, commit_sha: str) -> None:
105
- """Checkout a specific commit in the repository."""
104
+ async def _checkout_commit(self, local_path: Path, commit_sha: str) -> None:
105
+ """Checkout a specific commit internally.
106
+
107
+ Private method - external callers should not mutate repository state directly.
108
+ """
106
109
 
107
110
  def _checkout() -> None:
108
111
  try:
@@ -116,6 +119,52 @@ class GitPythonAdapter(GitAdapter):
116
119
 
117
120
  await asyncio.get_event_loop().run_in_executor(self.executor, _checkout)
118
121
 
122
+ async def restore_to_branch(
123
+ self, local_path: Path, branch_name: str = "main"
124
+ ) -> None:
125
+ """Restore repository to a specific branch, recovering from detached HEAD.
126
+
127
+ Args:
128
+ local_path: Path to the repository
129
+ branch_name: Branch to restore to (default: "main")
130
+
131
+ """
132
+
133
+ def _restore() -> None:
134
+ try:
135
+ repo = Repo(local_path)
136
+
137
+ # Try to checkout the requested branch
138
+ try:
139
+ repo.git.checkout(branch_name)
140
+ except Exception: # noqa: BLE001
141
+ # If requested branch doesn't exist, try common default branches
142
+ for fallback in ["master", "develop"]:
143
+ try:
144
+ repo.git.checkout(fallback)
145
+ except Exception: # noqa: BLE001
146
+ # Branch doesn't exist, try next fallback
147
+ self._log.debug(f"Branch {fallback} not found, trying next")
148
+ else:
149
+ self._log.debug(
150
+ f"Branch {branch_name} not found, "
151
+ f"restored to {fallback} instead"
152
+ )
153
+ return
154
+
155
+ # If all branches fail, stay in detached state
156
+ self._log.warning(
157
+ f"Could not restore to any branch in {local_path}, "
158
+ f"repository remains in detached HEAD state"
159
+ )
160
+ else:
161
+ self._log.debug(f"Restored repository to branch {branch_name}")
162
+ except Exception as e:
163
+ self._log.error(f"Failed to restore branch in {local_path}: {e}")
164
+ raise
165
+
166
+ await asyncio.get_event_loop().run_in_executor(self.executor, _restore)
167
+
119
168
  async def pull_repository(self, local_path: Path) -> None:
120
169
  """Pull latest changes for existing repository."""
121
170
 
@@ -139,12 +188,20 @@ class GitPythonAdapter(GitAdapter):
139
188
  repo = Repo(local_path)
140
189
 
141
190
  # Get local branches
191
+ # Check if HEAD is detached
192
+ try:
193
+ active_branch = repo.active_branch
194
+ except TypeError:
195
+ # HEAD is detached, no active branch
196
+ active_branch = None
197
+
142
198
  branches = [
143
199
  {
144
200
  "name": branch.name,
145
201
  "type": "local",
146
202
  "head_commit_sha": branch.commit.hexsha,
147
- "is_active": branch == repo.active_branch,
203
+ "is_active": active_branch is not None
204
+ and branch == active_branch,
148
205
  }
149
206
  for branch in repo.branches
150
207
  ]
@@ -291,7 +348,7 @@ class GitPythonAdapter(GitAdapter):
291
348
  async def get_commit_files(
292
349
  self, local_path: Path, commit_sha: str
293
350
  ) -> list[dict[str, Any]]:
294
- """Get all files in a specific commit."""
351
+ """Get all files in a specific commit from the git tree."""
295
352
 
296
353
  def _get_files() -> list[dict[str, Any]]:
297
354
  try:
@@ -332,6 +389,16 @@ class GitPythonAdapter(GitAdapter):
332
389
 
333
390
  return await asyncio.get_event_loop().run_in_executor(self.executor, _get_files)
334
391
 
392
+ async def get_commit_file_data(
393
+ self, local_path: Path, commit_sha: str
394
+ ) -> list[dict[str, Any]]:
395
+ """Get file metadata for a commit, with files checked out to disk."""
396
+ await self._checkout_commit(local_path, commit_sha)
397
+ try:
398
+ return await self.get_commit_files(local_path, commit_sha)
399
+ finally:
400
+ await self.restore_to_branch(local_path, "main")
401
+
335
402
  async def repository_exists(self, local_path: Path) -> bool:
336
403
  """Check if repository exists at local path."""
337
404
 
@@ -0,0 +1 @@
1
+ """Generic enricher infrastructure implementations."""
@@ -0,0 +1,53 @@
1
+ """Enricher factory for creating generic enricher domain services."""
2
+
3
+ from kodit.config import AppContext, Endpoint
4
+ from kodit.domain.enrichments.enricher import Enricher
5
+ from kodit.infrastructure.enricher.litellm_enricher import LiteLLMEnricher
6
+ from kodit.infrastructure.enricher.local_enricher import LocalEnricher
7
+ from kodit.infrastructure.enricher.null_enricher import NullEnricher
8
+ from kodit.log import log_event
9
+
10
+
11
+ def _get_endpoint_configuration(app_context: AppContext) -> Endpoint | None:
12
+ """Get the endpoint configuration for the enricher service.
13
+
14
+ Args:
15
+ app_context: The application context.
16
+
17
+ Returns:
18
+ The endpoint configuration or None.
19
+
20
+ """
21
+ return app_context.enrichment_endpoint or None
22
+
23
+
24
+ def enricher_domain_service_factory(
25
+ app_context: AppContext,
26
+ *,
27
+ use_null_enricher: bool = False,
28
+ ) -> Enricher:
29
+ """Create an enricher domain service.
30
+
31
+ Args:
32
+ app_context: The application context.
33
+ use_null_enricher: Whether to use the null enricher instead.
34
+
35
+ Returns:
36
+ An enricher domain service instance.
37
+
38
+ """
39
+ enricher: Enricher
40
+
41
+ if use_null_enricher:
42
+ log_event("kodit.enricher", {"provider": "null"})
43
+ enricher = NullEnricher()
44
+ else:
45
+ endpoint = _get_endpoint_configuration(app_context)
46
+ if endpoint:
47
+ log_event("kodit.enricher", {"provider": "litellm"})
48
+ enricher = LiteLLMEnricher(endpoint=endpoint)
49
+ else:
50
+ log_event("kodit.enricher", {"provider": "local"})
51
+ enricher = LocalEnricher()
52
+
53
+ return enricher
@@ -1,4 +1,4 @@
1
- """LiteLLM enrichment provider implementation."""
1
+ """LiteLLM enricher implementation."""
2
2
 
3
3
  import asyncio
4
4
  from collections.abc import AsyncGenerator
@@ -10,27 +10,22 @@ import structlog
10
10
  from litellm import acompletion
11
11
 
12
12
  from kodit.config import Endpoint
13
- from kodit.domain.services.enrichment_service import EnrichmentProvider
14
- from kodit.domain.value_objects import EnrichmentRequest, EnrichmentResponse
15
- from kodit.infrastructure.enrichment.utils import clean_thinking_tags
13
+ from kodit.domain.enrichments.enricher import Enricher
14
+ from kodit.domain.enrichments.request import EnrichmentRequest
15
+ from kodit.domain.enrichments.response import EnrichmentResponse
16
+ from kodit.infrastructure.enricher.utils import clean_thinking_tags
16
17
 
17
- ENRICHMENT_SYSTEM_PROMPT = """
18
- You are a professional software developer. You will be given a snippet of code.
19
- Please provide a concise explanation of the code.
20
- """
21
-
22
- # Default tuned conservatively for broad provider compatibility
23
18
  DEFAULT_NUM_PARALLEL_TASKS = 20
24
19
 
25
20
 
26
- class LiteLLMEnrichmentProvider(EnrichmentProvider):
27
- """LiteLLM enrichment provider that supports 100+ providers."""
21
+ class LiteLLMEnricher(Enricher):
22
+ """LiteLLM enricher that supports 100+ providers."""
28
23
 
29
24
  def __init__(
30
25
  self,
31
26
  endpoint: Endpoint,
32
27
  ) -> None:
33
- """Initialize the LiteLLM enrichment provider.
28
+ """Initialize the LiteLLM enricher.
34
29
 
35
30
  Args:
36
31
  endpoint: The endpoint configuration containing all settings.
@@ -44,23 +39,20 @@ class LiteLLMEnrichmentProvider(EnrichmentProvider):
44
39
  self.num_parallel_tasks = (
45
40
  endpoint.num_parallel_tasks or DEFAULT_NUM_PARALLEL_TASKS
46
41
  )
47
- self.timeout = endpoint.timeout or 30.0
42
+ self.timeout = endpoint.timeout
48
43
  self.extra_params = endpoint.extra_params or {}
49
44
 
50
- # Configure LiteLLM with custom HTTPX client for Unix socket support if needed
51
45
  self._setup_litellm_client()
52
46
 
53
47
  def _setup_litellm_client(self) -> None:
54
48
  """Set up LiteLLM with custom HTTPX client for Unix socket support."""
55
49
  if self.socket_path:
56
- # Create HTTPX client with Unix socket transport
57
50
  transport = httpx.AsyncHTTPTransport(uds=self.socket_path)
58
51
  unix_client = httpx.AsyncClient(
59
52
  transport=transport,
60
- base_url="http://localhost", # Base URL for Unix socket
53
+ base_url="http://localhost",
61
54
  timeout=self.timeout,
62
55
  )
63
- # Set as LiteLLM's async client session
64
56
  litellm.aclient_session = unix_client
65
57
 
66
58
  async def _call_chat_completion(self, messages: list[dict[str, str]]) -> Any:
@@ -79,20 +71,17 @@ class LiteLLMEnrichmentProvider(EnrichmentProvider):
79
71
  "timeout": self.timeout,
80
72
  }
81
73
 
82
- # Add API key if provided
83
74
  if self.api_key:
84
75
  kwargs["api_key"] = self.api_key
85
76
 
86
- # Add base_url if provided
87
77
  if self.base_url:
88
78
  kwargs["api_base"] = self.base_url
89
79
 
90
- # Add extra parameters
91
80
  kwargs.update(self.extra_params)
92
81
 
93
82
  try:
94
- # Use litellm's async completion function
95
83
  response = await acompletion(**kwargs)
84
+ self.log.debug("enrichment request", request=kwargs, response=response)
96
85
  return (
97
86
  response.model_dump() if hasattr(response, "model_dump") else response
98
87
  )
@@ -108,30 +97,31 @@ class LiteLLMEnrichmentProvider(EnrichmentProvider):
108
97
  """Enrich a list of requests using LiteLLM.
109
98
 
110
99
  Args:
111
- requests: List of enrichment requests.
100
+ requests: List of generic enrichment requests.
112
101
 
113
102
  Yields:
114
- Enrichment responses as they are processed.
103
+ Generic enrichment responses as they are processed.
115
104
 
116
105
  """
117
106
  if not requests:
118
107
  self.log.warning("No requests for enrichment")
119
108
  return
120
109
 
121
- # Process requests in parallel with a semaphore to limit concurrent requests
122
110
  sem = asyncio.Semaphore(self.num_parallel_tasks)
123
111
 
124
- async def process_request(request: EnrichmentRequest) -> EnrichmentResponse:
112
+ async def process_request(
113
+ request: EnrichmentRequest,
114
+ ) -> EnrichmentResponse:
125
115
  async with sem:
126
116
  if not request.text:
127
117
  return EnrichmentResponse(
128
- snippet_id=request.snippet_id,
118
+ id=request.id,
129
119
  text="",
130
120
  )
131
121
  messages = [
132
122
  {
133
123
  "role": "system",
134
- "content": ENRICHMENT_SYSTEM_PROMPT,
124
+ "content": request.system_prompt,
135
125
  },
136
126
  {"role": "user", "content": request.text},
137
127
  ]
@@ -141,22 +131,19 @@ class LiteLLMEnrichmentProvider(EnrichmentProvider):
141
131
  .get("message", {})
142
132
  .get("content", "")
143
133
  )
144
- # Remove thinking tags from the response
145
134
  cleaned_content = clean_thinking_tags(content or "")
146
135
  return EnrichmentResponse(
147
- snippet_id=request.snippet_id,
136
+ id=request.id,
148
137
  text=cleaned_content,
149
138
  )
150
139
 
151
- # Create tasks for all requests
152
140
  tasks = [process_request(request) for request in requests]
153
141
 
154
- # Process all requests and yield results as they complete
155
142
  for task in asyncio.as_completed(tasks):
156
143
  yield await task
157
144
 
158
145
  async def close(self) -> None:
159
- """Close the provider and cleanup HTTPX client if using Unix sockets."""
146
+ """Close the enricher and cleanup HTTPX client if using Unix sockets."""
160
147
  if (
161
148
  self.socket_path
162
149
  and hasattr(litellm, "aclient_session")