@bridgenet-tech/spear-data 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +112 -0
- package/dist/db.d.ts +13 -0
- package/dist/db.js +107 -0
- package/dist/db.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +139 -0
- package/dist/index.js.map +1 -0
- package/dist/tools/infra-status.d.ts +2 -0
- package/dist/tools/infra-status.js +103 -0
- package/dist/tools/infra-status.js.map +1 -0
- package/dist/tools/ingest-status.d.ts +2 -0
- package/dist/tools/ingest-status.js +105 -0
- package/dist/tools/ingest-status.js.map +1 -0
- package/dist/tools/schema.d.ts +2 -0
- package/dist/tools/schema.js +147 -0
- package/dist/tools/schema.js.map +1 -0
- package/dist/tools/semantic-search.d.ts +2 -0
- package/dist/tools/semantic-search.js +432 -0
- package/dist/tools/semantic-search.js.map +1 -0
- package/dist/tools/sql-query.d.ts +2 -0
- package/dist/tools/sql-query.js +138 -0
- package/dist/tools/sql-query.js.map +1 -0
- package/package.json +49 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getPostgres } from "../db.js";
|
|
3
|
+
const InputSchema = z.object({
|
|
4
|
+
target: z.enum(["local", "cloud"])
|
|
5
|
+
.default("cloud")
|
|
6
|
+
.describe("Which database to inspect: 'local' (Docker) or 'cloud' (Neon)"),
|
|
7
|
+
table: z.string()
|
|
8
|
+
.optional()
|
|
9
|
+
.describe("Optional: specific table name to get detailed info for. If omitted, returns all tables with column summaries.")
|
|
10
|
+
}).strict();
|
|
11
|
+
export function registerSchema(server) {
|
|
12
|
+
server.registerTool("spear_schema", {
|
|
13
|
+
title: "Database Schema",
|
|
14
|
+
description: `Return the Spear PSA database schema — tables, columns, types, and relationships.
|
|
15
|
+
|
|
16
|
+
Use this before writing SQL queries to understand the data model. Returns column names, data types, nullable status, and foreign key relationships.
|
|
17
|
+
|
|
18
|
+
Tables: tickets, organizations, contacts, agents, ticket_notes, ticket_note_chunks, assets, contracts, projects, tasks, time_entries, teams_messages, teams_message_chunks, pipeline_runs, sync_state, ticket_statuses, ticket_priorities, ticket_types, ticket_sources, ticket_queues, ticket_audit_log, sla_policies.
|
|
19
|
+
|
|
20
|
+
Key relationships:
|
|
21
|
+
- tickets.organization_id → organizations.id
|
|
22
|
+
- tickets.assigned_agent_id → agents.id
|
|
23
|
+
- tickets.contact_id → contacts.id
|
|
24
|
+
- ticket_notes.ticket_id → tickets.id
|
|
25
|
+
- time_entries.ticket_id → tickets.id
|
|
26
|
+
- time_entries.agent_id → agents.id
|
|
27
|
+
- assets.organization_id → organizations.id
|
|
28
|
+
- contracts.organization_id → organizations.id
|
|
29
|
+
- ticket_note_chunks.ticket_id → tickets.id (ON DELETE CASCADE)`,
|
|
30
|
+
inputSchema: InputSchema,
|
|
31
|
+
annotations: {
|
|
32
|
+
readOnlyHint: true,
|
|
33
|
+
destructiveHint: false,
|
|
34
|
+
idempotentHint: true,
|
|
35
|
+
openWorldHint: false
|
|
36
|
+
}
|
|
37
|
+
}, async (params) => {
|
|
38
|
+
const start = performance.now();
|
|
39
|
+
try {
|
|
40
|
+
const sql = getPostgres(params.target);
|
|
41
|
+
if (params.table) {
|
|
42
|
+
// Detailed schema for a specific table
|
|
43
|
+
const columns = await sql `
|
|
44
|
+
SELECT c.column_name, c.data_type, c.is_nullable, c.column_default,
|
|
45
|
+
c.character_maximum_length,
|
|
46
|
+
tc.constraint_type,
|
|
47
|
+
ccu.table_name as referenced_table
|
|
48
|
+
FROM information_schema.columns c
|
|
49
|
+
LEFT JOIN information_schema.key_column_usage kcu
|
|
50
|
+
ON c.table_name = kcu.table_name AND c.column_name = kcu.column_name
|
|
51
|
+
AND c.table_schema = kcu.table_schema
|
|
52
|
+
LEFT JOIN information_schema.table_constraints tc
|
|
53
|
+
ON kcu.constraint_name = tc.constraint_name
|
|
54
|
+
AND tc.constraint_type = 'FOREIGN KEY'
|
|
55
|
+
LEFT JOIN information_schema.constraint_column_usage ccu
|
|
56
|
+
ON tc.constraint_name = ccu.constraint_name
|
|
57
|
+
WHERE c.table_schema = 'public'
|
|
58
|
+
AND c.table_name = ${params.table}
|
|
59
|
+
ORDER BY c.ordinal_position
|
|
60
|
+
`;
|
|
61
|
+
// Get row count — validate table name to prevent injection
|
|
62
|
+
const validTables = [
|
|
63
|
+
"tickets", "organizations", "contacts", "agents", "ticket_notes",
|
|
64
|
+
"ticket_note_chunks", "assets", "contracts", "projects", "tasks",
|
|
65
|
+
"time_entries", "teams_messages", "teams_message_chunks", "pipeline_runs",
|
|
66
|
+
"sync_state", "ticket_statuses", "ticket_priorities", "ticket_types",
|
|
67
|
+
"ticket_sources", "ticket_queues", "ticket_audit_log", "sla_policies"
|
|
68
|
+
];
|
|
69
|
+
let rowCount = "unknown";
|
|
70
|
+
if (validTables.includes(params.table)) {
|
|
71
|
+
const countResult = await sql.unsafe(`SELECT COUNT(*)::int as count FROM "${params.table}"`).catch(() => [{ count: null }]);
|
|
72
|
+
rowCount = countResult[0]?.count ?? "unknown";
|
|
73
|
+
}
|
|
74
|
+
const ms = Math.round(performance.now() - start);
|
|
75
|
+
return {
|
|
76
|
+
content: [{
|
|
77
|
+
type: "text",
|
|
78
|
+
text: JSON.stringify({
|
|
79
|
+
_meta: { source: "postgres", target: params.target, execution_ms: ms },
|
|
80
|
+
table: params.table,
|
|
81
|
+
row_count: rowCount,
|
|
82
|
+
columns
|
|
83
|
+
}, null, 2)
|
|
84
|
+
}]
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
// All tables overview
|
|
88
|
+
const tables = await sql `
|
|
89
|
+
SELECT t.table_name,
|
|
90
|
+
(SELECT COUNT(*)
|
|
91
|
+
FROM information_schema.columns c
|
|
92
|
+
WHERE c.table_name = t.table_name AND c.table_schema = 'public'
|
|
93
|
+
)::int as column_count
|
|
94
|
+
FROM information_schema.tables t
|
|
95
|
+
WHERE t.table_schema = 'public' AND t.table_type = 'BASE TABLE'
|
|
96
|
+
ORDER BY t.table_name
|
|
97
|
+
`;
|
|
98
|
+
// Get columns for all tables in one query
|
|
99
|
+
const allColumns = await sql `
|
|
100
|
+
SELECT table_name, column_name, data_type, is_nullable
|
|
101
|
+
FROM information_schema.columns
|
|
102
|
+
WHERE table_schema = 'public'
|
|
103
|
+
ORDER BY table_name, ordinal_position
|
|
104
|
+
`;
|
|
105
|
+
// Group columns by table
|
|
106
|
+
const tableColumns = {};
|
|
107
|
+
for (const col of allColumns) {
|
|
108
|
+
const tn = col.table_name;
|
|
109
|
+
if (!tableColumns[tn])
|
|
110
|
+
tableColumns[tn] = [];
|
|
111
|
+
tableColumns[tn].push({
|
|
112
|
+
column_name: col.column_name,
|
|
113
|
+
data_type: col.data_type,
|
|
114
|
+
is_nullable: col.is_nullable
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
const ms = Math.round(performance.now() - start);
|
|
118
|
+
return {
|
|
119
|
+
content: [{
|
|
120
|
+
type: "text",
|
|
121
|
+
text: JSON.stringify({
|
|
122
|
+
_meta: { source: "postgres", target: params.target, execution_ms: ms },
|
|
123
|
+
tables: tables.map(t => ({
|
|
124
|
+
name: t.table_name,
|
|
125
|
+
column_count: t.column_count,
|
|
126
|
+
columns: tableColumns[t.table_name] ?? []
|
|
127
|
+
}))
|
|
128
|
+
}, null, 2)
|
|
129
|
+
}]
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
catch (e) {
|
|
133
|
+
const ms = Math.round(performance.now() - start);
|
|
134
|
+
return {
|
|
135
|
+
isError: true,
|
|
136
|
+
content: [{
|
|
137
|
+
type: "text",
|
|
138
|
+
text: JSON.stringify({
|
|
139
|
+
_meta: { source: "postgres", target: params.target, execution_ms: ms },
|
|
140
|
+
error: e.message
|
|
141
|
+
}, null, 2)
|
|
142
|
+
}]
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/tools/schema.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getPostgres } from "../db.js";
|
|
3
|
+
const GOOGLE_AI_API_KEY = process.env.GOOGLE_API_KEY ?? "";
|
|
4
|
+
const EMBED_SERVER_URL = process.env.EMBED_SERVER_URL ?? "http://studio:11434";
|
|
5
|
+
const EMBED_MODEL_GOOGLE = "gemini-embedding-001";
|
|
6
|
+
const EMBED_MODEL_SERVER = "nomic-embed-text";
|
|
7
|
+
const EMBED_DIM = 768;
|
|
8
|
+
const ENTITY_TYPES = [
|
|
9
|
+
"tickets", "organizations", "contacts", "agents", "assets",
|
|
10
|
+
"contracts", "projects", "tasks", "time_entries", "ticket_note_chunks",
|
|
11
|
+
"teams_message_chunks", "all"
|
|
12
|
+
];
|
|
13
|
+
const InputSchema = z.object({
|
|
14
|
+
query: z.string()
|
|
15
|
+
.min(1, "Search query is required")
|
|
16
|
+
.describe("Natural language search query — will be embedded and matched against entity vectors via pgvector"),
|
|
17
|
+
entity: z.enum(ENTITY_TYPES)
|
|
18
|
+
.default("tickets")
|
|
19
|
+
.describe("Entity type to search: tickets (default), organizations, contacts, agents, assets, contracts, projects, tasks, time_entries, ticket_note_chunks, teams_message_chunks, or 'all' to search everything"),
|
|
20
|
+
target: z.enum(["local", "cloud"])
|
|
21
|
+
.default("cloud")
|
|
22
|
+
.describe("Which database to search: 'local' (Docker) or 'cloud' (Neon)"),
|
|
23
|
+
limit: z.number()
|
|
24
|
+
.int()
|
|
25
|
+
.min(1)
|
|
26
|
+
.max(50)
|
|
27
|
+
.default(10)
|
|
28
|
+
.describe("Maximum results to return (default 10, max 50)"),
|
|
29
|
+
score_threshold: z.number()
|
|
30
|
+
.min(0)
|
|
31
|
+
.max(1)
|
|
32
|
+
.default(0.3)
|
|
33
|
+
.describe("Minimum cosine similarity score (0-1) to include in results (default 0.3)")
|
|
34
|
+
}).strict();
|
|
35
|
+
async function embedQuery(text) {
|
|
36
|
+
// Try Google AI Studio first (globally accessible, best for production)
|
|
37
|
+
if (GOOGLE_AI_API_KEY) {
|
|
38
|
+
try {
|
|
39
|
+
const resp = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/${EMBED_MODEL_GOOGLE}:embedContent?key=${GOOGLE_AI_API_KEY}`, {
|
|
40
|
+
method: "POST",
|
|
41
|
+
headers: { "Content-Type": "application/json" },
|
|
42
|
+
body: JSON.stringify({
|
|
43
|
+
model: `models/${EMBED_MODEL_GOOGLE}`,
|
|
44
|
+
content: { parts: [{ text }] },
|
|
45
|
+
outputDimensionality: EMBED_DIM
|
|
46
|
+
})
|
|
47
|
+
});
|
|
48
|
+
if (resp.ok) {
|
|
49
|
+
const data = await resp.json();
|
|
50
|
+
if (data.embedding?.values?.length === EMBED_DIM) {
|
|
51
|
+
return data.embedding.values;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Google failed — fall through to remote server
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// Google unreachable — fall through to remote server
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Fallback: remote embedding server (Mac Studio or similar)
|
|
61
|
+
const resp = await fetch(`${EMBED_SERVER_URL}/api/embed`, {
|
|
62
|
+
method: "POST",
|
|
63
|
+
headers: { "Content-Type": "application/json" },
|
|
64
|
+
body: JSON.stringify({ model: EMBED_MODEL_SERVER, input: [text] })
|
|
65
|
+
});
|
|
66
|
+
if (!resp.ok) {
|
|
67
|
+
const body = await resp.text();
|
|
68
|
+
throw new Error(`Embed server failed (${resp.status}): ${body}`);
|
|
69
|
+
}
|
|
70
|
+
const data = await resp.json();
|
|
71
|
+
if (!data.embeddings?.[0] || data.embeddings[0].length !== EMBED_DIM) {
|
|
72
|
+
throw new Error(`Expected ${EMBED_DIM}-dim vector, got ${data.embeddings?.[0]?.length ?? 0}`);
|
|
73
|
+
}
|
|
74
|
+
return data.embeddings[0];
|
|
75
|
+
}
|
|
76
|
+
async function searchTickets(sql, vectorStr, threshold, limit) {
|
|
77
|
+
return sql `
|
|
78
|
+
SELECT t.id,
|
|
79
|
+
t.ticket_number,
|
|
80
|
+
t.title,
|
|
81
|
+
LEFT(t.description, 300) AS description_snippet,
|
|
82
|
+
t.status,
|
|
83
|
+
t.priority,
|
|
84
|
+
t.ticket_type,
|
|
85
|
+
t.source,
|
|
86
|
+
o.name AS org_name,
|
|
87
|
+
COALESCE(a.first_name || ' ' || a.last_name, '') AS agent_name,
|
|
88
|
+
t.created_at,
|
|
89
|
+
t.completed_date,
|
|
90
|
+
t.sla_resolution_breached,
|
|
91
|
+
1 - (t.embedding <=> ${vectorStr}::vector) AS similarity
|
|
92
|
+
FROM tickets t
|
|
93
|
+
LEFT JOIN organizations o ON o.id = t.organization_id
|
|
94
|
+
LEFT JOIN agents a ON a.id = t.assigned_agent_id
|
|
95
|
+
WHERE t.embedding IS NOT NULL
|
|
96
|
+
AND 1 - (t.embedding <=> ${vectorStr}::vector) >= ${threshold}
|
|
97
|
+
ORDER BY t.embedding <=> ${vectorStr}::vector
|
|
98
|
+
LIMIT ${limit}
|
|
99
|
+
`;
|
|
100
|
+
}
|
|
101
|
+
async function searchOrganizations(sql, vectorStr, threshold, limit) {
|
|
102
|
+
return sql `
|
|
103
|
+
SELECT o.id,
|
|
104
|
+
o.name,
|
|
105
|
+
o.phone,
|
|
106
|
+
o.city,
|
|
107
|
+
o.state,
|
|
108
|
+
o.tier,
|
|
109
|
+
o.active,
|
|
110
|
+
1 - (o.embedding <=> ${vectorStr}::vector) AS similarity
|
|
111
|
+
FROM organizations o
|
|
112
|
+
WHERE o.embedding IS NOT NULL
|
|
113
|
+
AND 1 - (o.embedding <=> ${vectorStr}::vector) >= ${threshold}
|
|
114
|
+
ORDER BY o.embedding <=> ${vectorStr}::vector
|
|
115
|
+
LIMIT ${limit}
|
|
116
|
+
`;
|
|
117
|
+
}
|
|
118
|
+
async function searchContacts(sql, vectorStr, threshold, limit) {
|
|
119
|
+
return sql `
|
|
120
|
+
SELECT c.id,
|
|
121
|
+
c.first_name,
|
|
122
|
+
c.last_name,
|
|
123
|
+
c.email,
|
|
124
|
+
c.phone,
|
|
125
|
+
o.name AS org_name,
|
|
126
|
+
1 - (c.embedding <=> ${vectorStr}::vector) AS similarity
|
|
127
|
+
FROM contacts c
|
|
128
|
+
LEFT JOIN organizations o ON o.id = c.organization_id
|
|
129
|
+
WHERE c.embedding IS NOT NULL
|
|
130
|
+
AND 1 - (c.embedding <=> ${vectorStr}::vector) >= ${threshold}
|
|
131
|
+
ORDER BY c.embedding <=> ${vectorStr}::vector
|
|
132
|
+
LIMIT ${limit}
|
|
133
|
+
`;
|
|
134
|
+
}
|
|
135
|
+
async function searchAgents(sql, vectorStr, threshold, limit) {
|
|
136
|
+
return sql `
|
|
137
|
+
SELECT a.id,
|
|
138
|
+
a.first_name,
|
|
139
|
+
a.last_name,
|
|
140
|
+
a.email,
|
|
141
|
+
a.active AS is_active,
|
|
142
|
+
1 - (a.embedding <=> ${vectorStr}::vector) AS similarity
|
|
143
|
+
FROM agents a
|
|
144
|
+
WHERE a.embedding IS NOT NULL
|
|
145
|
+
AND 1 - (a.embedding <=> ${vectorStr}::vector) >= ${threshold}
|
|
146
|
+
ORDER BY a.embedding <=> ${vectorStr}::vector
|
|
147
|
+
LIMIT ${limit}
|
|
148
|
+
`;
|
|
149
|
+
}
|
|
150
|
+
async function searchAssets(sql, vectorStr, threshold, limit) {
|
|
151
|
+
return sql `
|
|
152
|
+
SELECT a.id,
|
|
153
|
+
a.name,
|
|
154
|
+
a.serial_number,
|
|
155
|
+
a.asset_type,
|
|
156
|
+
o.name AS org_name,
|
|
157
|
+
1 - (a.embedding <=> ${vectorStr}::vector) AS similarity
|
|
158
|
+
FROM assets a
|
|
159
|
+
LEFT JOIN organizations o ON o.id = a.organization_id
|
|
160
|
+
WHERE a.embedding IS NOT NULL
|
|
161
|
+
AND 1 - (a.embedding <=> ${vectorStr}::vector) >= ${threshold}
|
|
162
|
+
ORDER BY a.embedding <=> ${vectorStr}::vector
|
|
163
|
+
LIMIT ${limit}
|
|
164
|
+
`;
|
|
165
|
+
}
|
|
166
|
+
async function searchContracts(sql, vectorStr, threshold, limit) {
|
|
167
|
+
return sql `
|
|
168
|
+
SELECT c.id,
|
|
169
|
+
c.name,
|
|
170
|
+
c.contract_type,
|
|
171
|
+
c.status,
|
|
172
|
+
o.name AS org_name,
|
|
173
|
+
1 - (c.embedding <=> ${vectorStr}::vector) AS similarity
|
|
174
|
+
FROM contracts c
|
|
175
|
+
LEFT JOIN organizations o ON o.id = c.organization_id
|
|
176
|
+
WHERE c.embedding IS NOT NULL
|
|
177
|
+
AND 1 - (c.embedding <=> ${vectorStr}::vector) >= ${threshold}
|
|
178
|
+
ORDER BY c.embedding <=> ${vectorStr}::vector
|
|
179
|
+
LIMIT ${limit}
|
|
180
|
+
`;
|
|
181
|
+
}
|
|
182
|
+
async function searchProjects(sql, vectorStr, threshold, limit) {
|
|
183
|
+
return sql `
|
|
184
|
+
SELECT p.id,
|
|
185
|
+
p.name,
|
|
186
|
+
p.status,
|
|
187
|
+
o.name AS org_name,
|
|
188
|
+
1 - (p.embedding <=> ${vectorStr}::vector) AS similarity
|
|
189
|
+
FROM projects p
|
|
190
|
+
LEFT JOIN organizations o ON o.id = p.organization_id
|
|
191
|
+
WHERE p.embedding IS NOT NULL
|
|
192
|
+
AND 1 - (p.embedding <=> ${vectorStr}::vector) >= ${threshold}
|
|
193
|
+
ORDER BY p.embedding <=> ${vectorStr}::vector
|
|
194
|
+
LIMIT ${limit}
|
|
195
|
+
`;
|
|
196
|
+
}
|
|
197
|
+
async function searchTasks(sql, vectorStr, threshold, limit) {
|
|
198
|
+
return sql `
|
|
199
|
+
SELECT tk.id,
|
|
200
|
+
tk.title,
|
|
201
|
+
tk.status,
|
|
202
|
+
p.name AS project_name,
|
|
203
|
+
1 - (tk.embedding <=> ${vectorStr}::vector) AS similarity
|
|
204
|
+
FROM tasks tk
|
|
205
|
+
LEFT JOIN projects p ON p.id = tk.project_id
|
|
206
|
+
WHERE tk.embedding IS NOT NULL
|
|
207
|
+
AND 1 - (tk.embedding <=> ${vectorStr}::vector) >= ${threshold}
|
|
208
|
+
ORDER BY tk.embedding <=> ${vectorStr}::vector
|
|
209
|
+
LIMIT ${limit}
|
|
210
|
+
`;
|
|
211
|
+
}
|
|
212
|
+
async function searchTimeEntries(sql, vectorStr, threshold, limit) {
|
|
213
|
+
return sql `
|
|
214
|
+
SELECT te.id,
|
|
215
|
+
te.summary,
|
|
216
|
+
te.hours_worked,
|
|
217
|
+
te.date_worked,
|
|
218
|
+
t.ticket_number,
|
|
219
|
+
COALESCE(a.first_name || ' ' || a.last_name, '') AS agent_name,
|
|
220
|
+
1 - (te.embedding <=> ${vectorStr}::vector) AS similarity
|
|
221
|
+
FROM time_entries te
|
|
222
|
+
LEFT JOIN tickets t ON t.id = te.ticket_id
|
|
223
|
+
LEFT JOIN agents a ON a.id = te.agent_id
|
|
224
|
+
WHERE te.embedding IS NOT NULL
|
|
225
|
+
AND 1 - (te.embedding <=> ${vectorStr}::vector) >= ${threshold}
|
|
226
|
+
ORDER BY te.embedding <=> ${vectorStr}::vector
|
|
227
|
+
LIMIT ${limit}
|
|
228
|
+
`;
|
|
229
|
+
}
|
|
230
|
+
async function searchTicketNoteChunks(sql, vectorStr, threshold, limit) {
|
|
231
|
+
return sql `
|
|
232
|
+
SELECT c.id,
|
|
233
|
+
LEFT(c.chunk_text, 500) AS chunk_snippet,
|
|
234
|
+
c.note_count,
|
|
235
|
+
c.chunk_index,
|
|
236
|
+
t.ticket_number,
|
|
237
|
+
t.title AS ticket_title,
|
|
238
|
+
t.status AS ticket_status,
|
|
239
|
+
t.priority AS ticket_priority,
|
|
240
|
+
o.name AS org_name,
|
|
241
|
+
1 - (c.embedding <=> ${vectorStr}::vector) AS similarity
|
|
242
|
+
FROM ticket_note_chunks c
|
|
243
|
+
JOIN tickets t ON t.id = c.ticket_id
|
|
244
|
+
LEFT JOIN organizations o ON o.id = t.organization_id
|
|
245
|
+
WHERE c.embedding IS NOT NULL
|
|
246
|
+
AND 1 - (c.embedding <=> ${vectorStr}::vector) >= ${threshold}
|
|
247
|
+
ORDER BY c.embedding <=> ${vectorStr}::vector
|
|
248
|
+
LIMIT ${limit}
|
|
249
|
+
`;
|
|
250
|
+
}
|
|
251
|
+
async function searchTeamsMessageChunks(sql, vectorStr, threshold, limit) {
|
|
252
|
+
return sql `
|
|
253
|
+
SELECT c.id,
|
|
254
|
+
LEFT(c.chunk_text, 500) AS chunk_snippet,
|
|
255
|
+
c.channel_name,
|
|
256
|
+
c.message_count,
|
|
257
|
+
c.chunk_index,
|
|
258
|
+
array_to_string(c.author_names, ', ') AS authors,
|
|
259
|
+
c.earliest_message_at,
|
|
260
|
+
c.latest_message_at,
|
|
261
|
+
1 - (c.embedding <=> ${vectorStr}::vector) AS similarity
|
|
262
|
+
FROM teams_message_chunks c
|
|
263
|
+
WHERE c.embedding IS NOT NULL
|
|
264
|
+
AND 1 - (c.embedding <=> ${vectorStr}::vector) >= ${threshold}
|
|
265
|
+
ORDER BY c.embedding <=> ${vectorStr}::vector
|
|
266
|
+
LIMIT ${limit}
|
|
267
|
+
`;
|
|
268
|
+
}
|
|
269
|
+
// Map entity name to its search function
|
|
270
|
+
const SEARCH_FNS = {
|
|
271
|
+
tickets: searchTickets,
|
|
272
|
+
organizations: searchOrganizations,
|
|
273
|
+
contacts: searchContacts,
|
|
274
|
+
agents: searchAgents,
|
|
275
|
+
assets: searchAssets,
|
|
276
|
+
contracts: searchContracts,
|
|
277
|
+
projects: searchProjects,
|
|
278
|
+
tasks: searchTasks,
|
|
279
|
+
time_entries: searchTimeEntries,
|
|
280
|
+
ticket_note_chunks: searchTicketNoteChunks,
|
|
281
|
+
teams_message_chunks: searchTeamsMessageChunks,
|
|
282
|
+
};
|
|
283
|
+
// Count embedded rows for a given table
|
|
284
|
+
async function countEmbedded(sql, table) {
|
|
285
|
+
const result = await sql.unsafe(`SELECT COUNT(*) AS total FROM ${table} WHERE embedding IS NOT NULL`);
|
|
286
|
+
return Number(result[0].total);
|
|
287
|
+
}
|
|
288
|
+
// Searchable entity types (excludes "all")
|
|
289
|
+
const SEARCHABLE_ENTITIES = ENTITY_TYPES.filter((e) => e !== "all");
|
|
290
|
+
export function registerSemanticSearch(server) {
|
|
291
|
+
server.registerTool("spear_semantic_search", {
|
|
292
|
+
title: "Semantic Search",
|
|
293
|
+
description: `Search for similar records across all entity types using natural language via pgvector in PostgreSQL.
|
|
294
|
+
|
|
295
|
+
Entities are embedded using gemini-embedding-001 (768-dim) stored directly in each table via pgvector.
|
|
296
|
+
|
|
297
|
+
Supported entity types: tickets (default), organizations, contacts, agents, assets, contracts, projects, tasks, time_entries, ticket_note_chunks, teams_message_chunks, or 'all' to search across every type simultaneously.
|
|
298
|
+
|
|
299
|
+
Each entity returns its relevant columns plus a cosine similarity score. When entity='all', results from all tables with embeddings are merged and sorted by score, with an entity_type field on each result.
|
|
300
|
+
|
|
301
|
+
ticket_note_chunks contains grouped technician notes (~3000 chars each) — use this to find specific resolution steps, troubleshooting details, or recurring patterns buried in note history.
|
|
302
|
+
|
|
303
|
+
teams_message_chunks contains grouped Microsoft Teams messages from TechTeam channels — use this to find informal tech discussions, escalation context, and troubleshooting knowledge that doesn't appear in formal ticket notes.
|
|
304
|
+
|
|
305
|
+
Use this for fuzzy/conceptual queries like "printer problems at law firms" or "recurring VPN issues" where exact SQL matching would miss results.
|
|
306
|
+
|
|
307
|
+
Results are ranked by cosine similarity with scores included. Higher score = more similar.`,
|
|
308
|
+
inputSchema: InputSchema,
|
|
309
|
+
annotations: {
|
|
310
|
+
readOnlyHint: true,
|
|
311
|
+
destructiveHint: false,
|
|
312
|
+
idempotentHint: true,
|
|
313
|
+
openWorldHint: true
|
|
314
|
+
}
|
|
315
|
+
}, async (params) => {
|
|
316
|
+
const start = performance.now();
|
|
317
|
+
try {
|
|
318
|
+
const sql = getPostgres(params.target);
|
|
319
|
+
const entity = params.entity;
|
|
320
|
+
// Determine which entities to search
|
|
321
|
+
const entitiesToSearch = entity === "all" ? [...SEARCHABLE_ENTITIES] : [entity];
|
|
322
|
+
// Check embedding counts for the target entities
|
|
323
|
+
const countResults = await Promise.all(entitiesToSearch.map(async (e) => ({
|
|
324
|
+
entity: e,
|
|
325
|
+
count: await countEmbedded(sql, e),
|
|
326
|
+
})));
|
|
327
|
+
const totalEmbedded = countResults.reduce((sum, r) => sum + r.count, 0);
|
|
328
|
+
const embeddedCounts = {};
|
|
329
|
+
for (const r of countResults) {
|
|
330
|
+
embeddedCounts[r.entity] = r.count;
|
|
331
|
+
}
|
|
332
|
+
// Filter to entities that actually have embeddings
|
|
333
|
+
const entitiesWithEmbeddings = countResults
|
|
334
|
+
.filter(r => r.count > 0)
|
|
335
|
+
.map(r => r.entity);
|
|
336
|
+
if (entitiesWithEmbeddings.length === 0) {
|
|
337
|
+
const ms = Math.round(performance.now() - start);
|
|
338
|
+
return {
|
|
339
|
+
content: [{
|
|
340
|
+
type: "text",
|
|
341
|
+
text: JSON.stringify({
|
|
342
|
+
_meta: {
|
|
343
|
+
source: "pgvector",
|
|
344
|
+
target: params.target,
|
|
345
|
+
entity: entity,
|
|
346
|
+
execution_ms: ms,
|
|
347
|
+
total_embedded: 0,
|
|
348
|
+
embedded_counts: embeddedCounts
|
|
349
|
+
},
|
|
350
|
+
results: [],
|
|
351
|
+
message: `No embeddings found for ${entity === "all" ? "any entity type" : entity}. Run the ingest pipeline with embedding enabled to populate pgvector embeddings.`
|
|
352
|
+
}, null, 2)
|
|
353
|
+
}]
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
// Embed the query
|
|
357
|
+
const embedStart = performance.now();
|
|
358
|
+
const queryVector = await embedQuery(params.query);
|
|
359
|
+
const embedMs = Math.round(performance.now() - embedStart);
|
|
360
|
+
const vectorStr = `[${queryVector.join(",")}]`;
|
|
361
|
+
const searchStart = performance.now();
|
|
362
|
+
let results;
|
|
363
|
+
if (entity === "all") {
|
|
364
|
+
// Run parallel searches across all entities with embeddings
|
|
365
|
+
const perEntityLimit = params.limit; // fetch full limit per entity, then merge
|
|
366
|
+
const allResults = await Promise.all(entitiesWithEmbeddings.map(async (e) => {
|
|
367
|
+
const rows = await SEARCH_FNS[e](sql, vectorStr, params.score_threshold, perEntityLimit);
|
|
368
|
+
return rows.map(r => ({
|
|
369
|
+
entity_type: e,
|
|
370
|
+
...r,
|
|
371
|
+
score: Math.round(Number(r.similarity) * 1000) / 1000,
|
|
372
|
+
}));
|
|
373
|
+
}));
|
|
374
|
+
// Merge all results, sort by score descending, take top N
|
|
375
|
+
const merged = allResults.flat();
|
|
376
|
+
merged.sort((a, b) => b.score - a.score);
|
|
377
|
+
results = merged.slice(0, params.limit).map(r => {
|
|
378
|
+
const { similarity, ...rest } = r;
|
|
379
|
+
return rest;
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
else {
|
|
383
|
+
// Single-entity search
|
|
384
|
+
const searchResults = await SEARCH_FNS[entity](sql, vectorStr, params.score_threshold, params.limit);
|
|
385
|
+
results = searchResults.map(r => {
|
|
386
|
+
const { similarity, ...rest } = r;
|
|
387
|
+
return {
|
|
388
|
+
...rest,
|
|
389
|
+
score: Math.round(Number(r.similarity) * 1000) / 1000,
|
|
390
|
+
};
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
const searchMs = Math.round(performance.now() - searchStart);
|
|
394
|
+
const ms = Math.round(performance.now() - start);
|
|
395
|
+
const embedProvider = GOOGLE_AI_API_KEY ? `Google ${EMBED_MODEL_GOOGLE}` : `${EMBED_MODEL_SERVER} @ ${EMBED_SERVER_URL}`;
|
|
396
|
+
const result = {
|
|
397
|
+
_meta: {
|
|
398
|
+
source: "pgvector",
|
|
399
|
+
target: params.target,
|
|
400
|
+
entity: entity,
|
|
401
|
+
execution_ms: ms,
|
|
402
|
+
embed_ms: embedMs,
|
|
403
|
+
search_ms: searchMs,
|
|
404
|
+
total_embedded: totalEmbedded,
|
|
405
|
+
embedded_counts: embeddedCounts,
|
|
406
|
+
result_count: results.length,
|
|
407
|
+
model: embedProvider,
|
|
408
|
+
query: params.query,
|
|
409
|
+
score_threshold: params.score_threshold
|
|
410
|
+
},
|
|
411
|
+
results
|
|
412
|
+
};
|
|
413
|
+
return {
|
|
414
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
catch (e) {
|
|
418
|
+
const ms = Math.round(performance.now() - start);
|
|
419
|
+
return {
|
|
420
|
+
isError: true,
|
|
421
|
+
content: [{
|
|
422
|
+
type: "text",
|
|
423
|
+
text: JSON.stringify({
|
|
424
|
+
_meta: { source: "pgvector", target: params.target, execution_ms: ms },
|
|
425
|
+
error: e.message
|
|
426
|
+
}, null, 2)
|
|
427
|
+
}]
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
//# sourceMappingURL=semantic-search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"semantic-search.js","sourceRoot":"","sources":["../../src/tools/semantic-search.ts"],"names":[],"mappings":"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"}
|