@@ -49,7 +49,7 @@ import {
4949 ATTR_GEN_AI_PROVIDER_NAME ,
5050 ATTR_GEN_AI_SYSTEM_INSTRUCTIONS ,
5151 GEN_AI_OPERATION_NAME_VALUE_GENERATE_CONTENT ,
52- GEN_AI_PROVIDER_NAME_VALUE_GCP_GEMINI ,
52+ GEN_AI_PROVIDER_NAME_VALUE_GCP_GEN_AI ,
5353} from "@opentelemetry/semantic-conventions/incubating" ;
5454import {
5555 formatInputMessages ,
@@ -71,7 +71,7 @@ export const genaiFinishReasonMap: Record<string, string> = {
7171 SAFETY : FinishReasons . CONTENT_FILTER ,
7272 RECITATION : FinishReasons . CONTENT_FILTER ,
7373 LANGUAGE : FinishReasons . CONTENT_FILTER ,
74- OTHER : FinishReasons . OTHER ,
74+ OTHER : FinishReasons . ERROR ,
7575 BLOCKLIST : FinishReasons . CONTENT_FILTER ,
7676 PROHIBITED_CONTENT : FinishReasons . CONTENT_FILTER ,
7777 SPII : FinishReasons . CONTENT_FILTER ,
@@ -85,11 +85,6 @@ export const genaiFinishReasonMap: Record<string, string> = {
8585 IMAGE_OTHER : FinishReasons . ERROR ,
8686} ;
8787
88- // Lowercase and strip the "finish_reason_" prefix for the span-level attribute.
89- function geminiReasonToSpanValue ( reason : string ) : string {
90- return reason . toLowerCase ( ) . replace ( / ^ f i n i s h _ r e a s o n _ / , "" ) ;
91- }
92-
9388export class GenAIInstrumentation extends InstrumentationBase {
9489 declare protected _config : GenAIInstrumentationConfig ;
9590 /** Tracks already-patched modules to prevent double-patching on repeated calls. */
@@ -292,8 +287,11 @@ export class GenAIInstrumentation extends InstrumentationBase {
292287 for ( const part of cParts ) {
293288 if ( part . text !== undefined ) {
294289 const prev = acc [ acc . length - 1 ] ;
295- if ( prev ?. text !== undefined ) {
296- prev . text += part . text ; // concatenate consecutive text parts
290+ if (
291+ prev ?. text !== undefined &&
292+ ! ! prev . thought === ! ! part . thought
293+ ) {
294+ prev . text += part . text ; // concatenate consecutive same-type text parts
297295 } else {
298296 acc . push ( { ...part } ) ;
299297 }
@@ -377,7 +375,7 @@ export class GenAIInstrumentation extends InstrumentationBase {
377375 const operationType = GEN_AI_OPERATION_NAME_VALUE_GENERATE_CONTENT ;
378376
379377 const attributes : Attributes = {
380- [ ATTR_GEN_AI_PROVIDER_NAME ] : GEN_AI_PROVIDER_NAME_VALUE_GCP_GEMINI ,
378+ [ ATTR_GEN_AI_PROVIDER_NAME ] : GEN_AI_PROVIDER_NAME_VALUE_GCP_GEN_AI ,
381379 [ ATTR_GEN_AI_OPERATION_NAME ] : operationType ,
382380 [ ATTR_GEN_AI_REQUEST_MODEL ] : model ,
383381 } ;
@@ -463,18 +461,13 @@ export class GenAIInstrumentation extends InstrumentationBase {
463461 try {
464462 const candidates = response . candidates ?? [ ] ;
465463
466- // Collect finish reasons from all candidates (metadata — always emit).
467- // Use passthrough lowercase for span attribute; deduplicate across candidates.
468- const finishReasons = [
469- ...new Set (
470- candidates
471- . map ( ( c ) => c . finishReason )
472- . filter ( ( r ) => r != null )
473- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
474- . map ( ( r ) => geminiReasonToSpanValue ( r ! ) ) ,
475- ) ,
476- ] ;
477- if ( finishReasons . length > 0 ) {
464+ // Collect finish reasons per-candidate (no dedup — matches Python behaviour).
465+ // null/unspecified → ""; set attr only when at least one reason is non-empty.
466+ const finishReasons = candidates . map ( ( c ) => {
467+ const r = c . finishReason ;
468+ return r != null ? ( genaiFinishReasonMap [ r ] ?? r ) : "" ;
469+ } ) ;
470+ if ( finishReasons . some ( ( r ) => r !== "" ) ) {
478471 span . setAttribute ( ATTR_GEN_AI_RESPONSE_FINISH_REASONS , finishReasons ) ;
479472 }
480473
0 commit comments