Commit 860c322
feat(pgvector): migrate from psycopg2 to asyncpg (#5763)
# What does this PR do?
<!-- Provide a short summary of what this PR does and why. Link to
relevant issues if applicable. -->
**What:** Replaces `psycopg2 `(synchronous) with `asyncpg` (async) in
the PGVector vector store provider.
**Why:** psycopg2 blocks the event loop on every database call. In an
async server (uvicorn/FastAPI), this freezes ALL
request handling during DB operations. With `asyncpg`, queries yield
control via coroutines, allowing concurrent non-blocking PGVector I/O
operations.
<!-- If resolving an issue, uncomment and update the line below -->
Closes #5753
🤖 Co-Authored with [Claude Code](https://claude.com/claude-code)
## Test Plan
<!-- Describe the tests you ran to verify your changes with result
summaries. *Provide clear instructions so the plan can be easily
re-executed.* -->
### I created this bash script for testing:
```
#!/usr/bin/env bash
# End-to-end test: PGVector file search via curl against OGX server.
# Tests vector, keyword, hybrid search modes and Response API with file_search tool.
#
# This script manages the OGX server lifecycle: kills any existing server,
# clears state, starts fresh, runs tests, then cleans up.
#
# Required env vars:
# OPENAI_API_KEY - for Response API (chat completion via OpenAI)
#
# Optional env vars:
# PGVECTOR_HOST - default: localhost
# PGVECTOR_PORT - default: 5432
# PGVECTOR_DB - default: testvectordb
# PGVECTOR_USER - default: user
# PGVECTOR_PASSWORD - default: password
# OGX_BASE_URL - default: http://localhost:8321/v1
# OGX_MODEL - default: openai/gpt-5.5
# PDF_PATH - default: /Users/ianmiller/Downloads/invoicesample.pdf
#
# Usage:
# OPENAI_API_KEY=<YOUR_API_KEY> bash scripts/test_pgvector_file_search.sh
set -uo pipefail
BASE_URL="${OGX_BASE_URL:-http://localhost:8321/v1}"
PDF_PATH="${PDF_PATH:-/Users/ianmiller/Downloads/invoicesample.pdf}"
MODEL="${OGX_MODEL:-openai/gpt-5.5}"
EMBEDDING_MODEL="sentence-transformers/nomic-ai/nomic-embed-text-v1.5"
SERVER_LOG="/tmp/ogx-server-test.log"
export PGVECTOR_DB="${PGVECTOR_DB:-testvectordb}"
export PGVECTOR_HOST="${PGVECTOR_HOST:-localhost}"
export PGVECTOR_PORT="${PGVECTOR_PORT:-5432}"
export PGVECTOR_USER="${PGVECTOR_USER:-user}"
export PGVECTOR_PASSWORD="${PGVECTOR_PASSWORD:-password}"
PASS=0
FAIL=0
step() {
echo ""
echo "============================================================"
echo " $1"
echo "============================================================"
}
check_result() {
local name="$1"
local condition="$2"
if [ "$condition" = "true" ]; then
echo " -> $name: PASS"
PASS=$((PASS + 1))
else
echo " -> $name: FAIL"
FAIL=$((FAIL + 1))
fi
}
json_get() {
python3 -c "import sys,json; print(json.load(sys.stdin)$1)" 2>/dev/null
}
# ------------------------------------------------------------------
# Step A: Clean up via existing server (if running)
# ------------------------------------------------------------------
step "Cleanup: removing ALL vector stores and files via API"
if curl -s "$BASE_URL/models" 2>/dev/null | grep -q "data" 2>/dev/null; then
echo " Existing server found, cleaning up..."
# Delete ALL vector stores (not just pgvector-test — clean everything)
curl -s "$BASE_URL/vector_stores" -H "Authorization: Bearer fake" 2>/dev/null | python3 -c "
import sys, json
data = json.load(sys.stdin)
for vs in data.get('data', []):
print(vs['id'], vs.get('name', 'unnamed'))
" 2>/dev/null | while read -r vs_id vs_name; do
echo " Deleting vector store: $vs_id ($vs_name)"
curl -s -X DELETE "$BASE_URL/vector_stores/$vs_id" -H "Authorization: Bearer fake" > /dev/null
done
# Delete ALL files
curl -s "$BASE_URL/files" -H "Authorization: Bearer fake" 2>/dev/null | python3 -c "
import sys, json
data = json.load(sys.stdin)
for f in data.get('data', []):
print(f['id'], f.get('filename', 'unnamed'))
" 2>/dev/null | while read -r file_id file_name; do
echo " Deleting file: $file_id ($file_name)"
curl -s -X DELETE "$BASE_URL/files/$file_id" -H "Authorization: Bearer fake" > /dev/null
done
echo " API cleanup complete"
else
echo " No existing server running, skipping API cleanup"
fi
# ------------------------------------------------------------------
# Step B: Kill server, clear local state, start fresh
# ------------------------------------------------------------------
step "Starting fresh OGX server"
echo " Killing server..."
pkill -f "ogx run" 2>/dev/null || true
sleep 2
echo " Clearing local distribution state..."
rm -f ~/.llama/distributions/starter/kvstore.db
rm -f ~/.llama/distributions/starter/sql_store.db
echo " Starting server (log: $SERVER_LOG)..."
uv run ogx run run-pgvector-test.yaml > "$SERVER_LOG" 2>&1 &
SERVER_PID=$!
echo " Server PID: $SERVER_PID"
echo " Waiting for server..."
SERVER_READY=false
for i in $(seq 1 120); do
if curl -s "$BASE_URL/models" 2>/dev/null | grep -q "data" 2>/dev/null; then
echo " Server ready after ${i}s"
SERVER_READY=true
break
fi
sleep 1
done
if [ "$SERVER_READY" = "false" ]; then
echo " ERROR: Server failed to start. Last 20 lines of log:"
tail -20 "$SERVER_LOG"
kill $SERVER_PID 2>/dev/null || true
exit 1
fi
echo " Server running. Will stay running after tests complete."
# ------------------------------------------------------------------
# Step 1: Upload PDF file
# ------------------------------------------------------------------
step "Step 1: Upload PDF file"
if [ ! -f "$PDF_PATH" ]; then
echo " ERROR: PDF not found at $PDF_PATH"
exit 1
fi
UPLOAD_RESP=$(curl -s "$BASE_URL/files" \
-H "Authorization: Bearer fake" \
-F "file=@$PDF_PATH" \
-F "purpose=assistants")
FILE_ID=$(echo "$UPLOAD_RESP" | json_get "['id']")
FILE_NAME=$(echo "$UPLOAD_RESP" | json_get "['filename']")
FILE_SIZE=$(echo "$UPLOAD_RESP" | json_get "['bytes']")
if [ -z "$FILE_ID" ]; then
echo " ERROR: File upload failed"
echo " Response: $UPLOAD_RESP"
exit 1
fi
echo " File uploaded: $FILE_ID"
echo " Filename: $FILE_NAME"
echo " Size: $FILE_SIZE bytes"
# ------------------------------------------------------------------
# Step 2: Create vector store
# ------------------------------------------------------------------
step "Step 2: Create vector store"
VS_RESP=$(curl -s "$BASE_URL/vector_stores" \
-H "Authorization: Bearer fake" \
-H "Content-Type: application/json" \
-d "{
\"name\": \"pgvector-test-invoice\",
\"embedding_model\": \"$EMBEDDING_MODEL\",
\"embedding_dimension\": 768
}")
VS_ID=$(echo "$VS_RESP" | json_get "['id']")
VS_STATUS=$(echo "$VS_RESP" | json_get "['status']")
if [ -z "$VS_ID" ]; then
echo " ERROR: Vector store creation failed"
echo " Response: $VS_RESP"
exit 1
fi
echo " Vector store created: $VS_ID"
echo " Status: $VS_STATUS"
# ------------------------------------------------------------------
# Step 3: Attach file to vector store
# ------------------------------------------------------------------
step "Step 3: Attach file to vector store"
ATTACH_RESP=$(curl -s "$BASE_URL/vector_stores/$VS_ID/files" \
-H "Authorization: Bearer fake" \
-H "Content-Type: application/json" \
-d "{\"file_id\": \"$FILE_ID\"}")
ATTACH_STATUS=$(echo "$ATTACH_RESP" | json_get "['status']")
echo " Attachment status: $ATTACH_STATUS"
RETRIES=0
while [ "$ATTACH_STATUS" = "in_progress" ] && [ $RETRIES -lt 60 ]; do
sleep 1
RETRIES=$((RETRIES + 1))
ATTACH_RESP=$(curl -s "$BASE_URL/vector_stores/$VS_ID/files/$FILE_ID" \
-H "Authorization: Bearer fake")
ATTACH_STATUS=$(echo "$ATTACH_RESP" | json_get "['status']")
if [ $((RETRIES % 5)) -eq 0 ]; then
echo " Still processing... (${RETRIES}s)"
fi
done
echo " Final status: $ATTACH_STATUS"
if [ "$ATTACH_STATUS" != "completed" ]; then
echo " ERROR: File attachment failed"
echo " Server log tail:"
tail -20 "$SERVER_LOG"
exit 1
fi
# ------------------------------------------------------------------
# Step 4: Vector Store Search — mode=vector
# ------------------------------------------------------------------
step "Step 4: Vector Store Search — mode=vector"
VECTOR_RESP=$(curl -s "$BASE_URL/vector_stores/$VS_ID/search" \
-H "Authorization: Bearer fake" \
-H "Content-Type: application/json" \
-d '{
"query": "What invoice number belongs to Denny Gunawan?",
"max_num_results": 5,
"search_mode": "vector"
}')
VECTOR_COUNT=$(echo "$VECTOR_RESP" | python3 -c "import sys,json; print(len(json.load(sys.stdin).get('data',[])))" 2>/dev/null || echo "0")
echo " Results count: $VECTOR_COUNT"
echo "$VECTOR_RESP" | python3 -c "
import sys, json
data = json.load(sys.stdin)
for i, r in enumerate(data.get('data', [])):
score = r.get('score', 0)
text = r.get('content', [{}])[0].get('text', 'N/A')[:120]
print(f' [{i+1}] score={score:.4f} | {text}...')
" 2>/dev/null
check_result "vector_search" "$([ "$VECTOR_COUNT" -gt 0 ] && echo true || echo false)"
# ------------------------------------------------------------------
# Step 5: Vector Store Search — mode=keyword
# ------------------------------------------------------------------
step "Step 5: Vector Store Search — mode=keyword"
KEYWORD_RESP=$(curl -s "$BASE_URL/vector_stores/$VS_ID/search" \
-H "Authorization: Bearer fake" \
-H "Content-Type: application/json" \
-d '{
"query": "invoice apple total",
"max_num_results": 5,
"search_mode": "keyword"
}')
KEYWORD_COUNT=$(echo "$KEYWORD_RESP" | python3 -c "import sys,json; print(len(json.load(sys.stdin).get('data',[])))" 2>/dev/null || echo "0")
echo " Results count: $KEYWORD_COUNT"
echo "$KEYWORD_RESP" | python3 -c "
import sys, json
data = json.load(sys.stdin)
for i, r in enumerate(data.get('data', [])):
score = r.get('score', 0)
text = r.get('content', [{}])[0].get('text', 'N/A')[:120]
print(f' [{i+1}] score={score:.4f} | {text}...')
" 2>/dev/null
check_result "keyword_search" "$([ "$KEYWORD_COUNT" -gt 0 ] && echo true || echo false)"
# ------------------------------------------------------------------
# Step 6: Vector Store Search — mode=hybrid
# ------------------------------------------------------------------
step "Step 6: Vector Store Search — mode=hybrid"
HYBRID_RESP=$(curl -s "$BASE_URL/vector_stores/$VS_ID/search" \
-H "Authorization: Bearer fake" \
-H "Content-Type: application/json" \
-d '{
"query": "What invoice number belongs to Denny Gunawan?",
"max_num_results": 5,
"search_mode": "hybrid",
"ranking_options": {"ranker": "rrf"}
}')
HYBRID_COUNT=$(echo "$HYBRID_RESP" | python3 -c "import sys,json; print(len(json.load(sys.stdin).get('data',[])))" 2>/dev/null || echo "0")
echo " Results count: $HYBRID_COUNT"
echo "$HYBRID_RESP" | python3 -c "
import sys, json
data = json.load(sys.stdin)
for i, r in enumerate(data.get('data', [])):
score = r.get('score', 0)
text = r.get('content', [{}])[0].get('text', 'N/A')[:120]
print(f' [{i+1}] score={score:.4f} | {text}...')
" 2>/dev/null
check_result "hybrid_search" "$([ "$HYBRID_COUNT" -gt 0 ] && echo true || echo false)"
# ------------------------------------------------------------------
# Step 7: Response API with file_search tool
# ------------------------------------------------------------------
step "Step 7: Response API with file_search tool"
echo " NOTE: Responses API uses default_search_mode from server config."
echo " To change mode, update run-pgvector-test.yaml and restart server."
RESPONSE_RESP=$(curl -s "$BASE_URL/responses" \
-H "Authorization: Bearer fake" \
-H "Content-Type: application/json" \
-d "{
\"model\": \"$MODEL\",
\"input\": \"What invoice number belongs to Denny Gunawan?\",
\"tools\": [
{
\"type\": \"file_search\",
\"vector_store_ids\": [\"$VS_ID\"]
}
],
\"stream\": false,
\"include\": [\"file_search_call.results\"]
}")
echo ""
echo " --- Raw JSON Response ---"
echo "$RESPONSE_RESP" | python3 -m json.tool 2>/dev/null || echo "$RESPONSE_RESP"
echo " --- End Raw JSON ---"
echo ""
RESP_STATUS=$(echo "$RESPONSE_RESP" | json_get ".get('status','failed')" || echo "failed")
RESP_ID=$(echo "$RESPONSE_RESP" | json_get ".get('id','N/A')" || echo "N/A")
echo " Response ID: $RESP_ID"
echo " Status: $RESP_STATUS"
echo "$RESPONSE_RESP" | python3 -c "
import sys, json
data = json.load(sys.stdin)
for item in data.get('output', []):
if item.get('type') == 'file_search_call':
print(f\" File search call ID: {item.get('id')}\")
print(f\" Status: {item.get('status')}\")
results = item.get('results', [])
if results:
print(f' Search results: {len(results)}')
for j, r in enumerate(results[:3]):
text = (r.get('text') or 'N/A')[:100]
score = r.get('score', 0)
file_id = r.get('file_id', 'N/A')
print(f' [{j+1}] score={score:.4f} file={file_id} | {text}...')
elif item.get('type') == 'message':
for content in item.get('content', []):
if content.get('type') == 'output_text':
print(f\"\")
print(f\" LLM Response:\")
print(f\" {content['text'][:500]}\")
" 2>/dev/null
check_result "response_file_search" "$([ "$RESP_STATUS" = "completed" ] && echo true || echo false)"
# ------------------------------------------------------------------
# Results Summary
# ------------------------------------------------------------------
step "RESULTS SUMMARY"
echo " Passed: $PASS"
echo " Failed: $FAIL"
if [ $FAIL -eq 0 ]; then
echo ""
echo " All tests passed!"
exit 0
else
echo ""
echo " Some tests failed!"
echo " Server log: $SERVER_LOG"
exit 1
fi
```
### I used this custom ogx distro:
```
version: 2
distro_name: starter
apis:
- file_processors
- files
- inference
- responses
- tool_runtime
- vector_io
providers:
inference:
- provider_id: openai
provider_type: remote::openai
config:
api_key: ${env.OPENAI_API_KEY:=}
base_url: ${env.OPENAI_BASE_URL:=https://api.openai.com/v1}
- provider_id: sentence-transformers
provider_type: inline::sentence-transformers
config:
trust_remote_code: true
- provider_id: transformers
provider_type: inline::transformers
vector_io:
- provider_id: pgvector
provider_type: remote::pgvector
config:
host: ${env.PGVECTOR_HOST:=localhost}
port: ${env.PGVECTOR_PORT:=5432}
db: ${env.PGVECTOR_DB:=testvectordb}
user: ${env.PGVECTOR_USER:=user}
password: ${env.PGVECTOR_PASSWORD:=password}
distance_metric: COSINE
vector_index:
type: HNSW
m: 16
ef_construction: 64
ef_search: 40
persistence:
namespace: vector_io::pgvector
backend: kv_default
files:
- provider_id: meta-reference-files
provider_type: inline::localfs
config:
storage_dir: ${env.FILES_STORAGE_DIR:=~/.llama/distributions/starter/files}
metadata_store:
table_name: files_metadata
backend: sql_default
file_processors:
- provider_id: auto
provider_type: inline::auto
responses:
- provider_id: builtin
provider_type: inline::builtin
config:
persistence:
agent_state:
namespace: agents
backend: kv_default
responses:
table_name: responses
backend: sql_default
max_write_queue_size: 10000
num_writers: 4
tool_runtime:
- provider_id: file-search
provider_type: inline::file-search
storage:
backends:
kv_default:
type: kv_sqlite
db_path: ${env.SQLITE_STORE_DIR:=~/.llama/distributions/starter}/kvstore.db
sql_default:
type: sql_sqlite
db_path: ${env.SQLITE_STORE_DIR:=~/.llama/distributions/starter}/sql_store.db
stores:
metadata:
namespace: registry
backend: kv_default
inference:
table_name: inference_store
backend: sql_default
max_write_queue_size: 10000
num_writers: 4
conversations:
table_name: openai_conversations
backend: sql_default
prompts:
namespace: prompts
backend: kv_default
connectors:
namespace: connectors
backend: kv_default
registered_resources:
models:
- metadata: {}
model_id: gpt-5.5
provider_id: openai
provider_model_id: gpt-5.5
model_type: llm
- metadata:
embedding_dimension: 768
model_id: nomic-ai/nomic-embed-text-v1.5
provider_id: sentence-transformers
provider_model_id: nomic-ai/nomic-embed-text-v1.5
model_type: embedding
- metadata:
embedding_dimension: 768
model_id: Qwen/Qwen3-Reranker-0.6B
provider_id: transformers
provider_model_id: Qwen/Qwen3-Reranker-0.6B
model_type: rerank
vector_dbs: []
tool_groups:
- toolgroup_id: builtin::file_search
provider_id: file-search
server:
port: 8321
vector_stores:
default_provider_id: pgvector
default_embedding_model:
provider_id: sentence-transformers
model_id: nomic-ai/nomic-embed-text-v1.5
default_reranker_model:
provider_id: transformers
model_id: Qwen/Qwen3-Reranker-0.6B
file_search_params:
header_template: 'file_search tool found {num_chunks} chunks:
BEGIN of file_search tool results.
'
footer_template: 'END of file_search tool results.
'
context_prompt_params:
chunk_annotation_template: 'Result {index}
Content: {chunk.content}
Metadata: {metadata}
'
context_template: 'The above results were retrieved to help answer the user''s
query: "{query}". Use them as supporting information only in answering this
query. {annotation_instruction}
'
annotation_prompt_params:
enable_annotations: true
annotation_instruction_template: Cite sources immediately at the end of sentences
before punctuation, using `<|file-id|>` format like 'This is a fact <|file-Cn3MSNn72ENTiiq11Qda4A|>.'.
Do not add extra punctuation. Use only the file IDs provided, do not invent
new ones.
chunk_annotation_template: '[{index}] {metadata_text} cite as <|{file_id}|>
{chunk_text}
'
file_ingestion_params:
default_chunk_size_tokens: 512
default_chunk_overlap_tokens: 128
chunk_retrieval_params:
chunk_multiplier: 5
max_tokens_in_context: 4000
default_search_mode: hybrid
default_reranker_strategy: rrf
rrf_impact_factor: 60.0
weighted_search_alpha: 0.5
file_batch_params:
max_concurrent_files_per_batch: 3
file_batch_chunk_size: 10
cleanup_interval_seconds: 86400
connectors: []
```
### The output after running the script against OGX server:
```
============================================================
Cleanup: removing ALL vector stores and files via API
============================================================
Existing server found, cleaning up...
Deleting vector store: vs_bceebfbd-c41e-42a0-b32f-8abc5df607ff (pgvector-test-invoice)
Deleting file: file-56a58b08c93c44fc852fb3c2ab5aff8a (invoicesample.pdf)
API cleanup complete
============================================================
Starting fresh OGX server
============================================================
Killing server...
Clearing local distribution state...
Starting server (log: /tmp/ogx-server-test.log)...
Server PID: 40943
Waiting for server...
Server ready after 6s
Server running. Will stay running after tests complete.
============================================================
Step 1: Upload PDF file
============================================================
File uploaded: file-af746dafd6dc4b198408dc3e28052314
Filename: invoicesample.pdf
Size: 149568 bytes
============================================================
Step 2: Create vector store
============================================================
Vector store created: vs_337d1d7b-291e-4056-be9e-2ce94c12723f
Status: completed
============================================================
Step 3: Attach file to vector store
============================================================
Attachment status: completed
Final status: completed
============================================================
Step 4: Vector Store Search — mode=vector
============================================================
Results count: 1
[1] score=2.9101 | Denny Gunawan
221 Queen St
Melbourne VIC 3000
$39.60
123 Somewhere St, Melbourne VIC 3000
(03) 1234 5678
Invoice Number:...
-> vector_search: PASS
============================================================
Step 5: Vector Store Search — mode=keyword
============================================================
Results count: 1
[1] score=0.0520 | Denny Gunawan
221 Queen St
Melbourne VIC 3000
$39.60
123 Somewhere St, Melbourne VIC 3000
(03) 1234 5678
Invoice Number:...
-> keyword_search: PASS
============================================================
Step 6: Vector Store Search — mode=hybrid
============================================================
Results count: 1
[1] score=0.0164 | Denny Gunawan
221 Queen St
Melbourne VIC 3000
$39.60
123 Somewhere St, Melbourne VIC 3000
(03) 1234 5678
Invoice Number:...
-> hybrid_search: PASS
============================================================
Step 7: Response API with file_search tool
============================================================
NOTE: Responses API uses default_search_mode from server config.
To change mode, update run-pgvector-test.yaml and restart server.
--- Raw JSON Response ---
{
"background": false,
"created_at": 1778236536,
"completed_at": 1778236541,
"error": null,
"frequency_penalty": 0.0,
"id": "resp_5768445a-320c-4f5b-aca9-2673755e32e7",
"incomplete_details": null,
"model": "openai/gpt-5.5",
"object": "response",
"output": [
{
"id": "fc_357b594b-a147-49c8-ad9b-0951d551b681",
"queries": [
"Denny Gunawan invoice number"
],
"status": "completed",
"type": "file_search_call",
"results": [
{
"attributes": {
"title": "Sunny Farm Invoice Sample",
"file_id": "file-af746dafd6dc4b198408dc3e28052314",
"chunk_id": "595160ad-647a-6c5c-1211-7218c5fb5d79",
"filename": "invoicesample.pdf",
"producer": "Prince 16 (www.princexml.com)",
"page_count": 1.0,
"document_id": "798683ff-cd3b-451b-98f0-eb145b9e2d96",
"token_count": 199.0,
"chunk_tokenizer": "tiktoken:cl100k_base",
"metadata_token_count": 93.0
},
"file_id": "798683ff-cd3b-451b-98f0-eb145b9e2d96",
"filename": "798683ff-cd3b-451b-98f0-eb145b9e2d96",
"score": 0.03278688524590164,
"text": "Denny Gunawan\n221 Queen St\nMelbourne VIC 3000\n$39.60\n123 Somewhere St, Melbourne VIC 3000\n(03) 1234 5678\nInvoice Number: #20130304\nOrganic Items Price/kg Quantity(kg) Subtotal\nApple $5.00 1 $5.00\nOrange $1.99 2 $3.98\nWatermelon $1.69 3 $5.07\nMango $9.56 2 $19.12\nPeach $2.99 1 $2.99\nSubtotal $36.00\nGST (10%) $3.60\nTotal $39.60\n* Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam sodales\ndapibus fermentum. Nunc adipiscing, magna sed scelerisque cursus, erat\nlectus dapibus urna, sed facilisis leo dui et ipsum."
}
]
},
{
"content": [
{
"text": "The invoice number for Denny Gunawan is **#20130304** <|798683ff-cd3b-451b-98f0-eb145b9e2d96|>.",
"type": "output_text",
"annotations": [],
"logprobs": []
}
],
"role": "assistant",
"type": "message",
"id": "msg_c56f096f-5893-4cab-8611-b0b13ab575ce",
"status": "completed"
}
],
"parallel_tool_calls": true,
"previous_response_id": null,
"prompt_cache_key": null,
"prompt": null,
"status": "completed",
"temperature": 1.0,
"text": {
"format": {
"type": "text"
},
"verbosity": null
},
"top_p": 1.0,
"top_logprobs": 0,
"tools": [
{
"type": "file_search",
"vector_store_ids": [
"vs_337d1d7b-291e-4056-be9e-2ce94c12723f"
],
"filters": null,
"max_num_results": 10,
"ranking_options": null
}
],
"tool_choice": "auto",
"truncation": "disabled",
"usage": {
"input_tokens": 847,
"output_tokens": 81,
"total_tokens": 928,
"input_tokens_details": {
"cached_tokens": 0
},
"output_tokens_details": {
"reasoning_tokens": 0
}
},
"instructions": null,
"max_tool_calls": null,
"reasoning": null,
"max_output_tokens": null,
"safety_identifier": null,
"service_tier": "default",
"metadata": null,
"presence_penalty": 0.0,
"store": true
}
--- End Raw JSON ---
Response ID: resp_5768445a-320c-4f5b-aca9-2673755e32e7
Status: completed
File search call ID: fc_357b594b-a147-49c8-ad9b-0951d551b681
Status: completed
Search results: 1
[1] score=0.0328 file=798683ff-cd3b-451b-98f0-eb145b9e2d96 | Denny Gunawan
221 Queen St
Melbourne VIC 3000
$39.60
123 Somewhere St, Melbourne VIC 3000
(03) 1234 ...
LLM Response:
The invoice number for Denny Gunawan is **#20130304** <|798683ff-cd3b-451b-98f0-eb145b9e2d96|>.
-> response_file_search: PASS
============================================================
RESULTS SUMMARY
============================================================
Passed: 4
Failed: 0
All tests passed!
```
### P.S. if you're curious about test file I used, here it is:
[invoicesample.pdf](https://github.com/user-attachments/files/27517019/invoicesample.pdf)
<!-- For API changes, include:
1. A testing script (Python, curl, etc.) that exercises the new/modified
endpoints
2. The output from running your script
Example:
```python
...
...
```
Output:
```
<paste actual output here>
```
-->
---------
Signed-off-by: Ian Miller <milleryan2003@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Francisco Javier Arceo <arceofrancisco@gmail.com>1 parent 25cab0f commit 860c322
9 files changed
Lines changed: 642 additions & 458 deletions
File tree
- .github/workflows
- docs/docs/providers/vector_io
- src/ogx/providers
- registry
- remote/vector_io/pgvector
- tests/unit/providers/vector_io
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
85 | 85 | | |
86 | 86 | | |
87 | 87 | | |
88 | | - | |
89 | | - | |
90 | | - | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
91 | 91 | | |
92 | 92 | | |
93 | | - | |
| 93 | + | |
94 | 94 | | |
95 | 95 | | |
96 | 96 | | |
97 | 97 | | |
98 | 98 | | |
99 | 99 | | |
100 | | - | |
101 | | - | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
102 | 111 | | |
103 | 112 | | |
104 | 113 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
236 | 236 | | |
237 | 237 | | |
238 | 238 | | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
239 | 242 | | |
240 | 243 | | |
241 | 244 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
104 | 104 | | |
105 | 105 | | |
106 | 106 | | |
107 | | - | |
| 107 | + | |
108 | 108 | | |
109 | 109 | | |
110 | 110 | | |
| |||
200 | 200 | | |
201 | 201 | | |
202 | 202 | | |
203 | | - | |
| 203 | + | |
| 204 | + | |
204 | 205 | | |
205 | 206 | | |
206 | 207 | | |
| |||
223 | 224 | | |
224 | 225 | | |
225 | 226 | | |
226 | | - | |
| 227 | + | |
| 228 | + | |
227 | 229 | | |
228 | 230 | | |
229 | 231 | | |
| |||
577 | 579 | | |
578 | 580 | | |
579 | 581 | | |
580 | | - | |
581 | | - | |
582 | | - | |
583 | 582 | | |
584 | 583 | | |
585 | 584 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
373 | 373 | | |
374 | 374 | | |
375 | 375 | | |
376 | | - | |
| 376 | + | |
377 | 377 | | |
378 | 378 | | |
379 | 379 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
89 | 89 | | |
90 | 90 | | |
91 | 91 | | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
92 | 97 | | |
93 | 98 | | |
94 | 99 | | |
| |||
97 | 102 | | |
98 | 103 | | |
99 | 104 | | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
100 | 113 | | |
101 | 114 | | |
102 | 115 | | |
| |||
0 commit comments