Skip to content

Commit 7a4eb43

Browse files
committed
refactor: address kb connector review (timeout, composite conflict, tests)
CodeRabbit review follow-ups on the kb/* connectors: - Bound pgx.Connect with a 15s connect timeout (Major) so a stalled Postgres handshake can't hang a step that has no timeout of its own; it only shortens an existing deadline. - conflict_target accepts a comma-separated composite key (each part validated as an identifier), not just a single column. - kbDistanceOperator error message lists the l2/euclidean and inner_product/ip aliases it accepts. - Tests: both-metadata-forms, both-vector-forms, and top_k: 0 error cases. - Schema example: note that only cosine is indexed; l2/inner_product need a matching index or they seq-scan. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WnKgCRfMvFcZAkoVbr8k79
1 parent ee8d159 commit 7a4eb43

3 files changed

Lines changed: 29 additions & 8 deletions

File tree

packages/engine/internal/connector/kb.go

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"regexp"
88
"strconv"
99
"strings"
10+
"time"
1011

1112
"github.com/jackc/pgx/v5"
1213
)
@@ -29,6 +30,10 @@ const (
2930
kbDefaultMetadataColumn = "metadata"
3031
kbDefaultTopK = 5
3132
kbMaxTopK = 1000
33+
// kbConnectTimeout bounds the Postgres connection handshake so a stalled
34+
// server can't hang a step that has no timeout of its own. It only shortens
35+
// an existing deadline, never extends one.
36+
kbConnectTimeout = 15 * time.Second
3237
)
3338

3439
// kbIdentParam reads an identifier param, applies a default when absent, and
@@ -162,10 +167,17 @@ func prepareUpsert(params map[string]any) (string, []any, error) {
162167

163168
var conflict string
164169
if ct, ok := params["conflict_target"].(string); ok && ct != "" {
165-
if !kbIdentRe.MatchString(ct) {
166-
return "", nil, fmt.Errorf("conflict_target %q is not a valid SQL identifier", ct)
170+
// Accept a single column or a comma-separated list (composite key),
171+
// validating each part as an identifier.
172+
parts := strings.Split(ct, ",")
173+
for i, p := range parts {
174+
p = strings.TrimSpace(p)
175+
if !kbIdentRe.MatchString(p) {
176+
return "", nil, fmt.Errorf("conflict_target %q is not a valid SQL identifier", ct)
177+
}
178+
parts[i] = p
167179
}
168-
conflict = fmt.Sprintf(" ON CONFLICT (%s) DO NOTHING", ct)
180+
conflict = fmt.Sprintf(" ON CONFLICT (%s) DO NOTHING", strings.Join(parts, ", "))
169181
}
170182

171183
var sb strings.Builder
@@ -193,7 +205,7 @@ func kbDistanceOperator(metric string) (string, error) {
193205
case "inner_product", "ip":
194206
return "<#>", nil
195207
default:
196-
return "", fmt.Errorf("unknown metric %q (use cosine, l2, or inner_product)", metric)
208+
return "", fmt.Errorf("unknown metric %q (use cosine, l2/euclidean, or inner_product/ip)", metric)
197209
}
198210
}
199211

@@ -293,7 +305,9 @@ func (c *KBUpsertConnector) Execute(ctx context.Context, params map[string]any)
293305
return nil, fmt.Errorf("kb/upsert: %w", err)
294306
}
295307

296-
conn, err := pgx.Connect(ctx, connURL)
308+
connectCtx, cancel := context.WithTimeout(ctx, kbConnectTimeout)
309+
conn, err := pgx.Connect(connectCtx, connURL)
310+
cancel()
297311
if err != nil {
298312
return nil, fmt.Errorf("kb/upsert: connecting: %w", err)
299313
}
@@ -321,7 +335,9 @@ func (c *KBQueryConnector) Execute(ctx context.Context, params map[string]any) (
321335
return nil, fmt.Errorf("kb/query: %w", err)
322336
}
323337

324-
conn, err := pgx.Connect(ctx, connURL)
338+
connectCtx, cancel := context.WithTimeout(ctx, kbConnectTimeout)
339+
conn, err := pgx.Connect(connectCtx, connURL)
340+
cancel()
325341
if err != nil {
326342
return nil, fmt.Errorf("kb/query: connecting: %w", err)
327343
}

packages/engine/internal/connector/kb_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ func TestPrepareUpsert_Errors(t *testing.T) {
108108
{"both content forms", map[string]any{"table": "t", "content": "x", "contents": []any{"y"}, "vector": "[1]"}},
109109
{"count mismatch", map[string]any{"table": "t", "contents": []any{"a", "b"}, "vectors": []any{"[1]"}}},
110110
{"metadata mismatch", map[string]any{"table": "t", "content": "a", "vector": "[1]", "metadatas": []any{map[string]any{}, map[string]any{}}}},
111+
{"both metadata forms", map[string]any{"table": "t", "content": "a", "vector": "[1]", "metadata": map[string]any{}, "metadatas": []any{map[string]any{}}}},
112+
{"both vector forms", map[string]any{"table": "t", "content": "a", "vector": "[1]", "vectors": []any{"[1]"}}},
111113
{"sql injection in table", map[string]any{"table": "kb; DROP TABLE users;--", "content": "x", "vector": "[1]"}},
112114
{"sql injection in column", map[string]any{"table": "t", "content_column": "c)); DROP", "content": "x", "vector": "[1]"}},
113115
{"sql injection in conflict", map[string]any{"table": "t", "content": "x", "vector": "[1]", "conflict_target": "a) DO UPDATE"}},
@@ -180,6 +182,7 @@ func TestPrepareQuery_Errors(t *testing.T) {
180182
{"missing vector", map[string]any{"table": "t"}},
181183
{"bad metric", map[string]any{"table": "t", "vector": "[1]", "metric": "manhattan"}},
182184
{"negative top_k", map[string]any{"table": "t", "vector": "[1]", "top_k": -1}},
185+
{"zero top_k", map[string]any{"table": "t", "vector": "[1]", "top_k": 0}},
183186
{"sql injection in table", map[string]any{"table": "t; DROP TABLE x", "vector": "[1]"}},
184187
{"sql injection in column", map[string]any{"table": "t", "vector": "[1]", "columns": []any{"content", "x); DROP"}}},
185188
}

packages/site/src/content/examples/rag-kb-schema.sql

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ CREATE TABLE IF NOT EXISTS kb_documents (
2727
CREATE UNIQUE INDEX IF NOT EXISTS idx_kb_documents_dedupe
2828
ON kb_documents (dedupe_key);
2929

30-
-- Approximate-nearest-neighbour index for cosine distance (pgvector >= 0.5).
31-
-- For small corpora you can omit this and rely on an exact scan.
30+
-- Approximate-nearest-neighbour index for cosine distance (pgvector >= 0.5),
31+
-- which is kb/query's default metric. For small corpora you can omit this and
32+
-- rely on an exact scan. If you query with metric: l2 or inner_product, add a
33+
-- matching index (vector_l2_ops / vector_ip_ops) or those queries seq-scan.
3234
CREATE INDEX IF NOT EXISTS idx_kb_documents_embedding
3335
ON kb_documents USING hnsw (embedding vector_cosine_ops);

0 commit comments

Comments
 (0)