From f0929d6a58e1f3cf63980498a9210f992b24281e Mon Sep 17 00:00:00 2001 From: Joy Teng Date: Fri, 12 Jun 2026 16:23:23 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20add=20seed/query=20scripts,=20Dockerfil?= =?UTF-8?q?e;=20fix=20vector=20dim=201024=E2=86=92768?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 50 ++++++++++++++++++++++++++ alembic/versions/002_toolbox_schema.py | 2 +- scripts/query.py | 38 ++++++++++++++++++++ scripts/seed.py | 32 +++++++++++++++++ 4 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 Dockerfile create mode 100644 scripts/query.py create mode 100644 scripts/seed.py diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..38d58d1 --- /dev/null +++ b/Dockerfile @@ -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}"] diff --git a/alembic/versions/002_toolbox_schema.py b/alembic/versions/002_toolbox_schema.py index fe38e55..611a60b 100644 --- a/alembic/versions/002_toolbox_schema.py +++ b/alembic/versions/002_toolbox_schema.py @@ -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") diff --git a/scripts/query.py b/scripts/query.py new file mode 100644 index 0000000..f81003c --- /dev/null +++ b/scripts/query.py @@ -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()) diff --git a/scripts/seed.py b/scripts/seed.py new file mode 100644 index 0000000..7600a60 --- /dev/null +++ b/scripts/seed.py @@ -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())