Handle project points in search result formatters
search-context.ts and search-issues.ts now check payload.type and branch formatting for project points (slug, name, status, summary) vs chunk points (source, section, text). Casts to Record<string, unknown> first instead of blindly casting to ChunkPayload.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { z } from "zod";
|
||||
import { embed } from "../lib/embeddings.js";
|
||||
import { getClient, COLLECTION, type ChunkPayload } from "../lib/qdrant.js";
|
||||
import { getClient, COLLECTION } from "../lib/qdrant.js";
|
||||
|
||||
export const searchContextSchema = z.object({
|
||||
query: z.string().describe("Natural language search query"),
|
||||
@@ -14,6 +14,28 @@ export const searchContextSchema = z.object({
|
||||
|
||||
export type SearchContextInput = z.infer<typeof searchContextSchema>;
|
||||
|
||||
function formatResult(payload: Record<string, unknown>, index: number, score: number): string {
|
||||
if (payload.type === "project") {
|
||||
const tags = Array.isArray(payload.tags) && payload.tags.length > 0 ? (payload.tags as string[]).join(", ") : "none";
|
||||
return [
|
||||
`### Result ${index + 1} (score: ${score.toFixed(3)})`,
|
||||
`**Project**: ${payload.name} (\`${payload.slug}\`) | **Status**: ${payload.status}`,
|
||||
`**Tags**: ${tags} | **Updated**: ${payload.updated}`,
|
||||
"",
|
||||
payload.summary as string,
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
const tags = Array.isArray(payload.tags) && payload.tags.length > 0 ? (payload.tags as string[]).join(", ") : "none";
|
||||
return [
|
||||
`### Result ${index + 1} (score: ${score.toFixed(3)})`,
|
||||
`**Source**: ${payload.source} | **Section**: ${payload.section} | **Type**: ${payload.type}${payload.host ? ` | **Host**: ${payload.host}` : ""}`,
|
||||
`**Tags**: ${tags} | **Updated**: ${payload.updated}`,
|
||||
"",
|
||||
payload.text as string,
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
export async function searchContext(input: SearchContextInput): Promise<string> {
|
||||
const vector = await embed(input.query);
|
||||
const client = getClient();
|
||||
@@ -38,15 +60,6 @@ export async function searchContext(input: SearchContextInput): Promise<string>
|
||||
}
|
||||
|
||||
return results
|
||||
.map((r, i) => {
|
||||
const p = r.payload as unknown as ChunkPayload;
|
||||
return [
|
||||
`### Result ${i + 1} (score: ${r.score.toFixed(3)})`,
|
||||
`**Source**: ${p.source} | **Section**: ${p.section} | **Type**: ${p.type}${p.host ? ` | **Host**: ${p.host}` : ""}`,
|
||||
`**Tags**: ${p.tags.length > 0 ? p.tags.join(", ") : "none"} | **Updated**: ${p.updated}`,
|
||||
"",
|
||||
p.text,
|
||||
].join("\n");
|
||||
})
|
||||
.map((r, i) => formatResult(r.payload as Record<string, unknown>, i, r.score))
|
||||
.join("\n\n---\n\n");
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { z } from "zod";
|
||||
import { embed } from "../lib/embeddings.js";
|
||||
import { getClient, COLLECTION, type ChunkPayload } from "../lib/qdrant.js";
|
||||
import { getClient, COLLECTION } from "../lib/qdrant.js";
|
||||
|
||||
export const searchIssuesSchema = z.object({
|
||||
query: z.string().describe("Search query for operational notes and issues"),
|
||||
@@ -9,6 +9,20 @@ export const searchIssuesSchema = z.object({
|
||||
|
||||
export type SearchIssuesInput = z.infer<typeof searchIssuesSchema>;
|
||||
|
||||
function formatIssueResult(payload: Record<string, unknown>, index: number, score: number): string {
|
||||
if (payload.type === "project") {
|
||||
return `### Result ${index + 1} (score: ${score.toFixed(3)})\n**Project**: ${payload.name} (\`${payload.slug}\`) | **Status**: ${payload.status}\n\n${payload.summary}`;
|
||||
}
|
||||
return `### Result ${index + 1} (score: ${score.toFixed(3)})\n**Source**: ${payload.source} | **Section**: ${payload.section}\n\n${payload.text}`;
|
||||
}
|
||||
|
||||
function getSearchableText(payload: Record<string, unknown>): string {
|
||||
if (payload.type === "project") {
|
||||
return `${payload.name ?? ""} ${payload.summary ?? ""} ${payload.current_state ?? ""}`;
|
||||
}
|
||||
return `${payload.section ?? ""} ${payload.text ?? ""}`;
|
||||
}
|
||||
|
||||
export async function searchIssues(input: SearchIssuesInput): Promise<string> {
|
||||
const vector = await embed(input.query);
|
||||
const client = getClient();
|
||||
@@ -24,8 +38,8 @@ export async function searchIssues(input: SearchIssuesInput): Promise<string> {
|
||||
// Filter to operational/issue-related sections
|
||||
const issuePatterns = /operational|issue|troubleshoot|problem|fix|error|outage|rca|incident|quirk|gotcha|known/i;
|
||||
const filtered = results.filter((r) => {
|
||||
const p = r.payload as unknown as ChunkPayload;
|
||||
return issuePatterns.test(p.section) || issuePatterns.test(p.text);
|
||||
const text = getSearchableText(r.payload as Record<string, unknown>);
|
||||
return issuePatterns.test(text);
|
||||
});
|
||||
|
||||
const final = filtered.slice(0, input.limit);
|
||||
@@ -38,18 +52,12 @@ export async function searchIssues(input: SearchIssuesInput): Promise<string> {
|
||||
return (
|
||||
"*No explicitly operational sections found. Showing best general matches:*\n\n" +
|
||||
fallback
|
||||
.map((r, i) => {
|
||||
const p = r.payload as unknown as ChunkPayload;
|
||||
return `### Result ${i + 1} (score: ${r.score.toFixed(3)})\n**Source**: ${p.source} | **Section**: ${p.section}\n\n${p.text}`;
|
||||
})
|
||||
.map((r, i) => formatIssueResult(r.payload as Record<string, unknown>, i, r.score))
|
||||
.join("\n\n---\n\n")
|
||||
);
|
||||
}
|
||||
|
||||
return final
|
||||
.map((r, i) => {
|
||||
const p = r.payload as unknown as ChunkPayload;
|
||||
return `### Result ${i + 1} (score: ${r.score.toFixed(3)})\n**Source**: ${p.source} | **Section**: ${p.section}\n\n${p.text}`;
|
||||
})
|
||||
.map((r, i) => formatIssueResult(r.payload as Record<string, unknown>, i, r.score))
|
||||
.join("\n\n---\n\n");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user