Skip to content

Commit 484b3e2

Browse files
JeevantheDevclaude
andcommitted
feat: wire Azure AI Foundry knowledge base (shipbrain-knowledgebase740) into chat
- Add foundry-kb.ts: calls the Foundry Project endpoint with data_sources pointing to the shipbrain-knowledgebase740 index so the ShipBrain Action Handbook grounding file is actually retrieved on every plain Q&A - Route all no-tool-call chat responses through the knowledge base first; fall back to probe response if the KB call fails (graceful degradation) - Add AZURE_AI_FOUNDRY_PROJECT_ENDPOINT, AZURE_OPENAI_ENDPOINT, and AZURE_AI_FOUNDRY_KNOWLEDGE_BASE env vars to .env.local Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 04486db commit 484b3e2

2 files changed

Lines changed: 82 additions & 4 deletions

File tree

lib/ai/foundry-kb.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/**
2+
* Calls Azure AI Foundry chat completions through the Project endpoint
3+
* with the configured knowledge base attached as a data_source.
4+
* Returns null if knowledge base is not configured or the call fails,
5+
* so callers can fall back to the probe response.
6+
*/
7+
export async function callWithFoundryKnowledgeBase(
8+
messages: Array<{ role: "system" | "user" | "assistant"; content: string }>
9+
): Promise<string | null> {
10+
const projectEndpoint = process.env.AZURE_AI_FOUNDRY_PROJECT_ENDPOINT?.trim();
11+
const knowledgeBase = process.env.AZURE_AI_FOUNDRY_KNOWLEDGE_BASE?.trim();
12+
const apiKey = process.env.AZURE_AI_FOUNDRY_API_KEY?.trim();
13+
const deployment = process.env.AZURE_AI_FOUNDRY_DEPLOYMENT_NAME?.trim();
14+
// Use the Azure OpenAI endpoint for the data_source search endpoint
15+
const openAiEndpoint = process.env.AZURE_OPENAI_ENDPOINT?.trim()
16+
?? process.env.AZURE_AI_FOUNDRY_ENDPOINT?.trim();
17+
18+
if (!projectEndpoint || !knowledgeBase || !apiKey || !deployment) return null;
19+
20+
const url =
21+
`${projectEndpoint}/openai/deployments/${deployment}/chat/completions` +
22+
`?api-version=2025-01-01-preview`;
23+
24+
try {
25+
const res = await fetch(url, {
26+
method: "POST",
27+
headers: {
28+
"content-type": "application/json",
29+
"api-key": apiKey
30+
},
31+
body: JSON.stringify({
32+
messages,
33+
data_sources: [
34+
{
35+
type: "azure_search",
36+
parameters: {
37+
endpoint: openAiEndpoint,
38+
index_name: knowledgeBase,
39+
authentication: {
40+
type: "api_key",
41+
key: apiKey
42+
},
43+
query_type: "simple",
44+
top_n_documents: 5
45+
}
46+
}
47+
]
48+
}),
49+
signal: AbortSignal.timeout(15_000)
50+
});
51+
52+
if (!res.ok) {
53+
const errBody = await res.text().catch(() => "");
54+
console.warn("[foundry-kb] data_sources call failed:", res.status, errBody);
55+
return null;
56+
}
57+
58+
const json = await res.json();
59+
return (json.choices?.[0]?.message?.content as string) ?? null;
60+
} catch (err) {
61+
console.warn("[foundry-kb] data_sources call error:", err);
62+
return null;
63+
}
64+
}
65+
66+
export function isFoundryKbConfigured(): boolean {
67+
return Boolean(
68+
process.env.AZURE_AI_FOUNDRY_PROJECT_ENDPOINT &&
69+
process.env.AZURE_AI_FOUNDRY_KNOWLEDGE_BASE &&
70+
process.env.AZURE_AI_FOUNDRY_API_KEY &&
71+
process.env.AZURE_AI_FOUNDRY_DEPLOYMENT_NAME
72+
);
73+
}

lib/ai/shipbrain-chat.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import { AIMessage, HumanMessage, SystemMessage } from "@langchain/core/messages";
1414
import { getModel } from "@/lib/ai/model";
15+
import { callWithFoundryKnowledgeBase } from "@/lib/ai/foundry-kb";
1516
import { getShipBrainAgentContext } from "@/lib/agent/context";
1617
import { listChatMessages, type StoredChatMessage } from "@/lib/ai/chat-store";
1718
import {
@@ -1293,10 +1294,14 @@ export async function streamShipBrainQuestion(input: {
12931294
const toolCalls = (probeResponse as any).tool_calls ?? (probeResponse as any).additional_kwargs?.tool_calls ?? [];
12941295

12951296
if (!toolCalls.length) {
1296-
// Reuse probe response directly — no second LLM call needed
1297-
const content = typeof probeResponse.content === "string"
1298-
? probeResponse.content
1299-
: JSON.stringify(probeResponse.content);
1297+
// Try Foundry IQ knowledge base first; fall back to probe response text
1298+
const kbMessages = messages.map((m) => ({
1299+
role: (m._getType?.() === "system" ? "system" : m._getType?.() === "human" ? "user" : "assistant") as "system" | "user" | "assistant",
1300+
content: typeof m.content === "string" ? m.content : JSON.stringify(m.content)
1301+
}));
1302+
const kbAnswer = await callWithFoundryKnowledgeBase(kbMessages).catch(() => null);
1303+
const content = kbAnswer
1304+
?? (typeof probeResponse.content === "string" ? probeResponse.content : JSON.stringify(probeResponse.content));
13001305
return { ...base, action: null, stream: textStream(content), responseSource: "foundry_iq" };
13011306
}
13021307

0 commit comments

Comments
 (0)