Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
f16eefc
feat(model): add document table model
NKTKLN Jun 23, 2026
a97f9d2
build(alembic): add documents table
NKTKLN Jun 23, 2026
69258b4
build(alembic): enable pgvector extension
NKTKLN Jun 23, 2026
b3d59c2
build(docker): update postgres image
NKTKLN Jun 23, 2026
a836fe4
feat(models): connect user to documents
NKTKLN Jun 23, 2026
84e30bf
build(alembic): connect user to documents
NKTKLN Jun 23, 2026
79d6fa7
feat(model): add document_chunk table
NKTKLN Jun 23, 2026
4161fbf
refactor(models): remove default=None
NKTKLN Jun 23, 2026
3221d34
build(alembic): remove default=None
NKTKLN Jun 23, 2026
ff51c09
feat(model): add rag_query model
NKTKLN Jun 23, 2026
834770d
build(alembic): add rag_queries table
NKTKLN Jun 23, 2026
52734ef
feat(model): add rag_query_source model
NKTKLN Jun 23, 2026
2f83461
build(alembic): fix rag_queries table vector type
NKTKLN Jun 23, 2026
8b2f031
build(alembic): add rag_query_sources table
NKTKLN Jun 23, 2026
ab41aad
style(model): fix RagQuery docstring
NKTKLN Jun 23, 2026
c66664a
docs(readme): add erd
NKTKLN Jun 23, 2026
86f896c
feat(repository): add document repository
NKTKLN Jun 23, 2026
1253bf3
feat(repostitory): add rag_query repository
NKTKLN Jun 23, 2026
1657ce1
feat(repository): add rag_query_source repository
NKTKLN Jun 23, 2026
4c82733
feat(model): add processed_at in document model
NKTKLN Jun 23, 2026
30fcfb2
build(alembic): add processed_at column in documents
NKTKLN Jun 23, 2026
ab3ed63
docs(readme): fix erd
NKTKLN Jun 23, 2026
eb8a50b
docs(repository): update docstring in soft_delete func
NKTKLN Jun 23, 2026
aae5670
feat(repository): add document_chunk repository
NKTKLN Jun 23, 2026
cb14f21
feat(repository): update init file
NKTKLN Jun 23, 2026
1f9a40d
style(repository): rename var in vector_search_in_user_session
NKTKLN Jun 23, 2026
90e020f
test(repository): add document repository tests
NKTKLN Jun 23, 2026
4f8d5e6
test(repository): add document chunk repository tests
NKTKLN Jun 23, 2026
fb252ef
test(repository): add rag query repository tests
NKTKLN Jun 23, 2026
7ba5d8d
test(repository): add rag query source repository tests
NKTKLN Jun 23, 2026
e8d8a63
build(docker): add MinIO
NKTKLN Jun 23, 2026
7d78409
build(uv): add minio
NKTKLN Jun 23, 2026
b62c667
feat(storage): add minio document storage service
NKTKLN Jun 23, 2026
3bfb03b
docs(minio): update config
NKTKLN Jun 23, 2026
b6818bd
feat(schemas): add document, chunk and rag schemas
NKTKLN Jun 23, 2026
f96e68e
style(imports): use absolute imports in package init files
NKTKLN Jun 23, 2026
246e5e2
build(uv): remove minio and add aioboto3
NKTKLN Jun 23, 2026
acc6cc3
refactor(config): update s3 config names
NKTKLN Jun 23, 2026
b647d9c
build(mypy): add aioboto3 ignore in mypy
NKTKLN Jun 23, 2026
eda8931
feat(storage): replcae minio with aioboto3
NKTKLN Jun 23, 2026
5f563f4
feat(model): add document_processing_job model
NKTKLN Jun 23, 2026
bdf7d97
feat(model): remove processed_at column
NKTKLN Jun 23, 2026
decea20
build(alembic): add document_processing_jobs table
NKTKLN Jun 23, 2026
3e74cd4
feat(repository): add document_processing_job repository
NKTKLN Jun 23, 2026
d9d48e1
feat(schemas): add document related schemas
NKTKLN Jun 23, 2026
616f5e4
feat(service): add document service
NKTKLN Jun 23, 2026
b1eda72
build(alembic): update migration
NKTKLN Jun 23, 2026
5d6b38b
feat(exceptions): add document related exceptions
NKTKLN Jun 23, 2026
4c8ec2c
feat(services): connect document with processing_job
NKTKLN Jun 23, 2026
6d53650
test(repository): add document processing job tests
NKTKLN Jun 23, 2026
300d0ce
feat(services): add unwrited document processing job service
NKTKLN Jun 23, 2026
356e925
feat(celery): add processing task
NKTKLN Jun 23, 2026
be7e0c2
docs(readme): update erd
NKTKLN Jun 23, 2026
f915903
feat(processing): add document text chunker
NKTKLN Jun 24, 2026
7d3f1d8
build(uv): add markitdown dependency
NKTKLN Jun 24, 2026
69414bb
feat(processing): add unsupported document format exception
NKTKLN Jun 24, 2026
79579cc
feat(processing): add document text extractor
NKTKLN Jun 24, 2026
f3e2cce
feat(proceesing): rewrite sync functions to async
NKTKLN Jun 24, 2026
ac497f7
refactor(processing): decompose processing func
NKTKLN Jun 24, 2026
8c8a84a
refactor(messages): rename router file
NKTKLN Jun 24, 2026
43e7fcf
feat(documents): add document service dependency
NKTKLN Jun 24, 2026
c5704a2
refactor(llm): rename models module to schemas
NKTKLN Jun 25, 2026
753af48
feat(ingestion): add document text extractor and chunker
NKTKLN Jun 25, 2026
0959781
refactor(processing): use ingestion module in document processing ser…
NKTKLN Jun 25, 2026
2a617e7
refactor(generation): rename job service and add status helpers
NKTKLN Jun 25, 2026
53d63b7
feat(services): add document processing job service
NKTKLN Jun 25, 2026
db284b5
refactor(documents): simplify document service and schemas
NKTKLN Jun 25, 2026
37dbabc
feat(documents): add documents router and wire job services
NKTKLN Jun 25, 2026
c47187c
docs(api): add error responses to endpoints
NKTKLN Jun 25, 2026
aebcf64
test(llm): add tool call and memory task tests
NKTKLN Jun 25, 2026
0838abe
build(uv): add markitdown extras and lower coverage threshold
NKTKLN Jun 25, 2026
aa9cc28
chore(compose): set explicit service passwords
NKTKLN Jun 25, 2026
7d3ba0a
ci(github): add s3 env vars to fix settings load
NKTKLN Jun 25, 2026
c836231
feat(celery): add generation message missing error
NKTKLN Jun 25, 2026
916e513
refactor(ingestion): add typing in chunking
NKTKLN Jun 25, 2026
aeb9001
refactor(mypy): fix error message slice
NKTKLN Jun 25, 2026
fdd9538
feat(celery): add message existing check
NKTKLN Jun 25, 2026
8bbc894
fix(mypy): import ignore
NKTKLN Jun 25, 2026
36fbc25
build(uv): allow prerelease & update packages
NKTKLN Jun 25, 2026
c383cca
ci(workflows): replace postgres with pgvector
NKTKLN Jun 25, 2026
9d8acaf
test(pgvector): fix pgvector plugin
NKTKLN Jun 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,11 @@ LLM_CONTEXT_MESSAGES_LIMIT=20
# Celery
CELERY_BROKER_URL=redis://localhost:6379/0
CELERY_RESULT_BACKEND=redis://localhost:6379/1

# S3
S3_ENDPOINT_URL=http://localhost:9000
S3_ACCESS_KEY_ID=minioadmin
S3_SECRET_ACCESS_KEY=minioadmin
S3_REGION=us-east-1
S3_BUCKET_NAME=documents
S3_PRESIGNED_URL_EXPIRE_SECONDS=3600
7 changes: 6 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:

services:
postgres:
image: postgres:16
image: pgvector/pgvector:pg16
env:
POSTGRES_DB: ai_notes
POSTGRES_USER: postgres
Expand Down Expand Up @@ -63,6 +63,11 @@ jobs:
CELERY_BROKER_URL: redis://localhost:6379/0
CELERY_RESULT_BACKEND: redis://localhost:6379/0

S3_ENDPOINT_URL: http://localhost:9000
S3_ACCESS_KEY_ID: test
S3_SECRET_ACCESS_KEY: test
S3_BUCKET_NAME: documents

steps:
- uses: actions/checkout@v4

Expand Down
176 changes: 176 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,12 @@ Required variables:
* `LLM_CONTEXT_MESSAGES_LIMIT` - number of recent messages sent as context
* `CELERY_BROKER_URL` - Redis URL for the Celery broker
* `CELERY_RESULT_BACKEND` - Redis URL for the Celery result backend
* `S3_ENDPOINT_URL` - S3 endpoint URL
* `S3_ACCESS_KEY_ID` - S3 access key ID
* `S3_SECRET_ACCESS_KEY` - S3 secret access key
* `S3_REGION` - S3 region name, default `us-east-1`
* `S3_BUCKET_NAME` - bucket used to store documents, default `documents`
* `S3_PRESIGNED_URL_EXPIRE_SECONDS` - presigned document URL lifetime in seconds

The database connection URL is composed automatically from the `POSTGRES_*` values.

Expand Down Expand Up @@ -165,6 +171,176 @@ task lint
task check
```

## 🗄 Database schema

Entity-relationship diagram for the database models:

```mermaid
erDiagram
users {
uuid id PK
string email UK
string username "null"
string hashed_password
bool is_active
bool is_superuser
datetime created_at
datetime updated_at
}

notes {
uuid id PK
uuid user_id FK
string title
text content
string_array tags
enum source "model_source"
string model_name "null"
jsonb model_metadata
datetime created_at
datetime updated_at
datetime deleted_at "null"
}

chat_sessions {
uuid id PK
uuid user_id FK
string title
enum generation_status "chat_session_generation_status"
uuid generation_id "null"
datetime generation_started_at "null"
datetime created_at
datetime updated_at
datetime deleted_at "null"
}

messages {
uuid id PK
uuid session_id FK
text content
enum role "message_role"
string provider "null"
string model_name "null"
int prompt_tokens "null"
int completion_tokens "null"
int total_tokens "null"
datetime created_at
datetime updated_at
datetime deleted_at "null"
}

chat_memories {
uuid id PK
uuid session_id FK,UK
text summary
jsonb facts
bool is_summarizing
uuid last_summarized_message_id FK "null"
datetime created_at
datetime updated_at
}

generation_jobs {
uuid id PK
uuid user_id FK
uuid session_id FK
enum status "generation_job_status"
text input_message
uuid output_message_id FK "null"
text error "null"
datetime started_at "null"
datetime finished_at "null"
datetime created_at
datetime updated_at
}

documents {
uuid id PK
uuid user_id FK
uuid session_id FK
string filename
string content_type
int file_size
string checksum_sha256
string storage_bucket
string storage_object_name
enum status "document_status"
text error_message "null"
datetime created_at
datetime updated_at
datetime deleted_at "null"
}

document_chunks {
uuid id PK
uuid user_id FK
uuid session_id FK
uuid document_id FK
int chunk_index
text content
string content_hash
vector embedding
string embedding_model
int token_count "null"
datetime created_at
datetime updated_at
datetime deleted_at "null"
}

rag_queries {
uuid id PK
uuid user_id FK
uuid session_id FK
text question
text answer "null"
string provider "null"
string model "null"
int prompt_tokens "null"
int completion_tokens "null"
int total_tokens "null"
int top_k
enum status "rag_query_status"
datetime finished_at "null"
text error_message "null"
datetime created_at
datetime updated_at
}

rag_query_sources {
uuid id PK
uuid rag_query_id FK
uuid document_id FK
uuid chunk_id FK
float score
int rank
text content_preview
datetime created_at
datetime updated_at
}

users ||--o{ notes : owns
users ||--o{ chat_sessions : owns
users ||--o{ generation_jobs : owns
users ||--o{ documents : owns
users ||--o{ document_chunks : owns
users ||--o{ rag_queries : owns

chat_sessions ||--o{ messages : contains
chat_sessions ||--o| chat_memories : has
chat_sessions ||--o{ generation_jobs : contains
chat_sessions ||--o{ documents : contains
chat_sessions ||--o{ document_chunks : contains
chat_sessions ||--o{ rag_queries : contains

messages ||--o| generation_jobs : "output of"
messages ||--o| chat_memories : "last summarized"

documents ||--o{ document_chunks : "split into"
documents ||--o{ rag_query_sources : "referenced by"
document_chunks ||--o{ rag_query_sources : "referenced by"
rag_queries ||--o{ rag_query_sources : "retrieved"
```

## 🛠 Database migrations

* Create a new Alembic revision:
Expand Down
32 changes: 32 additions & 0 deletions alembic/versions/239f767ca4fd_add_document_chunk_table.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""'Add document_chunk table'

Revision ID: 239f767ca4fd
Revises: 692926ebb7e2
Create Date: 2026-06-23 05:44:02.407327

"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision: str = '239f767ca4fd'
down_revision: Union[str, Sequence[str], None] = '692926ebb7e2'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###


def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"""'Add document_processing_jobs table'

Revision ID: 48add2a2bda8
Revises: e495a670b858
Create Date: 2026-06-23 07:33:08.783932

"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision: str = '48add2a2bda8'
down_revision: Union[str, Sequence[str], None] = 'e495a670b858'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('document_processing_jobs',
sa.Column('id', sa.Uuid(), nullable=False),
sa.Column('document_id', sa.Uuid(), nullable=False),
sa.Column('status', sa.Enum('queued', 'running', 'completed', 'failed', name='document_processing_job_status'), nullable=False),
sa.Column('started_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('finished_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('error', sa.Text(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(['document_id'], ['documents.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_document_processing_jobs_document_id'), 'document_processing_jobs', ['document_id'], unique=False)
# ### end Alembic commands ###


def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f('ix_document_processing_jobs_document_id'), table_name='document_processing_jobs')
op.drop_table('document_processing_jobs')
# ### end Alembic commands ###
32 changes: 32 additions & 0 deletions alembic/versions/5c4081c49d7e_remove_default_none.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""'Remove default=None'

Revision ID: 5c4081c49d7e
Revises: 239f767ca4fd
Create Date: 2026-06-23 05:45:13.909308

"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision: str = '5c4081c49d7e'
down_revision: Union[str, Sequence[str], None] = '239f767ca4fd'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###


def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###
Loading
Loading