Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
50 changes: 50 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# --- Stage 1: Build ---
FROM python:3.12-slim AS builder

ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PYTHONPATH=/app/src \
PORT=8000

WORKDIR /app

RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
libpq-dev \
git \
&& rm -rf /var/lib/apt/lists/*

# Clone SDK sibling repo into WORKDIR so ../chatbot-plugin-sdk resolves
RUN git clone https://github.com/Teng91/chatbot-plugin-sdk.git /chatbot-plugin-sdk

COPY --from=ghcr.io/astral-sh/uv:0.10.12 /uv /usr/local/bin/uv

COPY pyproject.toml uv.lock ./
RUN uv sync --frozen --no-dev

# --- Stage 2: Run ---
FROM python:3.12-slim

ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PYTHONPATH=/app/src \
PORT=8000

WORKDIR /app

COPY --from=builder /app/.venv /app/.venv

# Editable install needs the source checked out at the sibling path
COPY --from=builder /chatbot-plugin-sdk /chatbot-plugin-sdk

COPY src/ ./src/
COPY alembic/ ./alembic/
COPY alembic.ini .

RUN addgroup --system appuser && adduser --system --group appuser
RUN chown -R appuser:appuser /app
USER appuser

EXPOSE 8000

CMD ["sh", "-c", ".venv/bin/uvicorn chatbot_plugin.main:app --host 0.0.0.0 --port ${PORT:-8000}"]
2 changes: 1 addition & 1 deletion alembic/versions/002_toolbox_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def upgrade() -> None:
)

# Cast dense_vector to proper vector type
op.execute("ALTER TABLE article_chunks ALTER COLUMN dense_vector TYPE vector(1024) USING dense_vector::vector")
op.execute("ALTER TABLE article_chunks ALTER COLUMN dense_vector TYPE vector(768) USING dense_vector::vector")
# Cast sparse_vector to proper sparsevec type
op.execute("ALTER TABLE article_chunks ALTER COLUMN sparse_vector TYPE sparsevec(250002) USING sparse_vector::sparsevec")

Expand Down
38 changes: 38 additions & 0 deletions scripts/query.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""Minimal query: search DB directly with a fixed query vector."""
import asyncio
from sqlalchemy import select, text
from sqlalchemy.ext.asyncio import create_async_engine

ENGINE = create_async_engine(
"postgresql+asyncpg://postgres:postgres@localhost:5432/chatbot_plugin",
echo=False,
)
QUERY_VEC = [1.0] + [0.0] * 767 # should match v0 most closely

async def main():
from chatbot_plugin_sdk.models import Article, ArticleChunk

async with ENGINE.begin() as conn:
await conn.execute(text("CREATE EXTENSION IF NOT EXISTS vector"))

async with ENGINE.connect() as conn:
stmt = (
select(
ArticleChunk.id,
ArticleChunk.chunk_index,
ArticleChunk.content,
Article.title,
Article.url,
ArticleChunk.dense_vector.cosine_distance(QUERY_VEC).label("dist"),
)
.join(Article, ArticleChunk.article_id == Article.id)
.order_by(ArticleChunk.dense_vector.cosine_distance(QUERY_VEC))
.limit(3)
)
rows = (await conn.execute(stmt)).all()

print("=== query vector = [1.0, 0, 0, ...] ===")
for r in rows:
print(f" chunk {r.chunk_index}: dist={r.dist:.4f} | {r.content[:40]}")

asyncio.run(main())
32 changes: 32 additions & 0 deletions scripts/seed.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""Minimal seed: insert test article + chunks directly via SDK."""
import asyncio
import uuid
from chatbot_plugin_sdk import RagArticleProcessor

article_id = uuid.UUID("11111111-1111-1111-1111-111111111111")

# 768-dim vectors with intentional similarity pattern
v0 = [1.0] + [0.0] * 767 # article chunk 0
v1 = [0.9] + [0.1] * 767 # article chunk 1
v2 = [0.0] * 768 # noise chunk

async def main():
p = RagArticleProcessor()
p.configure(dbname="chatbot_plugin", user="postgres", password="postgres")
await p._save_article_and_chunks(
article_id=article_id,
metadata={
"url": "https://example.com/rag-intro",
"title": "RAG 介紹",
"source": "example.com",
"metadata": {"author": "test", "tags": ["RAG"]},
},
chunks_data=[
{"chunk_index": 0, "content": "RAG 是一種結合檢索與生成的技術。", "dense_vector": v0},
{"chunk_index": 1, "content": "它透過外部知識庫增強語言模型的回答品質。", "dense_vector": v1},
{"chunk_index": 2, "content": "這是一句不相關的填充文字。", "dense_vector": v2},
],
)
print("Inserted 1 article + 3 chunks.")

asyncio.run(main())
Loading