Skip to content

Vectorizer and in-database LLM calls migration guide

Plan your move off Tiger Cloud-managed vectorizer workers and in-database LLM SQL helpers before June 30, 2026. Tables, embeddings, and vectorizer definitions stay in the database.

This guide is for Tiger Cloud (AWS and Azure). It does not apply to self-hosted deployments that never used Tiger Cloud-managed vectorizer scheduling.

The following AI capabilities are deprecated on Tiger Cloud and will be removed on June 30, 2026:

  • Managed vectorizer: the Tiger Cloud-managed service that ran vectorizer workers for you. Your vectorizer definitions and embedding tables stay in place; you run the vectorizer worker yourself (see Migrate the vectorizer).
  • In-database LLM calls: SQL-callable helpers such as ai.openai_embed, ai.openai_chat_complete, ai.anthropic_generate, ai.ollama_embed, ai.cohere_embed, and related ai.* entry points. These will no longer be available from SQL.

Your data is not affected. Tables, embeddings, and vectorizer configuration remain in the database. Only Tiger Cloud-managed execution of the worker and the in-database LLM call surface are going away.

What is not changing: Semantic search with pgvector and pgvectorscale, and keyword search with BM25 via pg_textsearch, stay available on Tiger Cloud. Those are still the building blocks for hybrid search.

In pgai v0.10.0, vectorizer code moved out of the ai extension into the standalone pgai Python package. On extension versions 0.4.0 through 0.9.x, vectorizer SQL objects still belong to the extension. Upgrade the extension before you follow the worker migration below.

SELECT extversion FROM pg_extension WHERE extname = 'ai';

If the result is 0.9.x or earlier, complete the upgrade steps below. If it is 0.10.0 or later, skip to Migrate the vectorizer.

ALTER EXTENSION ai UPDATE;

This migration detaches vectorizer objects from the extension without dropping them. Definitions and data stay intact.

3. install the pgai library for ongoing vectorizer management

Section titled “3. install the pgai library for ongoing vectorizer management”

pip:

Terminal window
pip install "pgai[vectorizer-worker]"
pgai install -d "postgres://tsdbadmin:<password>@<host>:<port>/tsdb?sslmode=require"

Docker:

Terminal window
docker run --pull always --rm --entrypoint python \
timescale/pgai-vectorizer-worker:latest \
-m pgai install -d "postgres://tsdbadmin:<password>@<host>:<port>/tsdb?sslmode=require"

After these steps, continue with Migrate the vectorizer.

Connect to your database and remove the cloud scheduler from your vectorizers. This deletes the TimescaleDB background jobs and sets scheduling to none, while leaving vectorizers enabled so your self-hosted worker can process them:

-- Delete the TimescaleDB scheduled jobs
SELECT public.delete_job((config->'scheduling'->>'job_id')::int)
FROM ai.vectorizer
WHERE config->'scheduling'->>'implementation' = 'timescaledb';
-- Switch scheduling to none
UPDATE ai.vectorizer
SET config = jsonb_set(config, '{scheduling}', '{"config_type": "scheduling", "implementation": "none"}'::jsonb)
WHERE config->'scheduling'->>'implementation' = 'timescaledb';

Copy your Tiger Cloud connection string from Tiger Console. It looks like:

postgres://tsdbadmin:<password>@<host>:<port>/tsdb?sslmode=require

Pick one of the following.

Create a .env file with your API keys:

OPENAI_API_KEY=sk-your-openai-api-key

Run the worker:

Terminal window
docker run \
--env-file .env \
timescale/pgai-vectorizer-worker:latest \
--db-url "postgres://tsdbadmin:<password>@<host>:<port>/tsdb?sslmode=require" \
--poll-interval 5m \
-c 4
name: pgai-vectorizer
services:
vectorizer-worker:
image: timescale/pgai-vectorizer-worker:latest
environment:
PGAI_VECTORIZER_WORKER_DB_URL: "postgres://tsdbadmin:<password>@<host>:<port>/tsdb?sslmode=require"
OPENAI_API_KEY: "sk-your-openai-api-key"
command: ["--poll-interval", "5m", "-c", "4"]
restart: unless-stopped

Start it:

Terminal window
docker compose up -d
Terminal window
pip install pgai[vectorizer-worker]
Terminal window
export OPENAI_API_KEY=sk-your-openai-api-key
pgai vectorizer worker -d "postgres://tsdbadmin:<password>@<host>:<port>/tsdb?sslmode=require" --poll-interval 5m -c 4
import asyncio
from datetime import timedelta
from pgai import Worker
worker = Worker(
db_url="postgres://tsdbadmin:<password>@<host>:<port>/tsdb?sslmode=require",
poll_interval=timedelta(minutes=5),
concurrency=4,
)
asyncio.run(worker.run())

Full worker options: pgai vectorizer worker documentation.

Move provider calls into application code. Keep using PostgreSQL for storage and vector search; generate embeddings and completions outside the database, then pass vectors or text into SQL as parameters.

Before (embedding inside the database):

SELECT id, content
FROM documents
ORDER BY embedding <=> ai.openai_embed('text-embedding-3-small', 'search query')
LIMIT 5;

After (embedding in Python, vector as a query parameter):

import openai
import psycopg2
client = openai.OpenAI() # uses OPENAI_API_KEY env var
def semantic_search(query: str, limit: int = 5):
response = client.embeddings.create(
model="text-embedding-3-small",
input=query,
)
embedding = response.data[0].embedding
conn = psycopg2.connect("postgres://tsdbadmin:<password>@<host>:<port>/tsdb?sslmode=require")
cur = conn.cursor()
cur.execute(
"""
SELECT id, content
FROM documents
ORDER BY embedding <=> %s::vector
LIMIT %s
""",
(embedding, limit),
)
return cur.fetchall()

With asyncpg, bind the embedding the way your driver and pgvector build expect (for example a string literal for vector or a supported sequence type):

import openai
import asyncpg
client = openai.OpenAI()
async def semantic_search(query: str, limit: int = 5):
response = client.embeddings.create(
model="text-embedding-3-small",
input=query,
)
embedding = response.data[0].embedding
conn = await asyncpg.connect("postgres://tsdbadmin:<password>@<host>:<port>/tsdb?sslmode=require")
rows = await conn.fetch(
"""
SELECT id, content
FROM documents
ORDER BY embedding <=> $1::vector
LIMIT $2
""",
str(embedding),
limit,
)
return rows

Before:

SELECT ai.openai_chat_complete(
'gpt-4o',
jsonb_build_array(
jsonb_build_object('role', 'user', 'content', 'Summarize this: ' || doc.content)
)
)->'choices'->0->'message'->>'content' AS summary
FROM documents doc
WHERE doc.id = 1;

After:

import openai
client = openai.OpenAI()
def summarize(content: str) -> str:
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": f"Summarize this: {content}"}],
)
return response.choices[0].message.content

Before:

SELECT ai.anthropic_generate(
'claude-sonnet-4-20250514',
jsonb_build_array(
jsonb_build_object('role', 'user', 'content', 'Explain this concept')
)
);

After:

import anthropic
client = anthropic.Anthropic()
def generate(prompt: str) -> str:
message = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
messages=[{"role": "user", "content": prompt}],
)
return message.content[0].text

Before:

SELECT ai.cohere_rerank(
'rerank-english-v3.0',
'search query',
jsonb_agg(content)
)
FROM documents
LIMIT 100;

After:

import cohere
client = cohere.Client()
def rerank(query: str, documents: list[str]) -> list:
response = client.rerank(
model="rerank-english-v3.0",
query=query,
documents=documents,
)
return response.results
Deprecated functionReplacement
ai.openai_embed(model, text)openai.OpenAI().embeddings.create(model=model, input=text)
ai.openai_chat_complete(model, messages)openai.OpenAI().chat.completions.create(model=model, messages=messages)
ai.openai_chat_complete_simple(model, prompt)openai.OpenAI().chat.completions.create(model=model, messages=[...])
ai.openai_moderate(model, input)openai.OpenAI().moderations.create(model=model, input=input)
ai.anthropic_generate(model, messages)anthropic.Anthropic().messages.create(model=model, messages=messages)
ai.ollama_embed(model, text)ollama.embed(model=model, input=text)
ai.ollama_generate(model, prompt)ollama.generate(model=model, prompt=prompt)
ai.ollama_chat_complete(model, messages)ollama.chat(model=model, messages=messages)
ai.cohere_embed(model, text)cohere.Client().embed(model=model, texts=[text])
ai.cohere_rerank(model, query, docs)cohere.Client().rerank(model=model, query=query, documents=docs)
ai.cohere_chat_complete(model, messages)cohere.Client().chat(model=model, messages=messages)
ai.voyageai_embed(model, text)voyageai.Client().embed(texts=[text], model=model)
ai.voyageai_rerank(model, query, docs)voyageai.Client().rerank(query=query, documents=docs, model=model)

For any ai.* SQL function:

  1. Identify the provider from the name (openai_, anthropic_, ollama_, cohere_, voyageai_).
  2. Install that provider’s SDK (pip install openai, pip install anthropic, and so on).
  3. Call the SDK from your application before or after your SQL.
  4. Pass structured results as parameters (for embeddings, pass the vector into ORDER BY ... <=> $1 style queries).
TopicWhere to read
pgai SQL and vectorizer referencepgai reference
Vector search conceptsKey vector concepts for pgvector
Tiger Cloud + pgvector overviewUnderstand pgvector and pgvectorscale