Skip to content

Commit d3a88a5

Browse files
committed
fix(site): wrap groq_query with direct Sanity client fallback and safely handle tool results
1 parent 4fecf64 commit d3a88a5

1 file changed

Lines changed: 92 additions & 15 deletions

File tree

‎apps/site/src/pages/api/chat.ts‎

Lines changed: 92 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { env } from "cloudflare:workers";
22
import { createMCPClient } from "@ai-sdk/mcp";
3+
import { createClient } from "@sanity/client";
34
import { isStepCount, streamText } from "ai";
45
import type { APIRoute } from "astro";
56
import { createWorkersAI } from "workers-ai-provider";
@@ -125,7 +126,72 @@ export const POST: APIRoute = async ({ request }) => {
125126
},
126127
});
127128

128-
const tools = await mcpClient.tools();
129+
const mcpTools = await mcpClient.tools();
130+
131+
// Directly configure Sanity client for fallback per prompt instructions (Step 3)
132+
const sanityClient = createClient({
133+
projectId: (cfEnv?.PUBLIC_SANITY_PROJECT_ID as string) || "hfh83o0w",
134+
dataset: (cfEnv?.PUBLIC_SANITY_DATASET as string) || "production",
135+
apiVersion: (cfEnv?.PUBLIC_SANITY_API_VERSION as string) || "2025-09-30",
136+
useCdn: false,
137+
token: sanityToken,
138+
});
139+
140+
const tools = {
141+
...mcpTools,
142+
groq_query: {
143+
...mcpTools.groq_query,
144+
execute: async ({ query }: { query: string }) => {
145+
try {
146+
const mcpResult = await (mcpTools.groq_query as any).execute(
147+
{ query },
148+
{} as any,
149+
);
150+
const parsed =
151+
typeof mcpResult === "string"
152+
? JSON.parse(mcpResult)
153+
: mcpResult;
154+
const results =
155+
parsed?.result ||
156+
(Array.isArray(parsed?.content) && parsed.content[0]?.text
157+
? JSON.parse(parsed.content[0].text)?.result
158+
: null);
159+
if (Array.isArray(results) && results.length > 0) {
160+
return mcpResult;
161+
}
162+
} catch {}
163+
164+
// Fallback: direct Sanity fetch
165+
try {
166+
const cleanQuery = query.startsWith("*") ? query : `*[${query}]`;
167+
const directResult = await sanityClient.fetch(cleanQuery);
168+
return {
169+
content: [
170+
{
171+
type: "text",
172+
text: JSON.stringify({ result: directResult }),
173+
},
174+
],
175+
};
176+
} catch (directErr) {
177+
return {
178+
content: [
179+
{
180+
type: "text",
181+
text: JSON.stringify({
182+
error:
183+
directErr instanceof Error
184+
? directErr.message
185+
: String(directErr),
186+
}),
187+
},
188+
],
189+
isError: true,
190+
};
191+
}
192+
},
193+
},
194+
};
129195

130196
// 2. Setup Cloudflare Workers AI model
131197
const aiBinding = cfEnv?.AI;
@@ -153,10 +219,10 @@ export const POST: APIRoute = async ({ request }) => {
153219
const result = streamText({
154220
model,
155221
system:
156-
"You are CodingCat.dev's intelligent content assistant. Answer user queries by searching and exploring the Sanity Content Lake dataset using your tools (initial_context, groq_query, schema_explorer, array_field_reader). In each step, you can call tools to query the dataset. Once you obtain the results from your tool calls, you MUST synthesize your findings and generate a helpful, complete text answer grounded in the retrieved content (articles, podcasts, authors, transcripts) citing relevant titles or URLs.",
222+
"You are CodingCat.dev's intelligent content assistant (AJ). Answer user queries by searching and exploring the Sanity Content Lake dataset using your tools (initial_context, groq_query, schema_explorer, array_field_reader).\n\nDataset tips:\n- Content types: 'post' (blog articles), 'podcast' (podcast episodes), 'author', 'guest', 'transcript'.\n- People's names (authors, guests) are stored in the 'title' field (e.g. *[_type == 'author' && title match '*Alex*']).\n- Fields on posts and podcasts: title, slug, excerpt, date, author[]->title, guest[]->title, youtube.\n- Once you receive results from your query, synthesize them into a helpful, conversational, grounded response citing specific titles and URLs (/post/<slug> or /podcast/<slug>).",
157223
messages: coreMessages,
158224
tools,
159-
stopWhen: isStepCount(10),
225+
stopWhen: isStepCount(5),
160226
onError: ({ error }) => {
161227
console.error("[StreamText Internal Error]", error);
162228
},
@@ -179,21 +245,32 @@ export const POST: APIRoute = async ({ request }) => {
179245
if (!hasOutput) {
180246
// Check all steps to see if text was produced across steps
181247
const steps = await result.steps;
182-
const allText = steps.map((s) => s.text).filter(Boolean).join("\n\n");
248+
const allText = steps
249+
.map((s) => s.text)
250+
.filter(Boolean)
251+
.join("\n\n");
183252
if (allText) {
184253
await writer.write(encoder.encode(allText));
185254
} else {
186-
const debugInfo = steps
187-
.map(
188-
(s, idx) =>
189-
`Step ${idx + 1}: finish=${s.finishReason}, tools=${s.toolCalls?.map((t) => t.toolName).join(",") || "none"}`,
190-
)
191-
.join("; ");
192-
await writer.write(
193-
encoder.encode(
194-
`I queried the CodingCat.dev Sanity content lake (${steps.length} steps: ${debugInfo}), but no final text was emitted.`,
195-
),
196-
);
255+
const lastStep = steps[steps.length - 1];
256+
const firstToolResult = lastStep?.toolResults?.[0] as any;
257+
const directResult = firstToolResult?.output ?? firstToolResult?.result;
258+
if (directResult?.result?.length) {
259+
const titles = directResult.result
260+
.map((item: any) => `• ${item.title || item._id}`)
261+
.join("\n");
262+
await writer.write(
263+
encoder.encode(
264+
`Here are the relevant items found in CodingCat.dev:\n\n${titles}`,
265+
),
266+
);
267+
} else {
268+
await writer.write(
269+
encoder.encode(
270+
"I searched the CodingCat.dev Sanity content lake for your query, but did not find any matching articles or episodes.",
271+
),
272+
);
273+
}
197274
}
198275
}
199276
} catch (streamErr) {

0 commit comments

Comments
 (0)