coderag-ai 0.3.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.
- coderag/__init__.py +12 -0
- coderag/analyzers/__init__.py +0 -0
- coderag/analyzers/base.py +36 -0
- coderag/analyzers/flake8_adapter.py +43 -0
- coderag/analyzers/pylint_adapter.py +50 -0
- coderag/analyzers/workflow.py +107 -0
- coderag/api/__init__.py +0 -0
- coderag/api/app.py +351 -0
- coderag/api/dashboard.py +237 -0
- coderag/api/schemas.py +181 -0
- coderag/cli/__init__.py +0 -0
- coderag/cli/main.py +359 -0
- coderag/context/__init__.py +0 -0
- coderag/context/builder.py +206 -0
- coderag/context/package.py +100 -0
- coderag/core/__init__.py +0 -0
- coderag/core/config.py +100 -0
- coderag/core/logging.py +58 -0
- coderag/core/text.py +80 -0
- coderag/core/tokens.py +66 -0
- coderag/db/__init__.py +0 -0
- coderag/db/base.py +58 -0
- coderag/db/migrations/env.py +77 -0
- coderag/db/migrations/script.py.mako +26 -0
- coderag/db/migrations/versions/.gitkeep +0 -0
- coderag/db/migrations/versions/9a973932b94d_measure_baseline_tokens.py +41 -0
- coderag/db/migrations/versions/c8a40feeda41_initial_schema.py +227 -0
- coderag/db/models.py +275 -0
- coderag/embeddings/__init__.py +0 -0
- coderag/embeddings/base.py +53 -0
- coderag/embeddings/hashing.py +43 -0
- coderag/embeddings/pipeline.py +87 -0
- coderag/embeddings/registry.py +38 -0
- coderag/embeddings/sentence_transformer.py +47 -0
- coderag/evaluation/__init__.py +0 -0
- coderag/evaluation/datasets.py +36 -0
- coderag/evaluation/harness.py +246 -0
- coderag/git/__init__.py +0 -0
- coderag/git/repo.py +139 -0
- coderag/indexing/__init__.py +0 -0
- coderag/indexing/ignore.py +101 -0
- coderag/indexing/indexer.py +445 -0
- coderag/llm/__init__.py +0 -0
- coderag/llm/anthropic.py +117 -0
- coderag/llm/base.py +60 -0
- coderag/llm/null.py +16 -0
- coderag/llm/registry.py +17 -0
- coderag/localdb.py +100 -0
- coderag/mcp_server.py +209 -0
- coderag/parsing/__init__.py +0 -0
- coderag/parsing/base.py +76 -0
- coderag/parsing/python.py +294 -0
- coderag/parsing/registry.py +40 -0
- coderag/retrieval/__init__.py +0 -0
- coderag/retrieval/base.py +60 -0
- coderag/retrieval/engine.py +176 -0
- coderag/retrieval/fusion.py +41 -0
- coderag/retrieval/graph.py +139 -0
- coderag/retrieval/lexical.py +57 -0
- coderag/retrieval/reranking.py +99 -0
- coderag/retrieval/semantic.py +53 -0
- coderag/retrieval/symbol.py +91 -0
- coderag/security/__init__.py +0 -0
- coderag/security/authz.py +35 -0
- coderag/security/secrets.py +110 -0
- coderag/service.py +160 -0
- coderag/telemetry.py +72 -0
- coderag_ai-0.3.0.dist-info/METADATA +364 -0
- coderag_ai-0.3.0.dist-info/RECORD +73 -0
- coderag_ai-0.3.0.dist-info/WHEEL +4 -0
- coderag_ai-0.3.0.dist-info/entry_points.txt +3 -0
- coderag_ai-0.3.0.dist-info/licenses/LICENSE +202 -0
- coderag_ai-0.3.0.dist-info/licenses/NOTICE +5 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"""initial schema
|
|
2
|
+
|
|
3
|
+
Revision ID: c8a40feeda41
|
|
4
|
+
Revises:
|
|
5
|
+
Create Date: 2026-09-01 21:50:43.238620
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from collections.abc import Sequence
|
|
10
|
+
|
|
11
|
+
import pgvector.sqlalchemy
|
|
12
|
+
import sqlalchemy as sa
|
|
13
|
+
from alembic import op
|
|
14
|
+
from sqlalchemy.dialects import postgresql
|
|
15
|
+
|
|
16
|
+
revision: str = 'c8a40feeda41'
|
|
17
|
+
down_revision: str | None = None
|
|
18
|
+
branch_labels: str | Sequence[str] | None = None
|
|
19
|
+
depends_on: str | Sequence[str] | None = None
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def upgrade() -> None:
|
|
23
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
|
24
|
+
op.create_table('evaluation_runs',
|
|
25
|
+
sa.Column('id', sa.Integer(), nullable=False),
|
|
26
|
+
sa.Column('name', sa.String(length=255), nullable=False),
|
|
27
|
+
sa.Column('dataset', sa.String(length=1024), nullable=False),
|
|
28
|
+
sa.Column('recall_at_1', sa.Float(), nullable=False),
|
|
29
|
+
sa.Column('recall_at_3', sa.Float(), nullable=False),
|
|
30
|
+
sa.Column('recall_at_5', sa.Float(), nullable=False),
|
|
31
|
+
sa.Column('recall_at_10', sa.Float(), nullable=False),
|
|
32
|
+
sa.Column('mrr', sa.Float(), nullable=False),
|
|
33
|
+
sa.Column('avg_retrieved_tokens', sa.Float(), nullable=False),
|
|
34
|
+
sa.Column('avg_context_tokens', sa.Float(), nullable=False),
|
|
35
|
+
sa.Column('avg_retrieval_latency_ms', sa.Float(), nullable=False),
|
|
36
|
+
sa.Column('config', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
|
|
37
|
+
sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
|
|
38
|
+
sa.PrimaryKeyConstraint('id')
|
|
39
|
+
)
|
|
40
|
+
op.create_table('repositories',
|
|
41
|
+
sa.Column('id', sa.Integer(), nullable=False),
|
|
42
|
+
sa.Column('name', sa.String(length=255), nullable=False),
|
|
43
|
+
sa.Column('url', sa.String(length=1024), nullable=True),
|
|
44
|
+
sa.Column('local_path', sa.String(length=1024), nullable=False),
|
|
45
|
+
sa.Column('default_branch', sa.String(length=255), nullable=False),
|
|
46
|
+
sa.Column('indexed_commit_sha', sa.String(length=64), nullable=True),
|
|
47
|
+
sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
|
|
48
|
+
sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
|
|
49
|
+
sa.PrimaryKeyConstraint('id')
|
|
50
|
+
)
|
|
51
|
+
op.create_index(op.f('ix_repositories_name'), 'repositories', ['name'], unique=True)
|
|
52
|
+
op.create_table('indexing_runs',
|
|
53
|
+
sa.Column('id', sa.Integer(), nullable=False),
|
|
54
|
+
sa.Column('repository_id', sa.Integer(), nullable=False),
|
|
55
|
+
sa.Column('mode', sa.String(length=16), nullable=False),
|
|
56
|
+
sa.Column('status', sa.String(length=16), nullable=False),
|
|
57
|
+
sa.Column('from_commit', sa.String(length=64), nullable=True),
|
|
58
|
+
sa.Column('to_commit', sa.String(length=64), nullable=True),
|
|
59
|
+
sa.Column('files_indexed', sa.Integer(), nullable=False),
|
|
60
|
+
sa.Column('files_deleted', sa.Integer(), nullable=False),
|
|
61
|
+
sa.Column('symbols_indexed', sa.Integer(), nullable=False),
|
|
62
|
+
sa.Column('symbols_deleted', sa.Integer(), nullable=False),
|
|
63
|
+
sa.Column('embeddings_created', sa.Integer(), nullable=False),
|
|
64
|
+
sa.Column('duration_seconds', sa.Float(), nullable=False),
|
|
65
|
+
sa.Column('error', sa.Text(), nullable=True),
|
|
66
|
+
sa.Column('started_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
|
|
67
|
+
sa.Column('finished_at', sa.DateTime(), nullable=True),
|
|
68
|
+
sa.ForeignKeyConstraint(['repository_id'], ['repositories.id'], ondelete='CASCADE'),
|
|
69
|
+
sa.PrimaryKeyConstraint('id')
|
|
70
|
+
)
|
|
71
|
+
op.create_table('queries',
|
|
72
|
+
sa.Column('id', sa.Integer(), nullable=False),
|
|
73
|
+
sa.Column('repository_id', sa.Integer(), nullable=False),
|
|
74
|
+
sa.Column('query_text', sa.Text(), nullable=False),
|
|
75
|
+
sa.Column('mode', sa.String(length=16), nullable=False),
|
|
76
|
+
sa.Column('candidates_found', sa.Integer(), nullable=False),
|
|
77
|
+
sa.Column('candidates_selected', sa.Integer(), nullable=False),
|
|
78
|
+
sa.Column('candidate_tokens', sa.Integer(), nullable=False),
|
|
79
|
+
sa.Column('context_tokens', sa.Integer(), nullable=False),
|
|
80
|
+
sa.Column('dropped_tokens', sa.Integer(), nullable=False),
|
|
81
|
+
sa.Column('retrieval_latency_ms', sa.Float(), nullable=False),
|
|
82
|
+
sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
|
|
83
|
+
sa.ForeignKeyConstraint(['repository_id'], ['repositories.id'], ondelete='CASCADE'),
|
|
84
|
+
sa.PrimaryKeyConstraint('id')
|
|
85
|
+
)
|
|
86
|
+
op.create_table('source_files',
|
|
87
|
+
sa.Column('id', sa.Integer(), nullable=False),
|
|
88
|
+
sa.Column('repository_id', sa.Integer(), nullable=False),
|
|
89
|
+
sa.Column('path', sa.String(length=1024), nullable=False),
|
|
90
|
+
sa.Column('language', sa.String(length=32), nullable=False),
|
|
91
|
+
sa.Column('content_hash', sa.String(length=64), nullable=False),
|
|
92
|
+
sa.Column('size_bytes', sa.Integer(), nullable=False),
|
|
93
|
+
sa.Column('indexed_commit_sha', sa.String(length=64), nullable=True),
|
|
94
|
+
sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
|
|
95
|
+
sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
|
|
96
|
+
sa.ForeignKeyConstraint(['repository_id'], ['repositories.id'], ondelete='CASCADE'),
|
|
97
|
+
sa.PrimaryKeyConstraint('id'),
|
|
98
|
+
sa.UniqueConstraint('repository_id', 'path', name='uq_source_files_repo_path')
|
|
99
|
+
)
|
|
100
|
+
op.create_index('ix_source_files_repo', 'source_files', ['repository_id'], unique=False)
|
|
101
|
+
op.create_table('llm_requests',
|
|
102
|
+
sa.Column('id', sa.Integer(), nullable=False),
|
|
103
|
+
sa.Column('query_id', sa.Integer(), nullable=True),
|
|
104
|
+
sa.Column('provider', sa.String(length=64), nullable=False),
|
|
105
|
+
sa.Column('model', sa.String(length=128), nullable=False),
|
|
106
|
+
sa.Column('input_tokens', sa.Integer(), nullable=False),
|
|
107
|
+
sa.Column('output_tokens', sa.Integer(), nullable=False),
|
|
108
|
+
sa.Column('cached_input_tokens', sa.Integer(), nullable=True),
|
|
109
|
+
sa.Column('latency_ms', sa.Float(), nullable=False),
|
|
110
|
+
sa.Column('success', sa.Boolean(), nullable=False),
|
|
111
|
+
sa.Column('error', sa.Text(), nullable=True),
|
|
112
|
+
sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
|
|
113
|
+
sa.ForeignKeyConstraint(['query_id'], ['queries.id'], ondelete='SET NULL'),
|
|
114
|
+
sa.PrimaryKeyConstraint('id')
|
|
115
|
+
)
|
|
116
|
+
op.create_table('symbols',
|
|
117
|
+
sa.Column('id', sa.Integer(), nullable=False),
|
|
118
|
+
sa.Column('repository_id', sa.Integer(), nullable=False),
|
|
119
|
+
sa.Column('source_file_id', sa.Integer(), nullable=False),
|
|
120
|
+
sa.Column('file_path', sa.String(length=1024), nullable=False),
|
|
121
|
+
sa.Column('language', sa.String(length=32), nullable=False),
|
|
122
|
+
sa.Column('symbol_name', sa.String(length=512), nullable=False),
|
|
123
|
+
sa.Column('qualified_name', sa.String(length=1024), nullable=False),
|
|
124
|
+
sa.Column('symbol_type', sa.String(length=32), nullable=False),
|
|
125
|
+
sa.Column('signature', sa.Text(), nullable=True),
|
|
126
|
+
sa.Column('start_line', sa.Integer(), nullable=False),
|
|
127
|
+
sa.Column('end_line', sa.Integer(), nullable=False),
|
|
128
|
+
sa.Column('parent_symbol_id', sa.Integer(), nullable=True),
|
|
129
|
+
sa.Column('source_code', sa.Text(), nullable=False),
|
|
130
|
+
sa.Column('docstring', sa.Text(), nullable=True),
|
|
131
|
+
sa.Column('source_hash', sa.String(length=64), nullable=False),
|
|
132
|
+
sa.Column('commit_sha', sa.String(length=64), nullable=True),
|
|
133
|
+
sa.Column('token_count', sa.Integer(), nullable=False),
|
|
134
|
+
sa.Column('search_document', sa.Text(), nullable=False),
|
|
135
|
+
sa.Column('fts', postgresql.TSVECTOR(), sa.Computed("to_tsvector('english', search_document)", persisted=True), nullable=False),
|
|
136
|
+
sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
|
|
137
|
+
sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
|
|
138
|
+
sa.ForeignKeyConstraint(['parent_symbol_id'], ['symbols.id'], ondelete='CASCADE'),
|
|
139
|
+
sa.ForeignKeyConstraint(['repository_id'], ['repositories.id'], ondelete='CASCADE'),
|
|
140
|
+
sa.ForeignKeyConstraint(['source_file_id'], ['source_files.id'], ondelete='CASCADE'),
|
|
141
|
+
sa.PrimaryKeyConstraint('id')
|
|
142
|
+
)
|
|
143
|
+
op.create_index('ix_symbols_commit', 'symbols', ['commit_sha'], unique=False)
|
|
144
|
+
op.create_index('ix_symbols_fts', 'symbols', ['fts'], unique=False, postgresql_using='gin')
|
|
145
|
+
op.create_index('ix_symbols_path', 'symbols', ['repository_id', 'file_path'], unique=False)
|
|
146
|
+
op.create_index('ix_symbols_repo', 'symbols', ['repository_id'], unique=False)
|
|
147
|
+
op.create_index('ix_symbols_repo_qualified', 'symbols', ['repository_id', 'qualified_name'], unique=False)
|
|
148
|
+
op.create_index('ix_symbols_repo_type', 'symbols', ['repository_id', 'symbol_type'], unique=False)
|
|
149
|
+
op.create_index('ix_symbols_source_hash', 'symbols', ['source_hash'], unique=False)
|
|
150
|
+
op.create_table('retrieval_results',
|
|
151
|
+
sa.Column('id', sa.Integer(), nullable=False),
|
|
152
|
+
sa.Column('query_id', sa.Integer(), nullable=False),
|
|
153
|
+
sa.Column('symbol_id', sa.Integer(), nullable=False),
|
|
154
|
+
sa.Column('rank', sa.Integer(), nullable=False),
|
|
155
|
+
sa.Column('score', sa.Float(), nullable=False),
|
|
156
|
+
sa.Column('reasons', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
|
|
157
|
+
sa.Column('selected', sa.Boolean(), nullable=False),
|
|
158
|
+
sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
|
|
159
|
+
sa.ForeignKeyConstraint(['query_id'], ['queries.id'], ondelete='CASCADE'),
|
|
160
|
+
sa.ForeignKeyConstraint(['symbol_id'], ['symbols.id'], ondelete='CASCADE'),
|
|
161
|
+
sa.PrimaryKeyConstraint('id')
|
|
162
|
+
)
|
|
163
|
+
op.create_index('ix_retrieval_query', 'retrieval_results', ['query_id'], unique=False)
|
|
164
|
+
op.create_table('symbol_embeddings',
|
|
165
|
+
sa.Column('id', sa.Integer(), nullable=False),
|
|
166
|
+
sa.Column('repository_id', sa.Integer(), nullable=False),
|
|
167
|
+
sa.Column('symbol_id', sa.Integer(), nullable=False),
|
|
168
|
+
sa.Column('source_hash', sa.String(length=64), nullable=False),
|
|
169
|
+
sa.Column('embedding_model', sa.String(length=255), nullable=False),
|
|
170
|
+
sa.Column('embedding_version', sa.String(length=64), nullable=False),
|
|
171
|
+
sa.Column('embedding_dimension', sa.Integer(), nullable=False),
|
|
172
|
+
sa.Column('embedding', pgvector.sqlalchemy.Vector(), nullable=False),
|
|
173
|
+
sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
|
|
174
|
+
sa.ForeignKeyConstraint(['repository_id'], ['repositories.id'], ondelete='CASCADE'),
|
|
175
|
+
sa.ForeignKeyConstraint(['symbol_id'], ['symbols.id'], ondelete='CASCADE'),
|
|
176
|
+
sa.PrimaryKeyConstraint('id'),
|
|
177
|
+
sa.UniqueConstraint('symbol_id', 'embedding_model', 'embedding_version', name='uq_embedding_symbol_model')
|
|
178
|
+
)
|
|
179
|
+
op.create_index('ix_embedding_repo', 'symbol_embeddings', ['repository_id'], unique=False)
|
|
180
|
+
op.create_table('symbol_relationships',
|
|
181
|
+
sa.Column('id', sa.Integer(), nullable=False),
|
|
182
|
+
sa.Column('repository_id', sa.Integer(), nullable=False),
|
|
183
|
+
sa.Column('source_symbol_id', sa.Integer(), nullable=False),
|
|
184
|
+
sa.Column('target_symbol_id', sa.Integer(), nullable=True),
|
|
185
|
+
sa.Column('target_name', sa.String(length=1024), nullable=True),
|
|
186
|
+
sa.Column('relationship_type', sa.String(length=32), nullable=False),
|
|
187
|
+
sa.Column('confidence', sa.Float(), nullable=False),
|
|
188
|
+
sa.Column('metadata', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
|
|
189
|
+
sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
|
|
190
|
+
sa.ForeignKeyConstraint(['repository_id'], ['repositories.id'], ondelete='CASCADE'),
|
|
191
|
+
sa.ForeignKeyConstraint(['source_symbol_id'], ['symbols.id'], ondelete='CASCADE'),
|
|
192
|
+
sa.ForeignKeyConstraint(['target_symbol_id'], ['symbols.id'], ondelete='CASCADE'),
|
|
193
|
+
sa.PrimaryKeyConstraint('id')
|
|
194
|
+
)
|
|
195
|
+
op.create_index('ix_rel_repo_source', 'symbol_relationships', ['repository_id', 'source_symbol_id'], unique=False)
|
|
196
|
+
op.create_index('ix_rel_repo_target', 'symbol_relationships', ['repository_id', 'target_symbol_id'], unique=False)
|
|
197
|
+
op.create_index('ix_rel_type', 'symbol_relationships', ['relationship_type'], unique=False)
|
|
198
|
+
# ### end Alembic commands ###
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def downgrade() -> None:
|
|
202
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
|
203
|
+
op.drop_index('ix_rel_type', table_name='symbol_relationships')
|
|
204
|
+
op.drop_index('ix_rel_repo_target', table_name='symbol_relationships')
|
|
205
|
+
op.drop_index('ix_rel_repo_source', table_name='symbol_relationships')
|
|
206
|
+
op.drop_table('symbol_relationships')
|
|
207
|
+
op.drop_index('ix_embedding_repo', table_name='symbol_embeddings')
|
|
208
|
+
op.drop_table('symbol_embeddings')
|
|
209
|
+
op.drop_index('ix_retrieval_query', table_name='retrieval_results')
|
|
210
|
+
op.drop_table('retrieval_results')
|
|
211
|
+
op.drop_index('ix_symbols_source_hash', table_name='symbols')
|
|
212
|
+
op.drop_index('ix_symbols_repo_type', table_name='symbols')
|
|
213
|
+
op.drop_index('ix_symbols_repo_qualified', table_name='symbols')
|
|
214
|
+
op.drop_index('ix_symbols_repo', table_name='symbols')
|
|
215
|
+
op.drop_index('ix_symbols_path', table_name='symbols')
|
|
216
|
+
op.drop_index('ix_symbols_fts', table_name='symbols', postgresql_using='gin')
|
|
217
|
+
op.drop_index('ix_symbols_commit', table_name='symbols')
|
|
218
|
+
op.drop_table('symbols')
|
|
219
|
+
op.drop_table('llm_requests')
|
|
220
|
+
op.drop_index('ix_source_files_repo', table_name='source_files')
|
|
221
|
+
op.drop_table('source_files')
|
|
222
|
+
op.drop_table('queries')
|
|
223
|
+
op.drop_table('indexing_runs')
|
|
224
|
+
op.drop_index(op.f('ix_repositories_name'), table_name='repositories')
|
|
225
|
+
op.drop_table('repositories')
|
|
226
|
+
op.drop_table('evaluation_runs')
|
|
227
|
+
# ### end Alembic commands ###
|
coderag/db/models.py
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
"""SQLAlchemy 2.0 ORM models — the persisted code index and telemetry.
|
|
2
|
+
|
|
3
|
+
Design notes:
|
|
4
|
+
* Every retrieval-relevant table carries ``repository_id`` so queries can
|
|
5
|
+
(and must) be scoped to a single repository — see ``security/authz.py``.
|
|
6
|
+
* ``symbols.fts`` is a generated ``tsvector`` (Postgres full-text search).
|
|
7
|
+
* ``symbol_embeddings.embedding`` uses a dimension-less pgvector column so
|
|
8
|
+
the embedding model (and its dimension) can change without a schema
|
|
9
|
+
migration; the concrete dimension is recorded per row.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import datetime as dt
|
|
15
|
+
|
|
16
|
+
from pgvector.sqlalchemy import Vector
|
|
17
|
+
from sqlalchemy import (
|
|
18
|
+
JSON,
|
|
19
|
+
Boolean,
|
|
20
|
+
Computed,
|
|
21
|
+
Float,
|
|
22
|
+
ForeignKey,
|
|
23
|
+
Index,
|
|
24
|
+
Integer,
|
|
25
|
+
String,
|
|
26
|
+
Text,
|
|
27
|
+
UniqueConstraint,
|
|
28
|
+
func,
|
|
29
|
+
)
|
|
30
|
+
from sqlalchemy.dialects.postgresql import JSONB, TSVECTOR
|
|
31
|
+
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Base(DeclarativeBase):
|
|
35
|
+
type_annotation_map = {dict: JSON}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
TS = dt.datetime
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class Repository(Base):
|
|
42
|
+
__tablename__ = "repositories"
|
|
43
|
+
|
|
44
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
45
|
+
name: Mapped[str] = mapped_column(String(255), unique=True, index=True)
|
|
46
|
+
url: Mapped[str | None] = mapped_column(String(1024), nullable=True)
|
|
47
|
+
local_path: Mapped[str] = mapped_column(String(1024))
|
|
48
|
+
default_branch: Mapped[str] = mapped_column(String(255), default="main")
|
|
49
|
+
indexed_commit_sha: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
50
|
+
created_at: Mapped[TS] = mapped_column(server_default=func.now())
|
|
51
|
+
updated_at: Mapped[TS] = mapped_column(server_default=func.now(), onupdate=func.now())
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class SourceFile(Base):
|
|
55
|
+
__tablename__ = "source_files"
|
|
56
|
+
__table_args__ = (
|
|
57
|
+
UniqueConstraint("repository_id", "path", name="uq_source_files_repo_path"),
|
|
58
|
+
Index("ix_source_files_repo", "repository_id"),
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
62
|
+
repository_id: Mapped[int] = mapped_column(
|
|
63
|
+
ForeignKey("repositories.id", ondelete="CASCADE")
|
|
64
|
+
)
|
|
65
|
+
path: Mapped[str] = mapped_column(String(1024))
|
|
66
|
+
language: Mapped[str] = mapped_column(String(32))
|
|
67
|
+
content_hash: Mapped[str] = mapped_column(String(64))
|
|
68
|
+
size_bytes: Mapped[int] = mapped_column(Integer, default=0)
|
|
69
|
+
# Whole-file token count, recorded at index time so the "what would reading
|
|
70
|
+
# this file have cost?" baseline needs no file I/O at query time.
|
|
71
|
+
token_count: Mapped[int] = mapped_column(Integer, default=0)
|
|
72
|
+
indexed_commit_sha: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
73
|
+
created_at: Mapped[TS] = mapped_column(server_default=func.now())
|
|
74
|
+
updated_at: Mapped[TS] = mapped_column(server_default=func.now(), onupdate=func.now())
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class Symbol(Base):
|
|
78
|
+
__tablename__ = "symbols"
|
|
79
|
+
__table_args__ = (
|
|
80
|
+
Index("ix_symbols_repo", "repository_id"),
|
|
81
|
+
Index("ix_symbols_repo_qualified", "repository_id", "qualified_name"),
|
|
82
|
+
Index("ix_symbols_repo_type", "repository_id", "symbol_type"),
|
|
83
|
+
Index("ix_symbols_path", "repository_id", "file_path"),
|
|
84
|
+
Index("ix_symbols_source_hash", "source_hash"),
|
|
85
|
+
Index("ix_symbols_commit", "commit_sha"),
|
|
86
|
+
Index("ix_symbols_fts", "fts", postgresql_using="gin"),
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
90
|
+
repository_id: Mapped[int] = mapped_column(
|
|
91
|
+
ForeignKey("repositories.id", ondelete="CASCADE")
|
|
92
|
+
)
|
|
93
|
+
source_file_id: Mapped[int] = mapped_column(
|
|
94
|
+
ForeignKey("source_files.id", ondelete="CASCADE")
|
|
95
|
+
)
|
|
96
|
+
file_path: Mapped[str] = mapped_column(String(1024))
|
|
97
|
+
language: Mapped[str] = mapped_column(String(32))
|
|
98
|
+
|
|
99
|
+
symbol_name: Mapped[str] = mapped_column(String(512))
|
|
100
|
+
qualified_name: Mapped[str] = mapped_column(String(1024))
|
|
101
|
+
symbol_type: Mapped[str] = mapped_column(String(32)) # module|class|function|method
|
|
102
|
+
signature: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
103
|
+
|
|
104
|
+
start_line: Mapped[int] = mapped_column(Integer)
|
|
105
|
+
end_line: Mapped[int] = mapped_column(Integer)
|
|
106
|
+
parent_symbol_id: Mapped[int | None] = mapped_column(
|
|
107
|
+
ForeignKey("symbols.id", ondelete="CASCADE"), nullable=True
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
source_code: Mapped[str] = mapped_column(Text)
|
|
111
|
+
docstring: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
112
|
+
source_hash: Mapped[str] = mapped_column(String(64))
|
|
113
|
+
commit_sha: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
114
|
+
token_count: Mapped[int] = mapped_column(Integer, default=0)
|
|
115
|
+
|
|
116
|
+
# Text feeding full-text search (identifiers + signature + docstring).
|
|
117
|
+
search_document: Mapped[str] = mapped_column(Text, default="")
|
|
118
|
+
fts: Mapped[str] = mapped_column(
|
|
119
|
+
TSVECTOR,
|
|
120
|
+
Computed("to_tsvector('english', search_document)", persisted=True),
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
created_at: Mapped[TS] = mapped_column(server_default=func.now())
|
|
124
|
+
updated_at: Mapped[TS] = mapped_column(server_default=func.now(), onupdate=func.now())
|
|
125
|
+
|
|
126
|
+
parent: Mapped[Symbol | None] = relationship(
|
|
127
|
+
"Symbol", remote_side="Symbol.id", back_populates="children"
|
|
128
|
+
)
|
|
129
|
+
children: Mapped[list[Symbol]] = relationship(
|
|
130
|
+
"Symbol", back_populates="parent", viewonly=True
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class SymbolRelationship(Base):
|
|
135
|
+
__tablename__ = "symbol_relationships"
|
|
136
|
+
__table_args__ = (
|
|
137
|
+
Index("ix_rel_repo_source", "repository_id", "source_symbol_id"),
|
|
138
|
+
Index("ix_rel_repo_target", "repository_id", "target_symbol_id"),
|
|
139
|
+
Index("ix_rel_type", "relationship_type"),
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
143
|
+
repository_id: Mapped[int] = mapped_column(
|
|
144
|
+
ForeignKey("repositories.id", ondelete="CASCADE")
|
|
145
|
+
)
|
|
146
|
+
source_symbol_id: Mapped[int] = mapped_column(
|
|
147
|
+
ForeignKey("symbols.id", ondelete="CASCADE")
|
|
148
|
+
)
|
|
149
|
+
# Target may be unresolved (an external/未-indexed name), so it is nullable
|
|
150
|
+
# and we also keep the raw target_name.
|
|
151
|
+
target_symbol_id: Mapped[int | None] = mapped_column(
|
|
152
|
+
ForeignKey("symbols.id", ondelete="CASCADE"), nullable=True
|
|
153
|
+
)
|
|
154
|
+
target_name: Mapped[str | None] = mapped_column(String(1024), nullable=True)
|
|
155
|
+
relationship_type: Mapped[str] = mapped_column(String(32))
|
|
156
|
+
confidence: Mapped[float] = mapped_column(Float, default=1.0)
|
|
157
|
+
meta: Mapped[dict | None] = mapped_column("metadata", JSONB, nullable=True)
|
|
158
|
+
created_at: Mapped[TS] = mapped_column(server_default=func.now())
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class SymbolEmbedding(Base):
|
|
162
|
+
__tablename__ = "symbol_embeddings"
|
|
163
|
+
__table_args__ = (
|
|
164
|
+
UniqueConstraint(
|
|
165
|
+
"symbol_id", "embedding_model", "embedding_version",
|
|
166
|
+
name="uq_embedding_symbol_model",
|
|
167
|
+
),
|
|
168
|
+
Index("ix_embedding_repo", "repository_id"),
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
172
|
+
repository_id: Mapped[int] = mapped_column(
|
|
173
|
+
ForeignKey("repositories.id", ondelete="CASCADE")
|
|
174
|
+
)
|
|
175
|
+
symbol_id: Mapped[int] = mapped_column(ForeignKey("symbols.id", ondelete="CASCADE"))
|
|
176
|
+
source_hash: Mapped[str] = mapped_column(String(64))
|
|
177
|
+
embedding_model: Mapped[str] = mapped_column(String(255))
|
|
178
|
+
embedding_version: Mapped[str] = mapped_column(String(64))
|
|
179
|
+
embedding_dimension: Mapped[int] = mapped_column(Integer)
|
|
180
|
+
embedding: Mapped[list[float]] = mapped_column(Vector()) # dimension-less
|
|
181
|
+
created_at: Mapped[TS] = mapped_column(server_default=func.now())
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class IndexingRun(Base):
|
|
185
|
+
__tablename__ = "indexing_runs"
|
|
186
|
+
|
|
187
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
188
|
+
repository_id: Mapped[int] = mapped_column(
|
|
189
|
+
ForeignKey("repositories.id", ondelete="CASCADE")
|
|
190
|
+
)
|
|
191
|
+
mode: Mapped[str] = mapped_column(String(16)) # full|incremental
|
|
192
|
+
status: Mapped[str] = mapped_column(String(16), default="running")
|
|
193
|
+
from_commit: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
194
|
+
to_commit: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
195
|
+
files_indexed: Mapped[int] = mapped_column(Integer, default=0)
|
|
196
|
+
files_deleted: Mapped[int] = mapped_column(Integer, default=0)
|
|
197
|
+
symbols_indexed: Mapped[int] = mapped_column(Integer, default=0)
|
|
198
|
+
symbols_deleted: Mapped[int] = mapped_column(Integer, default=0)
|
|
199
|
+
embeddings_created: Mapped[int] = mapped_column(Integer, default=0)
|
|
200
|
+
duration_seconds: Mapped[float] = mapped_column(Float, default=0.0)
|
|
201
|
+
error: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
202
|
+
started_at: Mapped[TS] = mapped_column(server_default=func.now())
|
|
203
|
+
finished_at: Mapped[TS | None] = mapped_column(nullable=True)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
class QueryRecord(Base):
|
|
207
|
+
__tablename__ = "queries"
|
|
208
|
+
|
|
209
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
210
|
+
repository_id: Mapped[int] = mapped_column(
|
|
211
|
+
ForeignKey("repositories.id", ondelete="CASCADE")
|
|
212
|
+
)
|
|
213
|
+
query_text: Mapped[str] = mapped_column(Text)
|
|
214
|
+
mode: Mapped[str] = mapped_column(String(16)) # search|context|ask
|
|
215
|
+
candidates_found: Mapped[int] = mapped_column(Integer, default=0)
|
|
216
|
+
candidates_selected: Mapped[int] = mapped_column(Integer, default=0)
|
|
217
|
+
candidate_tokens: Mapped[int] = mapped_column(Integer, default=0)
|
|
218
|
+
context_tokens: Mapped[int] = mapped_column(Integer, default=0)
|
|
219
|
+
dropped_tokens: Mapped[int] = mapped_column(Integer, default=0)
|
|
220
|
+
# Counterfactual: tokens it would have cost to read the whole files that the
|
|
221
|
+
# selected symbols came from (the "just open the files" baseline).
|
|
222
|
+
baseline_tokens: Mapped[int] = mapped_column(Integer, default=0)
|
|
223
|
+
baseline_files: Mapped[int] = mapped_column(Integer, default=0)
|
|
224
|
+
retrieval_latency_ms: Mapped[float] = mapped_column(Float, default=0.0)
|
|
225
|
+
created_at: Mapped[TS] = mapped_column(server_default=func.now())
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
class RetrievalResult(Base):
|
|
229
|
+
__tablename__ = "retrieval_results"
|
|
230
|
+
__table_args__ = (Index("ix_retrieval_query", "query_id"),)
|
|
231
|
+
|
|
232
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
233
|
+
query_id: Mapped[int] = mapped_column(ForeignKey("queries.id", ondelete="CASCADE"))
|
|
234
|
+
symbol_id: Mapped[int] = mapped_column(ForeignKey("symbols.id", ondelete="CASCADE"))
|
|
235
|
+
rank: Mapped[int] = mapped_column(Integer)
|
|
236
|
+
score: Mapped[float] = mapped_column(Float)
|
|
237
|
+
reasons: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
|
|
238
|
+
selected: Mapped[bool] = mapped_column(Boolean, default=False)
|
|
239
|
+
created_at: Mapped[TS] = mapped_column(server_default=func.now())
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
class LLMRequest(Base):
|
|
243
|
+
__tablename__ = "llm_requests"
|
|
244
|
+
|
|
245
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
246
|
+
query_id: Mapped[int | None] = mapped_column(
|
|
247
|
+
ForeignKey("queries.id", ondelete="SET NULL"), nullable=True
|
|
248
|
+
)
|
|
249
|
+
provider: Mapped[str] = mapped_column(String(64))
|
|
250
|
+
model: Mapped[str] = mapped_column(String(128))
|
|
251
|
+
input_tokens: Mapped[int] = mapped_column(Integer, default=0)
|
|
252
|
+
output_tokens: Mapped[int] = mapped_column(Integer, default=0)
|
|
253
|
+
cached_input_tokens: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
254
|
+
latency_ms: Mapped[float] = mapped_column(Float, default=0.0)
|
|
255
|
+
success: Mapped[bool] = mapped_column(Boolean, default=True)
|
|
256
|
+
error: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
257
|
+
created_at: Mapped[TS] = mapped_column(server_default=func.now())
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
class EvaluationRun(Base):
|
|
261
|
+
__tablename__ = "evaluation_runs"
|
|
262
|
+
|
|
263
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
264
|
+
name: Mapped[str] = mapped_column(String(255))
|
|
265
|
+
dataset: Mapped[str] = mapped_column(String(1024))
|
|
266
|
+
recall_at_1: Mapped[float] = mapped_column(Float, default=0.0)
|
|
267
|
+
recall_at_3: Mapped[float] = mapped_column(Float, default=0.0)
|
|
268
|
+
recall_at_5: Mapped[float] = mapped_column(Float, default=0.0)
|
|
269
|
+
recall_at_10: Mapped[float] = mapped_column(Float, default=0.0)
|
|
270
|
+
mrr: Mapped[float] = mapped_column(Float, default=0.0)
|
|
271
|
+
avg_retrieved_tokens: Mapped[float] = mapped_column(Float, default=0.0)
|
|
272
|
+
avg_context_tokens: Mapped[float] = mapped_column(Float, default=0.0)
|
|
273
|
+
avg_retrieval_latency_ms: Mapped[float] = mapped_column(Float, default=0.0)
|
|
274
|
+
config: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
|
|
275
|
+
created_at: Mapped[TS] = mapped_column(server_default=func.now())
|
|
File without changes
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Embedding provider abstraction.
|
|
2
|
+
|
|
3
|
+
No source code should need to leave company infrastructure to be embedded, so
|
|
4
|
+
every provider runs locally. The default (``HashingEmbeddingProvider``) is
|
|
5
|
+
deterministic and dependency-free; ``SentenceTransformerEmbeddingProvider`` uses
|
|
6
|
+
a local model. Embeddings are provider-independent: each stored vector records
|
|
7
|
+
its model, version, and dimension so the model can change and code can be
|
|
8
|
+
re-embedded (ADR-005 / schema notes).
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from abc import ABC, abstractmethod
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class EmbeddingProvider(ABC):
|
|
17
|
+
model_name: str
|
|
18
|
+
model_version: str
|
|
19
|
+
dimension: int
|
|
20
|
+
|
|
21
|
+
@abstractmethod
|
|
22
|
+
def embed_documents(self, texts: list[str]) -> list[list[float]]:
|
|
23
|
+
"""Embed a batch of documents (structured symbol inputs)."""
|
|
24
|
+
|
|
25
|
+
@abstractmethod
|
|
26
|
+
def embed_query(self, text: str) -> list[float]:
|
|
27
|
+
"""Embed a single query string."""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def build_embedding_input(
|
|
31
|
+
repository_name: str,
|
|
32
|
+
file_path: str,
|
|
33
|
+
qualified_name: str,
|
|
34
|
+
symbol_type: str,
|
|
35
|
+
signature: str | None,
|
|
36
|
+
docstring: str | None,
|
|
37
|
+
source_code: str,
|
|
38
|
+
max_code_chars: int = 4000,
|
|
39
|
+
) -> str:
|
|
40
|
+
"""Compose an embedding input that carries structural context (spec §9)."""
|
|
41
|
+
code = source_code if len(source_code) <= max_code_chars else source_code[:max_code_chars]
|
|
42
|
+
parts = [
|
|
43
|
+
f"Repository: {repository_name}",
|
|
44
|
+
f"File: {file_path}",
|
|
45
|
+
f"Symbol: {qualified_name}",
|
|
46
|
+
f"Type: {symbol_type}",
|
|
47
|
+
]
|
|
48
|
+
if signature:
|
|
49
|
+
parts.append(f"Signature: {signature}")
|
|
50
|
+
if docstring:
|
|
51
|
+
parts.append(f"Documentation: {docstring}")
|
|
52
|
+
parts.append(f"Code:\n{code}")
|
|
53
|
+
return "\n".join(parts)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Deterministic, offline embedding provider (default).
|
|
2
|
+
|
|
3
|
+
Uses signed feature hashing over word-expanded identifiers: each token is hashed
|
|
4
|
+
to a bucket with a +/- sign, summed, and L2-normalised. This is NOT a learned
|
|
5
|
+
semantic model — similarity reflects shared vocabulary — but it is fully offline,
|
|
6
|
+
deterministic, and dependency-free, which makes it ideal for tests, CI, and
|
|
7
|
+
air-gapped dev. For genuine semantic matching use
|
|
8
|
+
``SentenceTransformerEmbeddingProvider``.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import hashlib
|
|
14
|
+
import math
|
|
15
|
+
|
|
16
|
+
from coderag.core.text import expand_query_terms
|
|
17
|
+
from coderag.embeddings.base import EmbeddingProvider
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class HashingEmbeddingProvider(EmbeddingProvider):
|
|
21
|
+
model_version = "1"
|
|
22
|
+
|
|
23
|
+
def __init__(self, dimension: int = 384) -> None:
|
|
24
|
+
self.dimension = dimension
|
|
25
|
+
self.model_name = f"hashing-{dimension}"
|
|
26
|
+
|
|
27
|
+
def _embed(self, text: str) -> list[float]:
|
|
28
|
+
vec = [0.0] * self.dimension
|
|
29
|
+
for token in expand_query_terms(text):
|
|
30
|
+
digest = hashlib.md5(token.lower().encode("utf-8")).digest() # noqa: S324
|
|
31
|
+
idx = int.from_bytes(digest[:4], "big") % self.dimension
|
|
32
|
+
sign = 1.0 if (digest[4] & 1) else -1.0
|
|
33
|
+
vec[idx] += sign
|
|
34
|
+
norm = math.sqrt(sum(v * v for v in vec))
|
|
35
|
+
if norm > 0:
|
|
36
|
+
vec = [v / norm for v in vec]
|
|
37
|
+
return vec
|
|
38
|
+
|
|
39
|
+
def embed_documents(self, texts: list[str]) -> list[list[float]]:
|
|
40
|
+
return [self._embed(t) for t in texts]
|
|
41
|
+
|
|
42
|
+
def embed_query(self, text: str) -> list[float]:
|
|
43
|
+
return self._embed(text)
|