Галоўная / Артыкулы / Практычныя прыемкі: PostgreSQL + pgvector і SQL Server 2025 як сховышчы вектараў

Практычныя прыемкі: PostgreSQL + pgvector і SQL Server 2025 як сховышчы вектараў

Практычныя нарады: PostgreSQL + pgvector і SQL Server 2025 як сховішчы вектарных дадзеных: контракты, перакрыцця і готовы фрагменты коду для команд, якія викорыстоўваюць гэты патерн.

4064 слоў

Наступныя прытамлівкі паказваюць практычны падход да кнігі «PostgreSQL + pgvector і SQL Server 2025 як сховішчы вектараў для RAG — Практычныя наставы». Акцэнт ставіцца на умовы, пераконтроўванні і месца для коду, які можна легка адразу застосавіць, а не на мотывацыйныя аспекты.

Ландшафт баз дадзеных вектароў у 2025 году

Калі працюеце над этапам «Ландшафт баз дадзеных вектароў», спачатку запісайце умовы: неабходныя вхідныя даны, сігнал успеху і тое, што выходзіць у разе частковай нявыполнення. Такі список дапамагае заставіць пазнейшыя змены коду быць чыстымі. Расследзіце гэты этап як угоду межа вхіднымі данымі і перакантроўванымі выходнымі рэзультатамі. Дайце назвы элементам, задаць критэрыя успеху і не падпішыцеся на тыхое частковае завершэння. Замерьце рівень вярнага аднаходжэння на фіксаванай сэтцы запытаў прычым регулюванні прамптав. Частае змены прамптав рэдка калі вярнуць слабыя рэзультаты аднаходжэння.

-- Find the most relevant chunks, but only from documents
-- belonging to enterprise-tier customers — a single SQL query
SELECT c.content, 1 - (c.embedding <=> query_vec) AS score
FROM rag_chunks c
JOIN documents d ON d.filename = c.source
JOIN customers cu ON cu.id = d.customer_id
WHERE cu.tier = 'enterprise'
ORDER BY score DESC
LIMIT 5;

Полны стак

Калі працюеце над стадзіяй The Full Stack, спачатку запісайце контракт: неабяжлівыя даны, сігнал успеху і тое, што выходзіць у разе частковага нявыпання. Такі список пераконвае ў тым, што пазнейшыя змены коду будуць чыстымі. Запісвайце час выканання і кост токена або запыту праза функцыйнальнымі рэзултатамі. Відразы коста з самага пачатку запобегае неспакоўным рахункам, калі працэс пераходзіць з дэмавайнтару ў спяльныя среды. Замерьце рэткі адзыв на фіксаваныя наборы запытаў прычым рэгулюванні прапазаў. Частыя змены прапазаў рэдка калі вядуць да павышэння якасці адзыва.

Частка 1 — PostgreSQL 18 + pgvector

Калі працуеце над першым этапам PostgreSQL 18, спачатку запісайце умовы викорыстання: неабяжлівыя даны, сігнал успеху і тое, што выходзіць пад частыя неудачы. Такі список дапамагае заліцварваць будучыя змены ў кодзе. Храніце настройкі парадульна ад коду прыемліка. Файлы сераўнавання, хранальнікі секрэтных данных і флагі функцыйяў должны знаходзіцца ў аднам месцы, куды аператары можаць пераглядаць іх без неабяжлівага чытання всей структуры. Перад налаштаваннем запитоў пераканайцеся, што система правільна адзыходзіць ад заданых пытанняў, вымерваючы рэгрэсію. Частае змены запитоў рэдка калі выправляюць слабыя аспекты пошуку.

Інсталяцыя pgvector на Windows

Калі працюеце над этапам з інсталяцыі pgvector на Windows, спачатку запісайце шэраг правіла: неабяжлівыя данні, сігнал успеху і тое, што выходзіць у разе частковага нявыпання. Такі шэраг дапамагае заліцьварыць пазнейшыя змены ў кодзе. Дакументавайце як правільны, так і альтернатыўны шляхы роботы. Практыка перапрыбуткі, людзкі контроль і обработка некоректных паведамленняў є часткай продукту, а не элементамі пазнейшага доўрабкі. Перад налаштаваннем запитоў пераканайцеся, што система правільна адпавядае на фіксованы набор запитанняў. Частае зміненне запитоў рэдка калі вярна выправляе слабыя аспекты адзысквання інформаціі.

set "PGROOT=C:\Program Files\PostgreSQL\18"
cd %TEMP%
git clone --branch v0.8.0 https://github.com/pgvector/pgvector.git
cd pgvector
nmake /F Makefile.win
nmake /F Makefile.win install
docker run -d -p 5432:5432 -e POSTGRES_PASSWORD=postgres --name pgvector pgvector/pgvector:pg18
conda install -c conda-forge pgvector

Стварэнне таблиц у pgAdmin

Калі працуеце над этапам «Стварэнне таблыцаў» у pgAdmin, спачатку запісайце умовы вярбавання: неабходныя данні, сігнал успеху і тое, што выходзіць на частым неудачам. Такі список дапамагае заліцварыць пазнейшыя змены ў кодзе. Валіце маленькія, тэставаныя елементы замест большых скрыптов. Калі якісь крок не выходзіць, неудача должна адносіцца да адной відпаведальнасці, а не да заплутанага ланца задач. Перад налаштаваннем запитоў пераканайцеся, наскількі эфектыва ўраджэнне адпаведных адказоў на фіксованыя запитанні. Частае змены запитоў рэдка калі вярнуе хорашую якасць адказоў. Калі працуеце над этапам «Стварэнне таблыцаў» у pgAdmin, спачатку запісайце умовы вярбавання: неабходныя данні, сігнал успеху і тое, што выходзіць на частым неудачам. Такі список дапамагае заліцварыць пазнейшыя змены ў кодзе. Запісвайце час выканання і кост токенаў або запытаў разам з функцыйнальнымі рэзултатамі. Відразувыя данні пра косты запобегаюць неспакойным рахункам, калі праця пераходзіць з дэмавайнага режыма ў спяльныя среды.

-- Enable the pgvector extension
CREATE EXTENSION IF NOT EXISTS vector;

-- Main chunks table with VECTOR(768) column
CREATE TABLE IF NOT EXISTS rag_chunks (
    id           UUID        PRIMARY KEY DEFAULT gen_random_uuid(),
    source       TEXT        NOT NULL,
    chunk_index  INTEGER     NOT NULL,
    content      TEXT        NOT NULL,
    file_hash    TEXT,
    ingested_at  TIMESTAMPTZ DEFAULT NOW(),
    embedding    vector(768)  -- pgvector native type
);

-- HNSW index for approximate cosine similarity search
CREATE INDEX IF NOT EXISTS idx_rag_chunks_embedding
    ON rag_chunks USING hnsw (embedding vector_cosine_ops);

-- Source filter index
CREATE INDEX IF NOT EXISTS idx_rag_chunks_source
    ON rag_chunks (source);

-- Staleness registry
CREATE TABLE IF NOT EXISTS rag_staleness (
    doc_name    TEXT        PRIMARY KEY,
    file_hash   TEXT        NOT NULL,
    chunk_count INTEGER,
    ingested_at TIMESTAMPTZ DEFAULT NOW(),
    version     INTEGER     DEFAULT 1
);

-- CDC chunk registry
CREATE TABLE IF NOT EXISTS rag_chunk_registry (
    doc_name    TEXT NOT NULL,
    chunk_hash  TEXT NOT NULL,
    chunk_id    TEXT NOT NULL,
    PRIMARY KEY (doc_name, chunk_hash)
);

-- Conversation sessions
CREATE TABLE IF NOT EXISTS rag_sessions (
    session_id  TEXT        PRIMARY KEY,
    created_at  TIMESTAMPTZ DEFAULT NOW(),
    updated_at  TIMESTAMPTZ DEFAULT NOW(),
    model       TEXT,
    embed_model TEXT,
    turn_count  INTEGER     DEFAULT 0
);

-- Conversation turns
CREATE TABLE IF NOT EXISTS rag_turns (
    id          SERIAL      PRIMARY KEY,
    session_id  TEXT        REFERENCES rag_sessions(session_id) ON DELETE CASCADE,
    role        TEXT        NOT NULL CHECK (role IN ('user','assistant')),
    content     TEXT        NOT NULL,
    sources     TEXT[],
    created_at  TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS idx_rag_turns_session
    ON rag_turns (session_id, created_at);

Залежнасці Python

Этап залежнасцяў Python работае наякша, калі яго розглядаць як мерыябельную плошчу. Запісаць адны ідеальны прыклад роботы, адзін прыклад неудачы і прыметку па абратанню змян перш чым расширваць сферу дзеяння. Канфігурацыю трэба заставіць пазначкай за межамі коду прыемліка. Файлы сяродавішча, хранільнікі секрэтных дадзеных і флагі функцыйяў должны знаходзіцца ў аднам месцы, куды аператары можаць адбавляць контроль без неабяжнага чытання всіх элементаў. Зафіксаваць версію інтэрпретара і файл з правіламі залежнасцяў перш чым пачынать розбіраць циклы. Разніця межаў лептопа і среды CI ёсць найчастэйшай прычыной тых, чаму дэманстраціі API не працуюць.

uv add google-genai pypdf pgvector psycopg2-binary python-dotenv huggingface_hub

Наладжэнне з’ѐеднання (Клетка 2)

Этап «Стварэнне з’язку, клетка 2» працюе наяўней, калі яго спрыяваць як мерыемую паверхню. Зафіксавайце адна ідеальная транскрыпцыю, адзін прыклад неудачы і запіс пра вярнэнне да пачатковага стану перш чым расширваць масштабы. Дакументавайце як успішны, так і вярнучыся шляхы. Перапрыбуткі, людзкія контрольныя пункты і обработка некоректных паведамленняў є частью продукту, а не наступным этапам дорабкі. Раздзеліце політыку частковай обработкі дадзеных ад політыки ўтрымання іх. Змена адной з яных не должна вымагаць перапісвання другой, калі зменяюцыся показнікі якосці.

import psycopg2
from pgvector.psycopg2 import register_vector

PG_HOST     = "localhost"
PG_PORT     = 5432
PG_DB       = "postgres"
PG_USER     = "postgres"
PG_PASSWORD = os.environ.get("PG_PASSWORD", "postgres")

def get_pg_conn():
    """Returns a fresh PostgreSQL connection with pgvector registered."""
    conn = psycopg2.connect(
        host=PG_HOST, port=PG_PORT,
        dbname=PG_DB, user=PG_USER, password=PG_PASSWORD
    )
    register_vector(conn)   # tells psycopg2 how to handle vector type
    return conn

Зберагчыце інтеграцыі (Клетка 6)

Этап 6 клеткі «Зберагчык эмбеддзінга» працюе наявнасць калі яго расследжваць як меравальную паверхню. Запісаце адна ідеальная транскрыпцыя, адзін прыклад неудачы і прыметку па адвярненні роботы перш чым расширваць масштаб. Воліце маленькія, тэставальныя елементы замест большых скрыптов. Калі якісь крок не выйшае, прычына неудачы павінна вказваць на адную адпаведальнасць, а не на заплутаны ланцужок задач. Раздзеляйце правілы часткавання дадзеных і правілы ўтрымання іх. Змена адных не павінна вымагаць перапісвання іншых, калі змянююцца паказнікі якосці.

import numpy as np

def store_in_postgres(chunks, embeddings, doc_name) -> int:
    conn = get_pg_conn()
    cur  = conn.cursor()
    for i, (chunk, emb) in enumerate(zip(chunks, embeddings)):
        cur.execute("""
            INSERT INTO rag_chunks (source, chunk_index, content, embedding)
            VALUES (%s, %s, %s, %s)
        """, (doc_name, i, chunk, np.array(emb)))  # np.array → pgvector handles serialization
    conn.commit()
    conn.close()
    return len(chunks)

Этап 6 клеткі «Зберагчык эмбеддзінга» працюе наявнасць калі яго расследжваць як меравальную паверхню. Запісаце адна ідеальная транскрыпцыя, адзін прыклад неудачы і прыметку па адвярненні роботы перш чым расширваць масштаб. Запісвайце часы выконання і кост токеноў або запитаў разам з функцыональнымі рэзультатамі. Відразлівасць костаў з самага пачатку запобегае неспакойным рахункам, калі процес пераходзіць з дэмовай среды ў спяльныя сераўы.

Утрыманне дадзеных за дапамою косінай сэмплярнасці (Клетка 6, продаж)

Для стадіі адаптавання за допамогай косайн-сэмілярнасці неабходна прадзеяванне вхідных дадзеных, апаведчэнне адпавядаючага апарату і вакладкі завершэння працы перад змянайом коду. Апараты должны магчымаць перапрацоўкю этапу з вядомага пункту контролю, не прыпускаючы невідомых станоў. Канфігурацыю трэба захаваць праз ваняродны код аплікацыі. Файлы сераўіса, хранільнікі секрэтных дадзеных і флагі функцыйяў должны знаходзіцца ў аднам месцы, якое апараты можаць пераглядаць, не чытаючы весь граф. Неабходна апаведчаць тыя часткі тексту, якія фактычна лежалі в основе адпаведзення. Без цых апаведчэнняў апараты не можаць разлічыць галюцинацію ад працягу індэксавання.

def retrieve_context(query: str) -> list[dict]:
    query_embedding = embed_query(query)
    conn = get_pg_conn()
    cur  = conn.cursor()

    cur.execute("""
        SELECT
            content,
            source,
            chunk_index,
            ingested_at,
            1 - (embedding <=> %s) AS cosine_score  -- <=> is cosine distance
        FROM rag_chunks
        ORDER BY embedding <=> %s                    -- sort ascending (smallest distance first)
        LIMIT %s
    """, (np.array(query_embedding), np.array(query_embedding), TOP_K))
    rows = cur.fetchall()
    conn.close()
    return [{"text": r[0], "source": r[1], "chunk_index": r[2],
             "ingested_at": str(r[3]) if r[3] else "",
             "score": round(float(r[4]), 4)} for r in rows]

CDC з pgvector — шаблон Upsert

Для CDC з фазай Upsert пакета pgvector неабяжна ўзначыць вхідныя даны, адпаведальнага за выкананне крока і критэрыя завершэння пры зміне коду. Аператары должны магчымаць перзапуск крока з вядомай точкі контролю, не падозрываючы прыхованы стан. Неабяжна задокументаваць як шлях успеху, так і шлях вярнення да нормальнага стану. Перапрыбуткі, людзкія перакрыцця і обробка некоректных паведамленняў є частью продукту, а не дадатковымі удосконаленнямі. Паказваць часткі тексту, якія фактычна лежаць у падставе адпаведнай адказы. Без ціх цитатаў аператары не можуць розразліці галюцинацію ад прычыны, зв’язанай з індексаванням.

cur.execute("""
    INSERT INTO rag_staleness (doc_name, file_hash, chunk_count, version)
    VALUES (%s, %s, %s, 1)
    ON CONFLICT (doc_name) DO UPDATE SET
        file_hash   = EXCLUDED.file_hash,
        chunk_count = EXCLUDED.chunk_count,
        ingested_at = NOW(),
        version     = rag_staleness.version + 1;
""", (doc_name, file_hash, chunk_count))

Частка 2 — SQL Server 2025 (Native Vector)

Для другага этапа SQL Server неабяжна ўзначыць вхідныя даны, адпаведальнага за крок і критэрыя завершэння пры зміне коду. Аператары должны магчыма ўвайсці крок з вядомага пункта контролю, не спрабоўваючы з’ясаваць захаваны стан. Лепш выбіраць маленькія, тэставаныя елементы замест большых скрыптов. Калі крок не выйшоў, прычына нехарактэрнага результата должна вказываць на адзін конкрэтны элемент, а не на заплутаны ланцуг задач. Наводзіце тыя часткі тексту, якія фактычна лежаць у падставе адпаведнай адказы. Без ціх цитатаў аператары не зможаць розразліць галюцинацыю ад працягу індексавання.

Чаму SQL Server 2025 не патрабуе расшырэнняў

Для стадіі «Чаму SQL Server 2025» неабяжна ўзначыць вхідныя даны, адпаведальнага за крок і крэтыяры завершэння пры зміне коду. Аперацыйныя працавнікі должны магчымае запускаць крок з вядомай точкі контролю, не падозрываючы прыхованы стан. Спрыяйце цій стадіі як даговору межа вхіднымі данымі і перакананымі выходнымі рэзультатамі. Даўце назвы артыфактам, узначыць перакананні успеху і адмовіцеся ад тыхняга частковага завершэння без паведамлення. Цітуйце тыя часткі, якія фактычна сталі падставай для адпаведнай адказы. Без цітаў аперацыйныя працавнікі не зможуць адразніць галюцинацыю ад прасоўкі індэксаў.

Настройка ў SSMS

У стадії налаштавання ў SSMS неабходна пазначыць параметры вхідных дадзеных, адпаведальнага за выкананне крока і критэрыя завершэння пры перадзеіснавленні коду. Аператары должны магчымае перадзеіснавляць крок з вядомага пункта контролю, не прыпускаючы стану, які залишаецца незрозумелым. Запісваюць час выканання і вартасць токена або запыту разам з функцыйнальнымі рэзултатамі. Відразы вартасці з самага пачатку запобегае неспакоўным рахункам, калі процес пераходзіць з дэмовай среды ў спакульную. Прыкладзіце часткі тексту, якія фактычна сталі падставай для адпаведнай адказы. Без ціх цитатаў аператары не зможаць разлічыць галюцинацію ад прасоўкі ў індэксах.

-- Batch 1: Run this first — must commit before vector objects are recognized
ALTER DATABASE SCOPED CONFIGURATION SET PREVIEW_FEATURES = ON;
-- Batch 2: Create all tables
CREATE TABLE rag_chunks (
    id           INT              IDENTITY(1,1) PRIMARY KEY CLUSTERED,
    source       NVARCHAR(500)    NOT NULL,
    chunk_index  INT              NOT NULL,
    content      NVARCHAR(MAX)    NOT NULL,
    file_hash    NVARCHAR(64),
    ingested_at  DATETIME2        DEFAULT GETUTCDATE(),
    embedding    VECTOR(768)      -- native SQL Server 2025 type
);

CREATE INDEX idx_rag_source ON rag_chunks(source);

CREATE TABLE rag_staleness (
    doc_name    NVARCHAR(500)   PRIMARY KEY,
    file_hash   NVARCHAR(64)    NOT NULL,
    chunk_count INT,
    ingested_at DATETIME2       DEFAULT GETUTCDATE(),
    version     INT             DEFAULT 1
);

CREATE TABLE rag_chunk_registry (
    doc_name    NVARCHAR(500)   NOT NULL,
    chunk_hash  NVARCHAR(64)    NOT NULL,
    chunk_id    NVARCHAR(64)    NOT NULL,
    PRIMARY KEY (doc_name, chunk_hash)
);

CREATE TABLE rag_sessions (
    session_id  NVARCHAR(100)   PRIMARY KEY,
    created_at  DATETIME2       DEFAULT GETUTCDATE(),
    updated_at  DATETIME2       DEFAULT GETUTCDATE(),
    model       NVARCHAR(200),
    embed_model NVARCHAR(200),
    turn_count  INT             DEFAULT 0
);

CREATE TABLE rag_turns (
    id          INT             IDENTITY(1,1) PRIMARY KEY,
    session_id  NVARCHAR(100)   NOT NULL REFERENCES rag_sessions(session_id),
    role        NVARCHAR(20)    NOT NULL CHECK (role IN ('user','assistant')),
    content     NVARCHAR(MAX)   NOT NULL,
    sources     NVARCHAR(MAX),
    created_at  DATETIME2       DEFAULT GETUTCDATE()
);

CREATE INDEX idx_rag_turns_session ON rag_turns(session_id, created_at);
-- Batch 3: Must run AFTER Batch 2 commits
-- Cannot run inside a transaction — this is a known SQL Server 2025 preview constraint
CREATE VECTOR INDEX idx_rag_embedding
    ON rag_chunks(embedding)
    WITH (METRIC = 'COSINE');

Залежнасці Python

Для стадіі залежнасцей Python неабяжна практычна вказаць на вхідныя даны, адміністратара крока і критэрыя завершэння пры зміне коду. Аператары должны магчымаць перзапуск крока з вядомай точкі контролю, не падозрываючы схованы стан. Конфігурацыю трэба захаваць пазнаходзіцца за межамы коду прыемліка. Файлы сераўнавання, хранільнікі секрэтных дадзеных і флагі функцый належаць у аднам месца, якое аператары можаць пераглядаць без неабяжнага чытання всіх элементаў системы. Трэба аддзеліць стварэнне кліента ад цыклу перадачы паведамленняў, каб было можна змяніць прадаўцоў без перапісвання машыны стану дыялогу.

uv add google-genai pypdf pyodbc python-dotenv huggingface_hub fpdf2

Установка з’ўязку (Клетка 2)

Для стадіі «Настройка з’єднання, клетка 2» неабяцо пазначыць вхідныя даны, адміністратара крока і критэрыя завершэння пры перадзеіснаванні коду. Аператары должны магчымаць перзапуск крока з вядомай точкі контролю, не падозрываючы схованы стан. Неабяцо задокументаваць як шлях успеху, так і шлях вярнення. Перапрыбуткі, людзкіе перакрыцця і обробка некоректных паведамленняў є часткай продукту, а не наступным этапам дапрацоўкі. Прыцягніце цитаты з тых частак, якія фактычна лежалі в основе адпаведнай адказу. Без цитатаў аператары не зможаць разлічыць галюцинацію ад прасоў у індэксаванні.

import pyodbc

SQL_SERVER   = "YOUR_SERVER_NAME"   # from SSMS title bar
SQL_DATABASE = "local_rag"
SQL_CONN_STR = (
    f"DRIVER={{ODBC Driver 17 for SQL Server}};"
    f"SERVER={SQL_SERVER};"
    f"DATABASE={SQL_DATABASE};"
    f"Trusted_Connection=yes;"    # Windows Authentication - no password needed
)

def get_conn():
    return pyodbc.connect(SQL_CONN_STR)

def get_conn_autocommit():
    """Required for CREATE/DROP VECTOR INDEX - cannot run inside a transaction."""
    return pyodbc.connect(SQL_CONN_STR, autocommit=True)

Зберагчыце інтеграціі (Клетка 6)

Для стадіі 6 «Зберагчэнне эмбедынгаў» неабходна перад зменай коду адзначыць вхідныя даны, адпаведальнага за крок і критэрыя завершэння. Аператары должны магчымае перайсці на выкананне кроку з вядомага пункта контролю, не спрабоўваючы здагадвацца пра схованы стан. Лепш выбіраць маленькія, тэставаныя елементы замест большых скрыптаў. Калі крок не выкананы, прычына неудачы должна вказываць на адзін конкрэтны элемент, а не на заплутаны ланцужок задач. Прытамульвайце тыя часткі тексту, якія фактычна лежаць у падставе адпаведнай адказы. Без ціх прытамульванняў аператары не зможаць розлічыць галюцинацію ад працягу ў індэксаванні. Для стадіі 6 «Зберагчэнне эмбедынгаў» неабходна перад зменай коду адзначыць вхідныя даны, адпаведальнага за крок і критэрыя завершэння. Аператары должны магчымае перайсці на выкананне кроку з вядомага пункта контролю, не спрабоўваючы здагадвацца пра схованы стан. Запісвайце час выканання і кост токеноў або запытаў разам з функцыйнальнымі рэзультатамі. Відразуваяя інформацыю пра косты, можна ухіліцца ад неспакою, калі процес пераходзіць з дэмавайнага режыма ў спяльную среду.

Указаныя пункты.

import json

def vec_to_json(embedding: list[float]) -> str:
    return json.dumps(embedding)   # '[0.12, 0.34, ...]'

def store_in_sqlserver(chunks, embeddings, doc_name) -> int:
    drop_vector_index()    # must drop before any INSERT

    conn = get_conn()
    cur  = conn.cursor()

    sql = """
        INSERT INTO rag_chunks (source, chunk_index, content, embedding)
        VALUES (?, ?, ?, CAST(? AS VECTOR(768)))
    """
    for i, (chunk, emb) in enumerate(zip(chunks, embeddings)):
        cur.setinputsizes([
            (_pyodbc.SQL_WVARCHAR, 500, 0),
            _pyodbc.SQL_INTEGER,
            (_pyodbc.SQL_WVARCHAR, 0,   0),
            (_pyodbc.SQL_VARCHAR,  0,   0),   # ← must be VARCHAR, not NTEXT
        ])
        cur.execute(sql, (doc_name, i, chunk, vec_to_json(emb)))

    conn.commit()
    conn.close()
    create_vector_index()   # recreate after all inserts
    return len(chunks)

Адзысквэнтнае выкарыстоўванне з VECTORDISTANCE (Клетка 6, продажнае)

Калі працуеце над этапам клеткі «Адзысквэнтнае выкарыстоўванне з VECTORDISTANCE», спачатку запісайце неабходныя умовы: трэбаваныя данні, сігнал успеху і тое, што выканаецца у разы ўзельнага неяксамоства. Такі чарткі спамагаюць залічыцца з пазнейшымі зменамі ў кодзе. Зберагаюце настройкі за межамі коду прыемліка. Файлы сяродавішча, хранільнікі секрэтных дадзеных і флагі функцыйяў должны знаходзіцца ў аднам месцы, куды аператары можаць пераглядаць без неабходнасці чытання всей структуры. Перад налагоджэньем запытанняў пераканайцеся ў рівні адзысквэнтнасці на фіксаванай групе запытанняў. Частае змена запытань рэдка калі выправляе слабую структуру адзысквэнтнасці.

def retrieve_context(query: str) -> list[dict]:
    query_embedding = embed_query(query)
    query_vec_json  = vec_to_json(query_embedding)

    conn = get_conn()
    cur  = conn.cursor()

    cur.setinputsizes([(_pyodbc.SQL_VARCHAR, 0, 0)])   # force VARCHAR for vector param

    cur.execute(f"""
        SELECT TOP ({TOP_K})
            content, source, chunk_index, ingested_at,
            VECTOR_DISTANCE('cosine', embedding, CAST(? AS VECTOR(768))) AS distance
        FROM rag_chunks
        ORDER BY distance ASC;
    """, (query_vec_json,))

    rows = cur.fetchall()
    conn.close()
    return [{"text": r[0], "source": r[1], "chunk_index": r[2],
             "ingested_at": str(r[3]) if r[3] else "",
             "score": round(1 - float(r[4]), 4)} for r in rows]

Прыкметы вектарных функцый SQL Server 2025 — усе, з якімі сталося сустрэча

Калі працюеце з этапам Vector у SQL Server 2025, спачатку запісайце шаблон: неабяжныя вхідныя даны, сигнал успеху і тое, што выходзіць у разе частковага невыпання. Такі список контроля дапамагае залічыць змяны ў кодзе пазнейшаю часам. Документавайце як шлях успеху, так і шлях вярнення да стану нормы. Перапрыбуткі, людзкія перекрыцці та обробка непрацюючых паведамленняў є частью продукту, а не наступным этапам дорабкі. Перад налаштаваннем запитоў пераканайцеся, насколькі эфектыва працуе система з фіксаваным наборам запитанняў. Частае зменшэнне кількасці запитоў рэдка колі вылечвае слабкую эфектывнасць пошуку.

Проблема 1 — Невядомы тип об’екта ‘VECTOR’ у запітэ CREATE

Калі працуеце над стадзіяй «Gotcha 1: Невядомы объект», спачатку запісайце умовы вярбунка: неабяжлівыя данні, сігнал успеху і тое, што выходзіць пад частковыя неудачы. Такі список контроля дапамагае заліцьварыць пазнейшыя змены ў кодзе. Валіце маленькія, тэставаныя елементы замест большых скрыптов. Калі якісь крок не выходзіць, неудача должна адносіцца да адной відпаведальнасці, а не да заплутанага ланца задач. Перад налаштаваннем запитоў пераканайцеся ў рэгулярнасці адпаведзей на фіксованы набор запитанняў. Частае змены запитоў рэдка калі вярнуе слабкую эфектыўнасць адзысквання інформаціі. Калі працуеце над стадзіяй «Gotcha 1: Невядомы объект», спачатку запісайце умовы вярбунка: неабяжлівыя данні, сігнал успеху і тое, што выходзіць пад частковыя неудачы. Такі список контроля дапамагае заліцьварыць пазнейшыя змены ў кодзе. Запісвайце час выканання і кост токеноў або запитаў разам з функцыональнымі рэзултатамі. Відразувыя данні пра косты запобегаюць неспакойным рахункам, калі праця пераходзіць з дэмаверсіі ў спакульнаныя сераўысы.

Msg 343, Level 15: Unknown object type 'VECTOR' used in CREATE, DROP, or ALTER statement.

Gotcha 2 — Ключ абсалютнай прыямоўнасці павінен быть адной 4-байтовай столбцам типу INT

Этап з ключам абсалютнай прыямоўнасці Gotcha 2 даўа найлепыя рэзультаты, калі яго спрыяглядаць як меркаваную плошчу. Зберагучы адну ідеальную копію дадзеных, адны прыклад неудачы і запіс пра вярнэнне да пачатковага стану, перш чым расширваць масштабы. Канфігурацыю трэба залічваць парадзельна ад коду прыемліка. Файлы сяродавішча, хранільнікі секрэтных дадзеных і флагі функцый должны знаходзіцца ў аднам месцы, куда аператары можуць адбавляць аудыт без неабяжнага чытання всіх дадзеных. Палітыку разбівання на часткі трэба аддзельна ад палітыкі выявлення дадзеных. Змена адной з іх не должна вымагаць перапісвання другой, калі зменяюцыся паказнікі якосці.

Msg 42217: Table must have a clustered primary key on a single 4 byte INT column to create a vector index.
-- ❌ Does not work with vector index
id  UNIQUEIDENTIFIER PRIMARY KEY DEFAULT NEWID()

-- ✅ Required
id  INT IDENTITY(1,1) PRIMARY KEY CLUSTERED

Gotcha 3 — Нельга выпрацавваць оперэйсы INSERT/DELETE/UPDATE, калі існуе вектарны індэкс

Функцыя Gotcha 3 Cannot INSERT stage працюе наўсёх краща, калі яе розглядаць як вимерную паверхню. Зафіксавайце адна ідеальная транскрыпцыю, адзін прыклад неудачы і запіс пра вярнэнне да пачатковага стану перш чым расширваць сферу дзеяння. Документавайце як шлях успеху, так і шлях вярнэння ў нормальны стан адночасна. Перапрыбуткі, людзкі контроль і обработка некоректных паведамленняў є частью продукту, а не етапамі далейшай наладкі. Раздзеліце правілы частковай обработкі дадзеных ад правіл ўтрымання іх. Змена аднаго з яных не должна прымусіваць перапісванне другога, калі змянююцыся паказателі якосці.

Msg 42231: Data modification statement failed because table 'rag_chunks' has a vector index on it.
def drop_vector_index():
    conn = get_conn_autocommit()   # autocommit required
    conn.cursor().execute("""
        IF EXISTS (
            SELECT 1 FROM sys.indexes
            WHERE name = 'idx_rag_embedding'
            AND object_id = OBJECT_ID('rag_chunks')
        )
        DROP INDEX idx_rag_embedding ON rag_chunks;
    """)
    conn.close()

def create_vector_index():
    conn = get_conn_autocommit()   # autocommit required
    conn.cursor().execute("""
        CREATE VECTOR INDEX idx_rag_embedding
            ON rag_chunks(embedding)
            WITH (METRIC = 'COSINE');
    """)
    conn.close()

Gotcha 4 — CREATE VECTOR INDEX не можа выконвацца ў межах транзакцыі

Этап Gotcha 4 CREATE VECTOR працюе найкраща, калі яго розглядаць як вимерную паверхню. Зафіксуйце адны ідеальны прыклад, адну справу з бягамі та прыметку па вярнэнню да пачатковага стану пры расшырэнні масштаба. Валіце варот на маленькія, тэставаныя элементы замест большых скрыптов. Калі якісь крок не выйшае, прычына бягу должна вказываць на адну конкрэтную адпаведальнасць, а не на заплутаны ланцюг задач. Раздзеліце правілы часткавання дадзеных ад правіл ўтрымання іх. Змена адных не должна вымагаць перапісву іншых, калі зменяюцца паказнікі якосці. Этап Gotcha 4 CREATE VECTOR працюе найкраща, калі яго розглядаць як вимерную паверхню. Зафіксуйце адны ідеальны прыклад, адну справу з бягамі та прыметку па вярнэнню да пачатковага стану пры расшырэнні масштаба. Запісвайце часы выконання та косты токенав або запитоў разам з функцыональнымі рэзультатамі. Відразлівасць костаў з самага пачатку запобегае неспакойным рахункам, калі процес пераходзіць з дэмавайнага режыма ў спяльныя сераўы.

Msg 574: CREATE VECTOR INDEX statement cannot be used inside a user transaction.
# ❌ Fails — implicit transaction
conn = pyodbc.connect(SQL_CONN_STR)
conn.cursor().execute("CREATE VECTOR INDEX ...")

# ✅ Works - no transaction wrapper
conn = pyodbc.connect(SQL_CONN_STR, autocommit=True)
conn.cursor().execute("CREATE VECTOR INDEX ...")

Gotcha 5 — Не дазволяецца явна перакладчыка з ntext у vector

Для этапу явнай перакладчыкі Gotcha 5 неабяжна праказаць вхідныя даны, адпаведальнага за шаг і крэтарыя выходу пры перадзеі коду. Аператары должны магчымаць перзапуск шагу з вядомай точкі контролю, не спрабоўваючы здагадвацца пра схованы стан. Конфігурацыю трэба залічыць праза код аплікацыі. Файлы сераўнавання, храненні секрэтных данных і флагі функций должны знаходзіцца ў аднам месцы, якое аператары можаць пераглядаць без неабяжнай чытанняў усіх элементаў структуры. Праказваць неабяжна тые часткі тексту, якія фактычна лежаць у падставе адпаведнай адказы. Без ціх цитатаў аператары не можаць разлічыць галюцинацію ад працягу індэксавання.

Msg 529: Explicit conversion from data type ntext to vector is not allowed.
cur.setinputsizes([
    (_pyodbc.SQL_WVARCHAR, 500, 0),  # source — Unicode fine
    _pyodbc.SQL_INTEGER,              # chunk_index
    (_pyodbc.SQL_WVARCHAR, 0,   0),  # content — Unicode fine
    (_pyodbc.SQL_VARCHAR,  0,   0),  # embedding ← must be ASCII VARCHAR
])
cur.execute(sql, (doc_name, i, chunk, vec_to_json(emb)))

Gotcha 6 — VECTOR_SEARCH не прымае заменнікі параметра ?

Для процэсу Gotcha 6 у VECTORSEARCH неабяжна падготовка: перш чым зменіць код, трэба задаць вхідныя даны, адпаведальнага за крок і критэрыі завершэння. Аператары должны магчымаць перзапуск кроку з вядомай точкі контролю, не падозрываючы прыхованы стан. Неабяжна задокументаваць як шлях успеху, так і шлях вярнення. Перапрыбуткі, людзкі контроль і обработка некоректных паведамленняў є частью продукту, а не яго пазнейшай дапрацоўкі. Паказваць трэба тые часткі тексту, якія фактычна лежаць у падставе адпаведнай адказы. Без ціх цитатаў аператары не зможуць розразліць галюцинацыю ад прасоўкі ў індэксаванні.

Msg 102: Incorrect syntax near '('.
-- ❌ VECTOR_SEARCH with parameter placeholder — fails
FROM VECTOR_SEARCH(
    TABLE = rag_chunks USING VECTOR INDEX idx_rag_embedding,
    SIMILAR_TO = CAST(? AS VECTOR(768)),  -- pyodbc cannot pass ? here
    ...
)

-- ✅ VECTOR_DISTANCE - fully parameterized, GA, works perfectly
SELECT TOP (5)
    content,
    VECTOR_DISTANCE('cosine', embedding, CAST(? AS VECTOR(768))) AS distance
FROM rag_chunks
ORDER BY distance ASC;

PostgreSQL проты SQL Server — паўнае порэванне

Для этапа PostgreSQL протыма SQL Server неабяжна прадзецелі, адпаведальнага за крок і крэтарыя выходу пры змены коду. Аператары должны магчымаць перзапуск крока з вядомай точкі контролю, не падозрываючы схованы стан. Лепш выбіраць маленькія, тэставаныя елементы замест большых скрыптов. Калі крок не выйшае, прычына нехаспеку должна вказваць на адзінную адпаведальнасць, а не на заплутаны процес. Наводзіце тыя часткі, якія фактычна леглі в основу адпаведнай адказы. Без ціх цитатаў аператары не зможаць разлічыць галюцинацію ад прасоўкі індэксаў.

Што дадваяць базы даных, чаго немае ChromaDB

Для стадіі «Што дадаюць обе базы даных» неабяжна практычна апранавальніка, власнікэ крока і критэрыі завершэння перад змянайом коду. Апранавальнікі должны магчымаць перзапуск крока з вядомага пункта контролю, не спрабоўваючы з’ясаваць захованы стан. Спрыятлівайце гэтую стадію як даговор межа вхіднымі данымі і пасвярджанымі выходнымі рэзультатамі. Даўце назвы артыфактам, практычная апранавальніка успеху і не прымайце часткова завершанне без падтверджэння. Указайце тыя часткі тексту, якія фактычна лежалі в основе адпаведнай адказы. Без цых цітатаў апранавальнікі не зможаць разлічыць галюцинацію ад працягу індэксавання.

-- PostgreSQL: Find chunks from documents belonging to a specific customer
SELECT c.content, 1 - (c.embedding <=> query_vec) AS score
FROM rag_chunks c
JOIN documents d ON d.filename = c.source
JOIN customers cu ON cu.id = d.customer_id
WHERE cu.tier = 'enterprise'
ORDER BY score DESC
LIMIT 5;
-- SQL Server: Same query, T-SQL syntax
SELECT TOP 5
    c.content,
    1 - VECTOR_DISTANCE('cosine', c.embedding, CAST(? AS VECTOR(768))) AS score
FROM rag_chunks c
JOIN documents d ON d.filename = c.source
JOIN customers cu ON cu.id = d.customer_id
WHERE cu.tier = 'enterprise'
ORDER BY score DESC;

Заключэнне

У стадії заканчэння неабяжна ўзначыць вхідныя даны, адпаведальнага за крок і критэрыя завершэння пры перадзеіснавленні коду. Аператары должны магчымаць перзапуск кроку з вядомай точкі контролю, не падозрываючы прыхованы стан. Запісваць час выконання і вартасць токеноў або запытак праза функцыйнае рэзультат. Відкрытая візуабілізацыя вартасцей запобегае неспакоўным рахункам, калі процес пераходзіць з дэмовай среды ў спяльнаныя сераўеры. Указваць тые часткі тексту, якія фактычна ляглі в основу адпаведнай адпаведзі. Без цых цытатаў аператары не можуць разлічыць галюцинацію ад прасоў у індэксаванні.

Чек-ліст для эксплуатацыі