-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
feat(core): Add byte size limit and oldest first truncation for gen_ai messages #17863
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
29e4eda
bf1003e
c50ad1b
18265ac
f1b468f
50d1d6d
7df9993
c91de63
bbea7cb
6becd4a
903de8a
b05c3c5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| export function getByteSize(str: string): number { | ||
| return new TextEncoder().encode(str).length; | ||
| } | ||
|
|
||
| export function truncateMessagesByBytes(messages: unknown[], maxBytes: number): unknown[] { | ||
RulaKhaled marked this conversation as resolved.
Show resolved
Hide resolved
RulaKhaled marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| if (!Array.isArray(messages) || messages.length === 0) { | ||
| return messages; | ||
| } | ||
|
|
||
| const messagesJson = JSON.stringify(messages); | ||
| const totalBytes = getByteSize(messagesJson); | ||
RulaKhaled marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| if (totalBytes <= maxBytes) { | ||
| return messages; | ||
| } | ||
|
|
||
| let truncatedMessages = [...messages]; | ||
RulaKhaled marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| while (truncatedMessages.length > 0) { | ||
| const truncatedJson = JSON.stringify(truncatedMessages); | ||
| const truncatedBytes = getByteSize(truncatedJson); | ||
|
|
||
| if (truncatedBytes <= maxBytes) { | ||
| break; | ||
| } | ||
|
|
||
| truncatedMessages.shift(); | ||
RulaKhaled marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| return truncatedMessages; | ||
cursor[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| export const DEFAULT_GEN_AI_MESSAGES_BYTE_LIMIT = 100000; | ||
RulaKhaled marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| export function truncateGenAiMessages(messages: unknown[]): unknown[] { | ||
| return truncateMessagesByBytes(messages, DEFAULT_GEN_AI_MESSAGES_BYTE_LIMIT); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,6 +23,7 @@ import { | |
| GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE, | ||
| GEN_AI_SYSTEM_ATTRIBUTE, | ||
| } from '../ai/gen-ai-attributes'; | ||
| import { truncateGenAiMessages } from '../ai/messageTruncation'; | ||
| import { buildMethodPath, getFinalOperationName, getSpanOperation, setTokenUsageAttributes } from '../ai/utils'; | ||
| import { handleCallbackErrors } from '../handleCallbackErrors'; | ||
| import { instrumentAsyncIterableStream, instrumentMessageStream } from './streaming'; | ||
|
|
@@ -71,16 +72,16 @@ function extractRequestAttributes(args: unknown[], methodPath: string): Record<s | |
| return attributes; | ||
| } | ||
|
|
||
| /** | ||
| * Add private request attributes to spans. | ||
| * This is only recorded if recordInputs is true. | ||
| */ | ||
| function addPrivateRequestAttributes(span: Span, params: Record<string, unknown>): void { | ||
| if ('messages' in params) { | ||
| span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(params.messages) }); | ||
| const messages = params.messages; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: JSDoc Comment Removal Ignored Reviewer's RequestJSDoc comment for addPrivateRequestAttributes function was removed despite reviewer @RulaKhaled explicitly requesting to "revert back this JSDoc comment" in the PR discussion. The original JSDoc comment should be restored. |
||
| const truncatedMessages = truncateGenAiMessages(messages as unknown[]); | ||
| span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(truncatedMessages) }); | ||
| } | ||
| if ('input' in params) { | ||
| span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(params.input) }); | ||
| const input = params.input; | ||
| const truncatedInput = truncateGenAiMessages(input as unknown[]); | ||
| span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(truncatedInput) }); | ||
cursor[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| if ('prompt' in params) { | ||
| span.setAttributes({ [GEN_AI_PROMPT_ATTRIBUTE]: JSON.stringify(params.prompt) }); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,6 +22,7 @@ import { | |
| GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE, | ||
| GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE, | ||
| } from '../ai/gen-ai-attributes'; | ||
| import { truncateGenAiMessages } from '../ai/messageTruncation'; | ||
| import { buildMethodPath, getFinalOperationName, getSpanOperation } from '../ai/utils'; | ||
| import { handleCallbackErrors } from '../handleCallbackErrors'; | ||
| import { CHAT_PATH, CHATS_CREATE_METHOD, GOOGLE_GENAI_SYSTEM_NAME } from './constants'; | ||
|
|
@@ -128,25 +129,23 @@ function extractRequestAttributes( | |
| return attributes; | ||
| } | ||
|
|
||
| /** | ||
| * Add private request attributes to spans. | ||
| * This is only recorded if recordInputs is true. | ||
| * Handles different parameter formats for different Google GenAI methods. | ||
| */ | ||
| function addPrivateRequestAttributes(span: Span, params: Record<string, unknown>): void { | ||
RulaKhaled marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // For models.generateContent: ContentListUnion: Content | Content[] | PartUnion | PartUnion[] | ||
| if ('contents' in params) { | ||
| span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(params.contents) }); | ||
| const contents = params.contents; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you revert the comment removal to help others understand the request structure? this could also be a string There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: AI Integration Functions Lack DocumentationJSDoc comments for Additional Locations (2) |
||
| const truncatedContents = truncateGenAiMessages(contents as unknown[]); | ||
| span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(truncatedContents) }); | ||
| } | ||
|
|
||
| // For chat.sendMessage: message can be string or Part[] | ||
| if ('message' in params) { | ||
| span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(params.message) }); | ||
| const message = params.message; | ||
RulaKhaled marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| const truncatedMessage = truncateGenAiMessages(message as unknown[]); | ||
| span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(truncatedMessage) }); | ||
cursor[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| // For chats.create: history contains the conversation history | ||
| if ('history' in params) { | ||
| span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(params.history) }); | ||
| const history = params.history; | ||
| const truncatedHistory = truncateGenAiMessages(history as unknown[]); | ||
| span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(truncatedHistory) }); | ||
| } | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,6 +19,7 @@ import { | |
| GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE, | ||
| GEN_AI_SYSTEM_ATTRIBUTE, | ||
| } from '../ai/gen-ai-attributes'; | ||
| import { truncateGenAiMessages } from '../ai/messageTruncation'; | ||
| import { OPENAI_INTEGRATION_NAME } from './constants'; | ||
| import { instrumentStream } from './streaming'; | ||
| import type { | ||
|
|
@@ -188,13 +189,16 @@ function addResponseAttributes(span: Span, result: unknown, recordOutputs?: bool | |
| } | ||
| } | ||
|
|
||
| // Extract and record AI request inputs, if present. This is intentionally separate from response attributes. | ||
| function addRequestAttributes(span: Span, params: Record<string, unknown>): void { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you also revert back this JSDoc comment? |
||
| if ('messages' in params) { | ||
| span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(params.messages) }); | ||
| const messages = params.messages; | ||
| const truncatedMessages = truncateGenAiMessages(messages as unknown[]); | ||
| span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(truncatedMessages) }); | ||
| } | ||
| if ('input' in params) { | ||
| span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(params.input) }); | ||
| const input = params.input; | ||
| const truncatedInput = truncateGenAiMessages(input as unknown[]); | ||
| span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(truncatedInput) }); | ||
cursor[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,7 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from ' | |
| import type { Event } from '../../types-hoist/event'; | ||
| import type { Span, SpanAttributes, SpanAttributeValue, SpanJSON, SpanOrigin } from '../../types-hoist/span'; | ||
| import { spanToJSON } from '../spanUtils'; | ||
| import { truncateGenAiMessages } from '../ai/messageTruncation'; | ||
| import { toolCallSpanMap } from './constants'; | ||
| import type { TokenSummary } from './types'; | ||
| import { accumulateTokensForParent, applyAccumulatedTokens } from './utils'; | ||
|
|
@@ -187,7 +188,13 @@ function processGenerateSpan(span: Span, name: string, attributes: SpanAttribute | |
| } | ||
|
|
||
| if (attributes[AI_PROMPT_ATTRIBUTE]) { | ||
| span.setAttribute('gen_ai.prompt', attributes[AI_PROMPT_ATTRIBUTE]); | ||
| const prompt = attributes[AI_PROMPT_ATTRIBUTE]; | ||
| if (Array.isArray(prompt)) { | ||
| const truncatedPrompt = truncateGenAiMessages(prompt); | ||
| span.setAttribute('gen_ai.prompt', JSON.stringify(truncatedPrompt)); | ||
| } else { | ||
| span.setAttribute('gen_ai.prompt', prompt); | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| } | ||
| if (attributes[AI_MODEL_ID_ATTRIBUTE] && !attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE]) { | ||
| span.setAttribute(GEN_AI_RESPONSE_MODEL_ATTRIBUTE, attributes[AI_MODEL_ID_ATTRIBUTE]); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mind adding tests for this? You can use the files under
dev-packages/node-integration-tests/suites/tracing