Skip to content

Commit aaefc82

Browse files
authored
(EAI-622 & EAI-623): Add braintrust tracing to server (#579)
* add braintrust to core * Update packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.ts * working server logging instrumentation * checkpoint * target more recent version to use es2023 * update lib to latest * update tracing * lets see * braintrust tracing in test * no trace in test * tracing tests * up timeout on flaky test * chatbot staging
1 parent 4437ae5 commit aaefc82

File tree

18 files changed

+383
-42
lines changed

18 files changed

+383
-42
lines changed

.drone.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ steps:
3232
OPENAI_PREPROCESSOR_CHAT_COMPLETION_DEPLOYMENT: gpt-4o-mini
3333
OPENAI_API_VERSION: "2024-06-01"
3434
BRAINTRUST_TEXT_TO_DRIVER_PROJECT_NAME: "text-to-node-js-driver-benchmark"
35-
# Setting this placeholder to make test suite pass in the CI
36-
BRAINTRUST_API_KEY: "PLACEHOLDER_UNTIL_OFFICIAL VENDOR"
35+
BRAINTRUST_API_KEY:
36+
from_secret: braintrust_api_key
3737
MONGODB_CONNECTION_URI:
3838
from_secret: mongodb_connection_uri
3939
OPENAI_ENDPOINT:

packages/chatbot-server-mongodb-public/.env.example

+2
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,5 @@ OPENAI_PREPROCESSOR_CHAT_COMPLETION_DEPLOYMENT=<deployment name>
1616
OPENAI_API_VERSION="2024-06-01"
1717
JUDGE_EMBEDDING_MODEL="text-embedding-3-small"
1818
JUDGE_LLM="gpt-4o-mini"
19+
BRAINTRUST_TRACING_API_KEY="<some api key>"
20+
BRAINTRUST_CHATBOT_TRACING_PROJECT_NAME="chatbot-responses"

packages/chatbot-server-mongodb-public/environments/staging.yml

+6-4
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,13 @@ env:
1313
OPENAI_CHAT_COMPLETION_DEPLOYMENT: gpt-4o
1414
OPENAI_VERIFIED_ANSWER_EMBEDDING_DEPLOYMENT: "docs-chatbot-embedding-ada-002"
1515
OPENAI_RETRIEVAL_EMBEDDING_DEPLOYMENT: "text-embedding-3-small"
16+
BRAINTRUST_CHATBOT_TRACING_PROJECT_NAME: "chatbot-responses-staging"
1617

17-
envSecrets:
18-
MONGODB_CONNECTION_URI: docs-chatbot-staging
19-
OPENAI_ENDPOINT: docs-chatbot-staging
20-
OPENAI_API_KEY: docs-chatbot-staging
18+
envSecrets:
19+
MONGODB_CONNECTION_URI: docs-chatbot-staging
20+
OPENAI_ENDPOINT: docs-chatbot-staging
21+
OPENAI_API_KEY: docs-chatbot-staging
22+
BRAINTRUST_TRACING_API_KEY: docs-chatbot-staging
2123

2224
ingress:
2325
enabled: true

packages/chatbot-server-mongodb-public/src/config.ts

+12-5
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import express from "express";
3131
import { wrapOpenAI, wrapTraced } from "mongodb-rag-core/braintrust";
3232
import { AzureOpenAI } from "mongodb-rag-core/openai";
3333
import { MongoClient } from "mongodb-rag-core/mongodb";
34+
import { makeAddMessageToConversationUpdateTrace } from "./tracing";
3435
export const {
3536
MONGODB_CONNECTION_URI,
3637
MONGODB_DATABASE_NAME,
@@ -50,11 +51,13 @@ export const {
5051

5152
const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(",") || [];
5253

53-
export const openAiClient = new AzureOpenAI({
54-
apiKey: OPENAI_API_KEY,
55-
endpoint: OPENAI_ENDPOINT,
56-
apiVersion: OPENAI_API_VERSION,
57-
});
54+
export const openAiClient = wrapOpenAI(
55+
new AzureOpenAI({
56+
apiKey: OPENAI_API_KEY,
57+
endpoint: OPENAI_ENDPOINT,
58+
apiVersion: OPENAI_API_VERSION,
59+
})
60+
);
5861

5962
export const llm = makeOpenAiChatLlm({
6063
openAiClient,
@@ -212,6 +215,10 @@ export const config: AppConfig = {
212215
createConversationCustomData: !isProduction
213216
? createCustomConversationDataWithIpAuthUserAndOrigin
214217
: undefined,
218+
addMessageToConversationUpdateTrace:
219+
makeAddMessageToConversationUpdateTrace(
220+
retrievalConfig.findNearestNeighborsOptions.k
221+
),
215222
generateUserPrompt,
216223
systemPrompt,
217224
maxUserMessagesInConversation: 50,

packages/chatbot-server-mongodb-public/src/systemPrompt.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { SystemPrompt } from "mongodb-chatbot-server";
22

3+
export const llmDoesNotKnowMessage =
4+
"I'm sorry, I do not know how to answer that question. Please try to rephrase your query.";
5+
36
export const systemPrompt = {
47
role: "system",
58
content: `You are expert MongoDB documentation chatbot.
@@ -10,7 +13,7 @@ You were created by MongoDB.
1013
Use the provided context information to answer user questions. You can also use your internal knowledge of MongoDB to inform the answer.
1114
1215
If you do not know the answer to the question, respond only with the following text:
13-
"I'm sorry, I do not know how to answer that question. Please try to rephrase your query."
16+
"${llmDoesNotKnowMessage}"
1417
1518
NEVER include links in your answer.
1619
Format your responses using Markdown. DO NOT mention that your response is formatted in Markdown. Do not use headers in your responses (e.g '# Some H1' or '## Some H2').
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { SomeMessage } from "mongodb-rag-core";
2+
import { extractTracingData } from "./tracing";
3+
import { llmDoesNotKnowMessage } from "./systemPrompt";
4+
describe("extractTracingData", () => {
5+
test("should reject query", () => {
6+
const messages: SomeMessage[] = [
7+
{
8+
role: "user",
9+
rejectQuery: true,
10+
content: "",
11+
},
12+
];
13+
const tracingData = extractTracingData(messages);
14+
expect(tracingData.rejectQuery).toBe(true);
15+
expect(tracingData.tags.includes("rejected_query")).toBe(true);
16+
});
17+
test("should extract metadata", () => {
18+
const messages: SomeMessage[] = [
19+
{
20+
role: "user",
21+
content: "",
22+
customData: {
23+
programmingLanguage: "javascript",
24+
mongoDbProduct: "MongoDB Atlas",
25+
},
26+
},
27+
];
28+
const tracingData = extractTracingData(messages);
29+
expect(tracingData.tags.includes("javascript")).toBe(true);
30+
expect(tracingData.tags.includes("mongodb_atlas")).toBe(true);
31+
});
32+
test("should get number of retrieved chunks", () => {
33+
const messagesNoContext: SomeMessage[] = [
34+
{
35+
role: "user",
36+
content: "",
37+
contextContent: [],
38+
},
39+
];
40+
const tracingData = extractTracingData(messagesNoContext);
41+
expect(tracingData.numRetrievedChunks).toBe(0);
42+
expect(tracingData.tags.includes("no_retrieved_content")).toBe(true);
43+
44+
const messagesWithContext: SomeMessage[] = [
45+
{
46+
role: "user",
47+
content: "",
48+
contextContent: [
49+
{
50+
text: "",
51+
},
52+
{
53+
text: "",
54+
},
55+
],
56+
},
57+
];
58+
const tracingDataWithContext = extractTracingData(messagesWithContext);
59+
expect(tracingDataWithContext.numRetrievedChunks).toBe(2);
60+
expect(tracingDataWithContext.tags.includes("no_retrieved_content")).toBe(
61+
false
62+
);
63+
});
64+
test("should capture verified answer", () => {
65+
const messagesNoContext: SomeMessage[] = [
66+
{
67+
role: "assistant",
68+
content: "",
69+
metadata: {
70+
verifiedAnswer: {
71+
_id: "123",
72+
created: new Date(),
73+
},
74+
},
75+
},
76+
];
77+
const tracingData = extractTracingData(messagesNoContext);
78+
expect(tracingData.isVerifiedAnswer).toBe(true);
79+
expect(tracingData.tags.includes("verified_answer")).toBe(true);
80+
});
81+
test("should capture LLM does not know", () => {
82+
const messagesNoContext: SomeMessage[] = [
83+
{
84+
role: "assistant",
85+
content: llmDoesNotKnowMessage,
86+
},
87+
];
88+
const tracingData = extractTracingData(messagesNoContext);
89+
expect(tracingData.llmDoesNotKnow).toBe(true);
90+
expect(tracingData.tags.includes("llm_does_not_know")).toBe(true);
91+
});
92+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { AppConfig } from "mongodb-chatbot-server";
2+
import { SomeMessage, UserMessage } from "mongodb-rag-core";
3+
import { llmDoesNotKnowMessage } from "./systemPrompt";
4+
5+
export const makeAddMessageToConversationUpdateTrace: (
6+
k: number
7+
) => AppConfig["conversationsRouterConfig"]["addMessageToConversationUpdateTrace"] = (
8+
k
9+
) =>
10+
async function ({ traceId, addedMessages, logger }) {
11+
const tracingData = extractTracingData(addedMessages);
12+
logger.updateSpan({
13+
id: traceId,
14+
tags: tracingData.tags,
15+
scores: {
16+
RejectedQuery: tracingData.rejectQuery === true ? 1 : null,
17+
VerifiedAnswer: tracingData.isVerifiedAnswer === true ? 1 : null,
18+
LlmDoesNotKnow: tracingData.llmDoesNotKnow === true ? 1 : null,
19+
[`RetrievedChunksOver${k}`]:
20+
tracingData.isVerifiedAnswer !== true
21+
? tracingData.numRetrievedChunks / k
22+
: null,
23+
},
24+
});
25+
};
26+
27+
export function extractTracingData(messages: SomeMessage[]) {
28+
const latestUserMessage = messages.findLast(
29+
(message) => message.role === "user"
30+
) as UserMessage | undefined;
31+
const tags = [];
32+
33+
const rejectQuery = latestUserMessage?.rejectQuery;
34+
if (rejectQuery === true) {
35+
tags.push("rejected_query");
36+
}
37+
const programmingLanguage = latestUserMessage?.customData
38+
?.programmingLanguage as string | undefined;
39+
const mongoDbProduct = latestUserMessage?.customData?.mongoDbProduct as
40+
| string
41+
| undefined;
42+
if (programmingLanguage) {
43+
tags.push(tagify(programmingLanguage));
44+
}
45+
if (mongoDbProduct) {
46+
tags.push(tagify(mongoDbProduct));
47+
}
48+
49+
const numRetrievedChunks = latestUserMessage?.contextContent?.length ?? 0;
50+
if (numRetrievedChunks === 0) {
51+
tags.push("no_retrieved_content");
52+
}
53+
54+
const latestAssistantMessage = messages.findLast(
55+
(message) => message.role === "assistant"
56+
);
57+
58+
const isVerifiedAnswer =
59+
latestAssistantMessage?.metadata?.verifiedAnswer !== undefined
60+
? true
61+
: undefined;
62+
if (isVerifiedAnswer) {
63+
tags.push("verified_answer");
64+
}
65+
66+
const llmDoesNotKnow = latestAssistantMessage?.content.includes(
67+
llmDoesNotKnowMessage
68+
);
69+
if (llmDoesNotKnow) {
70+
tags.push("llm_does_not_know");
71+
}
72+
73+
return {
74+
tags,
75+
rejectQuery,
76+
isVerifiedAnswer,
77+
llmDoesNotKnow,
78+
numRetrievedChunks,
79+
};
80+
}
81+
82+
function tagify(s: string) {
83+
return s.replaceAll(/ /g, "_").toLowerCase();
84+
}

packages/mongodb-chatbot-server/src/app.ts

+10
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { ObjectId } from "mongodb-rag-core/mongodb";
1717
import { getRequestId, logRequest, sendErrorResponse } from "./utils";
1818
import { CorsOptions } from "cors";
1919
import cloneDeep from "lodash.clonedeep";
20+
import { braintrustLogger } from "mongodb-rag-core/braintrust";
2021

2122
/**
2223
Configuration for the server Express.js app.
@@ -124,6 +125,15 @@ export const makeApp = async (config: AppConfig): Promise<Express> => {
124125
logger.info(
125126
stringifyFunctions(cloneDeep(config) as unknown as Record<string, unknown>)
126127
);
128+
129+
// Initialize the Braintrust logger if it exists
130+
if (process.env.BRAINTRUST_TRACING_API_KEY !== undefined) {
131+
const braintrustLoggerId = await braintrustLogger.id;
132+
logger.info(`Using Braintrust logger with ID: ${braintrustLoggerId}`);
133+
} else {
134+
logger.info("Braintrust logger not initialized");
135+
}
136+
127137
const app = express();
128138

129139
// Instantiate additional server logic, if it exists.

0 commit comments

Comments
 (0)