@juspay/neurolink 12.58.1 → 13.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -3
- package/dist/auth/anthropicOAuth.js +3 -3
- package/dist/auth/middleware/AuthMiddleware.d.ts +1 -1
- package/dist/auth/sessionManager.js +4 -4
- package/dist/auth/tokenStore.js +4 -4
- package/dist/browser/neurolink.min.js +474 -473
- package/dist/cli/commands/observability.d.ts +1 -6
- package/dist/cli/commands/observability.js +0 -106
- package/dist/cli/commands/proxy.js +9 -18
- package/dist/cli/commands/telemetry.d.ts +3 -13
- package/dist/cli/commands/telemetry.js +7 -354
- package/dist/cli/loop/session.js +1 -1
- package/dist/client/httpClient.js +1 -1
- package/dist/client/sseClient.js +1 -1
- package/dist/client/streamingClient.js +2 -2
- package/dist/client/wsClient.js +1 -1
- package/dist/constants/langfuse.d.ts +25 -0
- package/dist/constants/langfuse.js +38 -0
- package/dist/constants/telemetry.d.ts +20 -0
- package/dist/constants/telemetry.js +34 -0
- package/dist/core/baseProvider.d.ts +2 -1
- package/dist/core/baseProvider.js +4 -11
- package/dist/core/conversationMemoryManager.js +5 -8
- package/dist/core/factory.js +1 -1
- package/dist/core/geminiLoopAdapter.d.ts +8 -1
- package/dist/core/geminiLoopAdapter.js +25 -1
- package/dist/core/loopEngine.js +69 -40
- package/dist/core/modules/MessageBuilder.js +2 -2
- package/dist/core/modules/StreamHandler.js +2 -2
- package/dist/core/modules/TelemetryHandler.d.ts +2 -18
- package/dist/core/modules/TelemetryHandler.js +1 -66
- package/dist/core/modules/ToolsManager.js +40 -31
- package/dist/core/nativeGenerateLoop.js +78 -8
- package/dist/core/redisConversationMemoryManager.js +8 -15
- package/dist/core/toolExecutionGuards.js +1 -13
- package/dist/index.d.ts +5 -12
- package/dist/index.js +5 -37
- package/dist/mcp/batching/requestBatcher.js +1 -1
- package/dist/mcp/externalServerManager.js +3 -21
- package/dist/mcp/toolDiscoveryService.js +4 -16
- package/dist/mcp/toolRegistry.js +2 -5
- package/dist/memory/memoryRetrievalTools.js +1 -1
- package/dist/neurolink.d.ts +16 -27
- package/dist/neurolink.js +788 -792
- package/dist/observability/exporterSpanProcessor.d.ts +50 -0
- package/dist/observability/exporterSpanProcessor.js +181 -0
- package/dist/observability/exporters/langfuseExporter.d.ts +13 -34
- package/dist/observability/exporters/langfuseExporter.js +101 -154
- package/dist/observability/index.d.ts +1 -0
- package/dist/observability/index.js +1 -0
- package/dist/observability/langfuseMasking.d.ts +8 -0
- package/dist/observability/langfuseMasking.js +47 -0
- package/dist/observability/otlpEncoding.d.ts +20 -0
- package/dist/observability/otlpEncoding.js +85 -0
- package/dist/providers/amazonBedrock/client.js +142 -176
- package/dist/providers/amazonBedrock/loopAdapter.js +14 -1
- package/dist/providers/amazonSagemaker.js +118 -80
- package/dist/providers/anthropic/client.js +3 -52
- package/dist/providers/anthropic/loopAdapter.js +24 -1
- package/dist/providers/anthropic/rateLimitCapture.js +9 -12
- package/dist/providers/googleAiStudio/client.d.ts +4 -5
- package/dist/providers/googleAiStudio/client.js +38 -40
- package/dist/providers/googleVertex/client.d.ts +2 -7
- package/dist/providers/googleVertex/client.js +282 -378
- package/dist/providers/googleVertex/constants.js +2 -2
- package/dist/providers/litellm/client.d.ts +2 -9
- package/dist/providers/litellm/client.js +1 -63
- package/dist/providers/openAI/client.d.ts +1 -8
- package/dist/providers/openAI/client.js +0 -65
- package/dist/providers/openaiChatCompletionsBase.d.ts +1 -0
- package/dist/providers/openaiChatCompletionsBase.js +61 -4
- package/dist/providers/replicate.js +22 -4
- package/dist/proxy/codexUsage.js +7 -0
- package/dist/proxy/proxyFetch.js +19 -25
- package/dist/proxy/proxyTracer.d.ts +14 -7
- package/dist/proxy/proxyTracer.js +129 -83
- package/dist/proxy/proxyTranslationEngine.js +0 -1
- package/dist/proxy/requestLogger.d.ts +1 -1
- package/dist/proxy/requestLogger.js +39 -45
- package/dist/server/middleware/common.js +1 -1
- package/dist/server/routes/agentRoutes.js +4 -4
- package/dist/server/routes/claudeProxyRoutes.js +99 -196
- package/dist/server/routes/codexProxyRoutes.js +621 -591
- package/dist/server/routes/healthRoutes.js +6 -6
- package/dist/server/routes/mcpRoutes.js +7 -7
- package/dist/server/routes/memoryRoutes.js +7 -7
- package/dist/server/routes/openApiRoutes.js +3 -3
- package/dist/server/routes/toolRoutes.js +5 -5
- package/dist/server/routes/transcribeRoutes.js +1 -1
- package/dist/server/utils/validation.d.ts +3 -3
- package/dist/services/server/ai/observability/instrumentation.d.ts +34 -201
- package/dist/services/server/ai/observability/instrumentation.js +220 -1267
- package/dist/services/server/ai/observability/langfuseSetup.d.ts +14 -0
- package/dist/services/server/ai/observability/langfuseSetup.js +55 -0
- package/dist/services/server/ai/observability/otlpSetup.d.ts +16 -0
- package/dist/services/server/ai/observability/otlpSetup.js +58 -0
- package/dist/session/globalSessionState.js +0 -11
- package/dist/telemetry/attributes.d.ts +23 -46
- package/dist/telemetry/attributes.js +76 -162
- package/dist/telemetry/chatMessages.d.ts +13 -0
- package/dist/telemetry/chatMessages.js +59 -0
- package/dist/telemetry/index.d.ts +5 -19
- package/dist/telemetry/index.js +5 -26
- package/dist/telemetry/observation.d.ts +37 -0
- package/dist/telemetry/observation.js +112 -0
- package/dist/telemetry/observationNames.d.ts +18 -0
- package/dist/telemetry/observationNames.js +24 -0
- package/dist/telemetry/spanSanitizer.d.ts +39 -0
- package/dist/telemetry/spanSanitizer.js +92 -0
- package/dist/telemetry/spanSerialization.d.ts +13 -0
- package/dist/telemetry/spanSerialization.js +46 -0
- package/dist/telemetry/tracers.d.ts +37 -18
- package/dist/telemetry/tracers.js +97 -19
- package/dist/telemetry/withSpan.d.ts +47 -25
- package/dist/telemetry/withSpan.js +158 -71
- package/dist/types/cli.d.ts +0 -11
- package/dist/types/decision.d.ts +3 -0
- package/dist/types/exporter.d.ts +54 -0
- package/dist/types/exporter.js +0 -4
- package/dist/types/generate.d.ts +4 -1
- package/dist/types/isolatedAgent.d.ts +5 -4
- package/dist/types/loopEngine.d.ts +18 -28
- package/dist/types/observability.d.ts +127 -160
- package/dist/types/providers.d.ts +15 -0
- package/dist/types/proxy.d.ts +41 -2
- package/dist/utils/anthropicTraceSanitizer.d.ts +2 -2
- package/dist/utils/anthropicTraceSanitizer.js +3 -1
- package/dist/utils/conversationMemory.js +4 -4
- package/dist/utils/fileDetector.js +1 -1
- package/dist/utils/messageBuilder.js +1 -1
- package/dist/utils/noOutputSentinel.d.ts +4 -16
- package/dist/utils/noOutputSentinel.js +12 -28
- package/dist/utils/providerRetry.js +4 -4
- package/dist/voice/livekit/brain.js +1 -1
- package/dist/voice/livekit/realtimeMemory.d.ts +1 -1
- package/dist/voice/livekit/realtimeMemory.js +66 -1
- package/dist/voice/livekit/realtimeVoiceAgent.js +1 -1
- package/docs-site/static/search-index.json +42 -63
- package/package.json +10 -4
- package/dist/telemetry/telemetryService.d.ts +0 -79
- package/dist/telemetry/telemetryService.js +0 -572
- package/dist/utils/observabilityHelpers.d.ts +0 -27
- package/dist/utils/observabilityHelpers.js +0 -47
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/* eslint-disable max-lines-per-function */
|
|
2
2
|
// Native SDK imports - no more @ai-sdk/google-vertex dependency
|
|
3
3
|
import fs from "fs";
|
|
4
|
+
import { LangfuseOtelSpanAttributes } from "@langfuse/tracing";
|
|
4
5
|
import { guardToolExecutor } from "../../core/toolExecutionGuards.js";
|
|
5
6
|
import path from "path";
|
|
6
7
|
import { ErrorCategory, ErrorSeverity, } from "../../constants/enums.js";
|
|
@@ -35,17 +36,17 @@ import { createNativeThinkingConfig } from "../../utils/thinkingConfig.js";
|
|
|
35
36
|
import { TimeoutError, withTimeout } from "../../utils/async/index.js";
|
|
36
37
|
import { parseTimeout } from "../../utils/timeout.js";
|
|
37
38
|
import { appendStepText, buildContextCapMessage, buildDedupedEngineTools, buildLoopExitMessage, buildToolLoopCapMessage, buildWrapupNudgeText, createContextGuard, createTurnClock, extractThoughtSignature, isAbortError, mapGeminiFinishReason, prependConversationMessages, resolveTurnStopReason, DedupExecuteMap, } from "../googleNativeGemini3/index.js";
|
|
38
|
-
import { createGeminiLoopAdapter } from "../../core/geminiLoopAdapter.js";
|
|
39
|
+
import { createGeminiLoopAdapter, describeGeminiRequest, } from "../../core/geminiLoopAdapter.js";
|
|
39
40
|
import { runAgenticLoop } from "../../core/loopEngine.js";
|
|
40
41
|
import { createAnthropicLoopAdapter } from "../anthropic/loopAdapter.js";
|
|
41
|
-
import { extractMcpToolErrorMessage } from "../../utils/mcpErrorText.js";
|
|
42
42
|
import { createStreamChannel } from "../../core/streamChannel.js";
|
|
43
43
|
import { toNativeToolDeclarations } from "../../core/nativeToolFormat.js";
|
|
44
44
|
import { getAvailableInputTokens, getContextWindowSize, } from "../../constants/contextWindows.js";
|
|
45
45
|
import { resolveLiveTool } from "../../tools/toolDiscovery.js";
|
|
46
|
-
import { ATTR,
|
|
47
|
-
import {
|
|
48
|
-
import {
|
|
46
|
+
import { ATTR, spanContentAttribute, spanModelRequestAttribute, tracers, withClientSpan, withClientStreamSpan, withSpan, } from "../../telemetry/index.js";
|
|
47
|
+
import { observe, observeGeneration, observeTrace, } from "../../telemetry/observation.js";
|
|
48
|
+
import { withGeneration, withStepSpan } from "../../telemetry/withSpan.js";
|
|
49
|
+
import { context as otelContext, SpanKind, SpanStatusCode, trace as otelTrace, } from "@opentelemetry/api";
|
|
49
50
|
import { transformToolExecutions } from "../../utils/transformationUtils.js";
|
|
50
51
|
import { resolveToolExecutionRecords } from "../../core/toolExecutionRecorder.js";
|
|
51
52
|
import { modelSupportsForcedToolChoice, resolveSamplingParams, } from "../../models/modelRegistry.js";
|
|
@@ -1000,15 +1001,14 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
1000
1001
|
// it sees for AI Studio. BaseProvider.stream does NOT emit this span
|
|
1001
1002
|
// for any provider — each native provider has to add it itself.
|
|
1002
1003
|
return withClientStreamSpan({
|
|
1003
|
-
name: "
|
|
1004
|
+
name: "provider.stream",
|
|
1004
1005
|
tracer: tracers.provider,
|
|
1006
|
+
detached: true,
|
|
1005
1007
|
attributes: {
|
|
1006
|
-
[ATTR.
|
|
1007
|
-
[ATTR.GEN_AI_MODEL]: modelName,
|
|
1008
|
-
[ATTR.GEN_AI_OPERATION]: "stream",
|
|
1008
|
+
[ATTR.NL_MODEL]: modelName,
|
|
1009
1009
|
[ATTR.NL_PROVIDER]: this.providerName,
|
|
1010
1010
|
},
|
|
1011
|
-
}, async (
|
|
1011
|
+
}, async () => {
|
|
1012
1012
|
const streamStartTime = Date.now();
|
|
1013
1013
|
// Tool filter (a0269210): trust options.tools — caller (BaseProvider.stream)
|
|
1014
1014
|
// already merged MCP/built-in tools and applied any enabledToolNames filter.
|
|
@@ -1021,7 +1021,7 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
1021
1021
|
// Pipeline A. Without this, test:tracing's "Message Build Span"
|
|
1022
1022
|
// assertion has to skip on every native-Vertex stream.
|
|
1023
1023
|
const processedOptions = await withSpan({
|
|
1024
|
-
name: "
|
|
1024
|
+
name: "message.build",
|
|
1025
1025
|
tracer: tracers.provider,
|
|
1026
1026
|
attributes: {
|
|
1027
1027
|
[ATTR.NL_PROVIDER]: this.providerName,
|
|
@@ -1059,10 +1059,6 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
1059
1059
|
});
|
|
1060
1060
|
result = await this.runNativeStreamWithMiddleware(mergedOptions, modelName, (effectiveOptions) => this.executeNativeGemini3Stream(effectiveOptions));
|
|
1061
1061
|
}
|
|
1062
|
-
// Cost / token usage on the stream span. Native streams resolve
|
|
1063
|
-
// usage synchronously (the stream loop has already drained), so
|
|
1064
|
-
// `result.usage` is populated by the time we reach this point.
|
|
1065
|
-
this.attachUsageAndCostAttributes(streamSpan, modelName, result?.usage);
|
|
1066
1062
|
// Wrap the result's async iterable to fire onChunk / onFinish
|
|
1067
1063
|
// lifecycle callbacks. Pipeline A gets these via the AI SDK
|
|
1068
1064
|
// wrapStream middleware; the native path has to fire them here.
|
|
@@ -1695,6 +1691,7 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
1695
1691
|
// nudge, and the terminal block below.
|
|
1696
1692
|
const engineAdapter = createGeminiLoopAdapter({
|
|
1697
1693
|
providerLabel: "GoogleVertex",
|
|
1694
|
+
provider: this.providerName,
|
|
1698
1695
|
maxSteps,
|
|
1699
1696
|
// Ported verbatim: the same DEFAULT_TOOL_MAX_RETRIES threshold, plus
|
|
1700
1697
|
// the two rules this loop has always had and the engine did not.
|
|
@@ -2473,7 +2470,7 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
2473
2470
|
// Cross-step text accumulation + last SDK finish reason, so the
|
|
2474
2471
|
// maxSteps-exhaustion exit can surface real gathered text (Bug 1) and a
|
|
2475
2472
|
// meaningful finishReason (Bug 2) instead of a placeholder / "unknown".
|
|
2476
|
-
|
|
2473
|
+
const accumulatedText = "";
|
|
2477
2474
|
let lastFinishReason;
|
|
2478
2475
|
const allToolCalls = [];
|
|
2479
2476
|
const toolExecutions = [];
|
|
@@ -2540,6 +2537,7 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
2540
2537
|
// nudge, and the terminal block below.
|
|
2541
2538
|
const engineAdapter = createGeminiLoopAdapter({
|
|
2542
2539
|
providerLabel: "GoogleVertex",
|
|
2540
|
+
provider: this.providerName,
|
|
2543
2541
|
maxSteps,
|
|
2544
2542
|
// Ported verbatim: the same DEFAULT_TOOL_MAX_RETRIES threshold, plus
|
|
2545
2543
|
// the two rules this loop has always had and the engine did not.
|
|
@@ -2992,57 +2990,76 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
2992
2990
|
// Vertex endpoint would hang the maxSteps recovery path indefinitely.
|
|
2993
2991
|
// On timeout withTimeout rejects (TimeoutError) and the catch below
|
|
2994
2992
|
// falls back to the placeholder — no new failure path is introduced.
|
|
2995
|
-
return await
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
if (usageMetadata) {
|
|
3020
|
-
if (usageMetadata.promptTokenCount !== undefined &&
|
|
3021
|
-
usageMetadata.promptTokenCount > 0) {
|
|
3022
|
-
inputTokens = usageMetadata.promptTokenCount;
|
|
2993
|
+
return await withGeneration(describeGeminiRequest(this.providerName, {
|
|
2994
|
+
model: modelName,
|
|
2995
|
+
contents,
|
|
2996
|
+
config,
|
|
2997
|
+
}), async (span) => {
|
|
2998
|
+
const synth = await withTimeout((async () => {
|
|
2999
|
+
const stream = await client.models.generateContentStream({
|
|
3000
|
+
model: modelName,
|
|
3001
|
+
contents,
|
|
3002
|
+
config: synthConfig,
|
|
3003
|
+
});
|
|
3004
|
+
const parts = [];
|
|
3005
|
+
let finishReason;
|
|
3006
|
+
let inputTokens = 0;
|
|
3007
|
+
let outputTokens = 0;
|
|
3008
|
+
let reasoningTokens = 0;
|
|
3009
|
+
for await (const chunk of stream) {
|
|
3010
|
+
const chunkRecord = chunk;
|
|
3011
|
+
const candidates = chunkRecord.candidates;
|
|
3012
|
+
const firstCandidate = candidates?.[0];
|
|
3013
|
+
const chunkFinishReason = firstCandidate?.finishReason;
|
|
3014
|
+
if (typeof chunkFinishReason === "string" &&
|
|
3015
|
+
chunkFinishReason) {
|
|
3016
|
+
finishReason = chunkFinishReason;
|
|
3023
3017
|
}
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3018
|
+
const chunkContent = firstCandidate?.content;
|
|
3019
|
+
if (chunkContent && Array.isArray(chunkContent.parts)) {
|
|
3020
|
+
parts.push(...chunkContent.parts);
|
|
3027
3021
|
}
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3022
|
+
const usageMetadata = chunkRecord.usageMetadata;
|
|
3023
|
+
if (usageMetadata) {
|
|
3024
|
+
if (usageMetadata.promptTokenCount !== undefined &&
|
|
3025
|
+
usageMetadata.promptTokenCount > 0) {
|
|
3026
|
+
inputTokens = usageMetadata.promptTokenCount;
|
|
3027
|
+
}
|
|
3028
|
+
if (usageMetadata.candidatesTokenCount !== undefined &&
|
|
3029
|
+
usageMetadata.candidatesTokenCount > 0) {
|
|
3030
|
+
outputTokens = usageMetadata.candidatesTokenCount;
|
|
3031
|
+
}
|
|
3032
|
+
if (usageMetadata.thoughtsTokenCount !== undefined &&
|
|
3033
|
+
usageMetadata.thoughtsTokenCount > 0) {
|
|
3034
|
+
reasoningTokens = usageMetadata.thoughtsTokenCount;
|
|
3035
|
+
}
|
|
3031
3036
|
}
|
|
3032
3037
|
}
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
};
|
|
3045
|
-
|
|
3038
|
+
const text = parts
|
|
3039
|
+
.filter((part) => typeof part.text === "string")
|
|
3040
|
+
.map((part) => part.text)
|
|
3041
|
+
.join("");
|
|
3042
|
+
return {
|
|
3043
|
+
text,
|
|
3044
|
+
finishReason,
|
|
3045
|
+
inputTokens,
|
|
3046
|
+
outputTokens,
|
|
3047
|
+
reasoningTokens,
|
|
3048
|
+
};
|
|
3049
|
+
})(), timeoutMs, "Gemini synthesis call timed out");
|
|
3050
|
+
observeGeneration(span, {
|
|
3051
|
+
provider: this.providerName,
|
|
3052
|
+
model: modelName,
|
|
3053
|
+
output: synth.text,
|
|
3054
|
+
usage: {
|
|
3055
|
+
inputTokens: synth.inputTokens,
|
|
3056
|
+
outputTokens: synth.outputTokens,
|
|
3057
|
+
reasoningTokens: synth.reasoningTokens,
|
|
3058
|
+
},
|
|
3059
|
+
finishReason: synth.finishReason,
|
|
3060
|
+
});
|
|
3061
|
+
return synth;
|
|
3062
|
+
});
|
|
3046
3063
|
}
|
|
3047
3064
|
catch (error) {
|
|
3048
3065
|
logger.warn("[GoogleVertex] Tools-disabled synthesis call failed; falling back to placeholder", { error: error instanceof Error ? error.message : String(error) });
|
|
@@ -3466,24 +3483,18 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
3466
3483
|
const turnSpan = tracers.provider.startSpan("anthropic.vertex.stream", {
|
|
3467
3484
|
kind: SpanKind.CLIENT,
|
|
3468
3485
|
attributes: {
|
|
3469
|
-
|
|
3470
|
-
// from the gen_ai.* attributes; the model calls live in child spans.
|
|
3471
|
-
[LANGFUSE_ATTR.OBSERVATION_TYPE]: "span",
|
|
3472
|
-
[ATTR.GEN_AI_SYSTEM]: "anthropic",
|
|
3473
|
-
[ATTR.GEN_AI_MODEL]: modelName,
|
|
3474
|
-
[ATTR.GEN_AI_OPERATION]: "stream",
|
|
3486
|
+
[ATTR.NL_MODEL]: modelName,
|
|
3475
3487
|
[ATTR.NL_PROVIDER]: this.providerName,
|
|
3476
3488
|
[ATTR.NL_TOOL_COUNT]: offeredToolNames.length,
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
}),
|
|
3489
|
+
},
|
|
3490
|
+
});
|
|
3491
|
+
observe(turnSpan, "span", {
|
|
3492
|
+
input: turnInputAttribute,
|
|
3493
|
+
metadata: {
|
|
3494
|
+
toolsOffered: offeredToolNames,
|
|
3495
|
+
toolCount: offeredToolNames.length,
|
|
3496
|
+
maxSteps,
|
|
3497
|
+
structuredOutput: useFinalResultTool,
|
|
3487
3498
|
},
|
|
3488
3499
|
});
|
|
3489
3500
|
const turnContext = otelTrace.setSpan(otelContext.active(), turnSpan);
|
|
@@ -3543,7 +3554,6 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
3543
3554
|
const loopPromise = (async () => {
|
|
3544
3555
|
let step = 0;
|
|
3545
3556
|
const currentMessages = [...messages];
|
|
3546
|
-
let stepConversation = currentMessages;
|
|
3547
3557
|
// Step-cap / abort bookkeeping (mirrors the generate twin): read by the
|
|
3548
3558
|
// terminal recovery block after the loop and by the finishReason
|
|
3549
3559
|
// mapping written into finishReasonRef.
|
|
@@ -3558,71 +3568,6 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
3558
3568
|
const contextGuard = createContextGuard(getContextWindowSize("vertex", modelName));
|
|
3559
3569
|
const failedTools = new Map();
|
|
3560
3570
|
try {
|
|
3561
|
-
// Restores the per-tool observation the hand-rolled dispatch had.
|
|
3562
|
-
// The execution runs INSIDE the span's context so spans the tool opens
|
|
3563
|
-
// itself nest under this call rather than dangling beside the turn, and
|
|
3564
|
-
// the settled RESULT is inspected — an MCP tool reports failure in its
|
|
3565
|
-
// payload, so an observation that only watches for throws records a
|
|
3566
|
-
// failed call as successful.
|
|
3567
|
-
//
|
|
3568
|
-
// Stream path only: the generate twin never had per-tool spans, and
|
|
3569
|
-
// giving it them here would be behaviour GAINED under cover of a
|
|
3570
|
-
// migration. Shared with resolveToolOnMiss below: the old dispatch
|
|
3571
|
-
// opened the span before the executor lookup, so a tool hydrated
|
|
3572
|
-
// mid-turn was observed exactly like one declared up front.
|
|
3573
|
-
const withToolSpan = (toolCallName, run, call) => {
|
|
3574
|
-
// Arguments are inputs: honour the same opt-out the AI SDK path uses.
|
|
3575
|
-
const argsAttribute = call?.args !== undefined &&
|
|
3576
|
-
process.env.NEUROLINK_RECORD_INPUTS?.trim().toLowerCase() !==
|
|
3577
|
-
"false"
|
|
3578
|
-
? spanContentAttribute(call.args)
|
|
3579
|
-
: undefined;
|
|
3580
|
-
const toolSpan = tracers.mcp.startSpan("ai.toolCall", {
|
|
3581
|
-
kind: SpanKind.INTERNAL,
|
|
3582
|
-
attributes: {
|
|
3583
|
-
[LANGFUSE_ATTR.OBSERVATION_TYPE]: "tool",
|
|
3584
|
-
[ATTR.GEN_AI_TOOL_NAME]: toolCallName,
|
|
3585
|
-
"ai.toolCall.name": toolCallName,
|
|
3586
|
-
...(call?.toolCallId
|
|
3587
|
-
? { "ai.toolCall.id": call.toolCallId }
|
|
3588
|
-
: {}),
|
|
3589
|
-
...(argsAttribute !== undefined
|
|
3590
|
-
? {
|
|
3591
|
-
"ai.toolCall.args": argsAttribute,
|
|
3592
|
-
[LANGFUSE_ATTR.OBSERVATION_INPUT]: argsAttribute,
|
|
3593
|
-
}
|
|
3594
|
-
: {}),
|
|
3595
|
-
},
|
|
3596
|
-
}, turnContext);
|
|
3597
|
-
const finish = (output, errorMessage) => {
|
|
3598
|
-
const outputAttribute = spanContentAttribute(output);
|
|
3599
|
-
toolSpan.setAttribute("ai.toolCall.result", outputAttribute);
|
|
3600
|
-
toolSpan.setAttribute(LANGFUSE_ATTR.OBSERVATION_OUTPUT, outputAttribute);
|
|
3601
|
-
if (errorMessage) {
|
|
3602
|
-
toolSpan.setAttribute(LANGFUSE_ATTR.OBSERVATION_LEVEL, "ERROR");
|
|
3603
|
-
toolSpan.setAttribute(LANGFUSE_ATTR.OBSERVATION_STATUS_MESSAGE, errorMessage);
|
|
3604
|
-
toolSpan.setStatus({
|
|
3605
|
-
code: SpanStatusCode.ERROR,
|
|
3606
|
-
message: errorMessage,
|
|
3607
|
-
});
|
|
3608
|
-
}
|
|
3609
|
-
else {
|
|
3610
|
-
toolSpan.setStatus({ code: SpanStatusCode.OK });
|
|
3611
|
-
}
|
|
3612
|
-
toolSpan.end();
|
|
3613
|
-
};
|
|
3614
|
-
return otelContext.with(otelTrace.setSpan(turnContext, toolSpan), async () => {
|
|
3615
|
-
try {
|
|
3616
|
-
const result = await run();
|
|
3617
|
-
finish(result, extractMcpToolErrorMessage(result));
|
|
3618
|
-
return result;
|
|
3619
|
-
}
|
|
3620
|
-
catch (error) {
|
|
3621
|
-
finish({ error: true }, error instanceof Error ? error.message : String(error));
|
|
3622
|
-
throw error;
|
|
3623
|
-
}
|
|
3624
|
-
});
|
|
3625
|
-
};
|
|
3626
3571
|
// Executors handed to the engine, taken through the turn's
|
|
3627
3572
|
// DedupExecuteMap so an identical repeated call is answered from the
|
|
3628
3573
|
// per-turn cache rather than run again (BZ-3327). `.get()` returns the
|
|
@@ -3644,17 +3589,9 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
3644
3589
|
toolTimeoutMs: toolExecTimeoutMs,
|
|
3645
3590
|
abortSignal: internalAbort.signal,
|
|
3646
3591
|
onProgress: () => turnClock.noteProgress(),
|
|
3647
|
-
withToolSpan,
|
|
3648
3592
|
}),
|
|
3649
3593
|
};
|
|
3650
3594
|
}
|
|
3651
|
-
// The turn runs on the shared engine. The step cap, tool dispatch, the
|
|
3652
|
-
// failure breaker, per-step usage accumulation and the pre-first-chunk
|
|
3653
|
-
// provider retry all live there now. What stays here is everything the
|
|
3654
|
-
// engine has no opinion about: the turn clock, the context guard, the
|
|
3655
|
-
// per-step Langfuse generation span, conversation-memory storage, the
|
|
3656
|
-
// wrap-up nudge, and the reserved finalization in the terminal block.
|
|
3657
|
-
//
|
|
3658
3595
|
// maxSteps is agenticStepBudget, NOT maxSteps: when structured output is
|
|
3659
3596
|
// active the last slot is reserved for the forced final_result call, and
|
|
3660
3597
|
// the engine must never spend it.
|
|
@@ -3662,6 +3599,7 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
3662
3599
|
// MessageParam and every hook here is typed against the wrong shape.
|
|
3663
3600
|
const baseAdapter = createAnthropicLoopAdapter({
|
|
3664
3601
|
client,
|
|
3602
|
+
provider: this.providerName,
|
|
3665
3603
|
maxSteps: agenticStepBudget,
|
|
3666
3604
|
toolsRecord: options.tools ?? {},
|
|
3667
3605
|
// Set here and NOT for native Anthropic: these loops have always had
|
|
@@ -3751,15 +3689,10 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
3751
3689
|
}
|
|
3752
3690
|
: {}),
|
|
3753
3691
|
});
|
|
3754
|
-
// Wrapped rather than configured: both of these fire once PER STEP, and
|
|
3755
|
-
// these are the only hooks that see a single step's request and results.
|
|
3756
|
-
// Reading them off the turn's final result would batch every step into
|
|
3757
|
-
// one late write and lose the per-step generation span entirely.
|
|
3758
3692
|
const adapter = {
|
|
3759
3693
|
...baseAdapter,
|
|
3760
3694
|
buildStepRequest: (conversation, engineStep) => {
|
|
3761
3695
|
step = engineStep + 1;
|
|
3762
|
-
stepConversation = conversation;
|
|
3763
3696
|
turnClock.noteProgress();
|
|
3764
3697
|
return baseAdapter.buildStepRequest(conversation, engineStep);
|
|
3765
3698
|
},
|
|
@@ -3779,79 +3712,21 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
3779
3712
|
toolTimeoutMs: toolExecTimeoutMs,
|
|
3780
3713
|
abortSignal: internalAbort.signal,
|
|
3781
3714
|
onProgress: () => turnClock.noteProgress(),
|
|
3782
|
-
// Same observation as an up-front executor: the hand-rolled
|
|
3783
|
-
// dispatch spanned at the call, after the executor lookup, so
|
|
3784
|
-
// a hydrated tool was never the one unobserved call in a turn.
|
|
3785
|
-
withToolSpan,
|
|
3786
3715
|
}),
|
|
3787
3716
|
};
|
|
3788
3717
|
},
|
|
3789
3718
|
executeStep: async (request, stepChannel, signal) => {
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
kind: SpanKind.CLIENT,
|
|
3795
|
-
attributes: {
|
|
3796
|
-
[LANGFUSE_ATTR.OBSERVATION_TYPE]: "generation",
|
|
3797
|
-
[LANGFUSE_ATTR.OBSERVATION_MODEL_NAME]: modelName,
|
|
3798
|
-
[LANGFUSE_ATTR.OBSERVATION_MODEL_PARAMETERS]: spanJsonAttribute({
|
|
3799
|
-
max_tokens: requestParams.max_tokens,
|
|
3800
|
-
temperature: requestParams.temperature,
|
|
3801
|
-
top_p: requestParams.top_p,
|
|
3802
|
-
}),
|
|
3803
|
-
[LANGFUSE_ATTR.OBSERVATION_INPUT]: spanModelRequestAttribute({
|
|
3804
|
-
system: systemPromptWithSchema,
|
|
3805
|
-
messages: sanitizeAnthropicMessagesForTrace(stepConversation),
|
|
3806
|
-
}),
|
|
3807
|
-
[LANGFUSE_ATTR.OBSERVATION_METADATA]: spanJsonAttribute({
|
|
3808
|
-
step,
|
|
3809
|
-
toolsOffered: offeredToolNames.length,
|
|
3810
|
-
}),
|
|
3811
|
-
[ATTR.GEN_AI_SYSTEM]: "anthropic",
|
|
3812
|
-
[ATTR.GEN_AI_MODEL]: modelName,
|
|
3813
|
-
[ATTR.GEN_AI_OPERATION]: "chat",
|
|
3719
|
+
const result = await baseAdapter.executeStep(request, {
|
|
3720
|
+
push: (chunk) => {
|
|
3721
|
+
turnClock.noteProgress();
|
|
3722
|
+
stepChannel.push(chunk);
|
|
3814
3723
|
},
|
|
3815
|
-
},
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
if (chunk.content && !firstDeltaSeen) {
|
|
3822
|
-
firstDeltaSeen = true;
|
|
3823
|
-
// Time-to-first-token for this generation.
|
|
3824
|
-
generationSpan.setAttribute(LANGFUSE_ATTR.OBSERVATION_COMPLETION_START_TIME, new Date().toISOString());
|
|
3825
|
-
}
|
|
3826
|
-
stepChannel.push(chunk);
|
|
3827
|
-
},
|
|
3828
|
-
}, signal);
|
|
3829
|
-
turnCacheUsage.read += result.usage.cacheReadTokens ?? 0;
|
|
3830
|
-
turnCacheUsage.creation += result.usage.cacheWriteTokens ?? 0;
|
|
3831
|
-
turnCacheUsage.creation5m += result.usage.cacheWrite5mTokens ?? 0;
|
|
3832
|
-
turnCacheUsage.creation1h += result.usage.cacheWrite1hTokens ?? 0;
|
|
3833
|
-
generationSpan.setAttribute(LANGFUSE_ATTR.OBSERVATION_OUTPUT, spanContentAttribute({
|
|
3834
|
-
text: result.text,
|
|
3835
|
-
...(result.toolCalls.length > 0
|
|
3836
|
-
? { toolCalls: result.toolCalls }
|
|
3837
|
-
: {}),
|
|
3838
|
-
}));
|
|
3839
|
-
generationSpan.setAttribute(`${LANGFUSE_ATTR.OBSERVATION_METADATA}.finishReason`, result.rawStopReason ?? "unknown");
|
|
3840
|
-
return result;
|
|
3841
|
-
}
|
|
3842
|
-
catch (error) {
|
|
3843
|
-
generationSpan.setStatus({
|
|
3844
|
-
code: SpanStatusCode.ERROR,
|
|
3845
|
-
message: error instanceof Error ? error.message : String(error),
|
|
3846
|
-
});
|
|
3847
|
-
if (error instanceof Error) {
|
|
3848
|
-
generationSpan.recordException(error);
|
|
3849
|
-
}
|
|
3850
|
-
throw error;
|
|
3851
|
-
}
|
|
3852
|
-
finally {
|
|
3853
|
-
generationSpan.end();
|
|
3854
|
-
}
|
|
3724
|
+
}, signal);
|
|
3725
|
+
turnCacheUsage.read += result.usage.cacheReadTokens ?? 0;
|
|
3726
|
+
turnCacheUsage.creation += result.usage.cacheWriteTokens ?? 0;
|
|
3727
|
+
turnCacheUsage.creation5m += result.usage.cacheWrite5mTokens ?? 0;
|
|
3728
|
+
turnCacheUsage.creation1h += result.usage.cacheWrite1hTokens ?? 0;
|
|
3729
|
+
return result;
|
|
3855
3730
|
},
|
|
3856
3731
|
buildToolResultMessages: (conversation, stepResult, toolResults, engineStep) => {
|
|
3857
3732
|
for (const result of toolResults) {
|
|
@@ -3905,15 +3780,14 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
3905
3780
|
return next;
|
|
3906
3781
|
},
|
|
3907
3782
|
};
|
|
3908
|
-
const
|
|
3909
|
-
const { stream: engineStream, resultPromise } = runAgenticLoop(adapter, currentMessages.slice(), {
|
|
3783
|
+
const { stream: engineStream, resultPromise } = otelContext.with(turnContext, () => runAgenticLoop(adapter, currentMessages.slice(), {
|
|
3910
3784
|
tools: engineTools,
|
|
3911
3785
|
abortSignal: internalAbort.signal,
|
|
3912
|
-
|
|
3786
|
+
span: turnSpan,
|
|
3913
3787
|
// Same value `guardToolExecutor` already received above, so the
|
|
3914
3788
|
// engine's own per-tool bound cannot undercut it.
|
|
3915
3789
|
toolTimeoutMs: toolExecTimeoutMs,
|
|
3916
|
-
});
|
|
3790
|
+
}));
|
|
3917
3791
|
const pump = (async () => {
|
|
3918
3792
|
for await (const chunk of engineStream) {
|
|
3919
3793
|
if (chunk.reasoning) {
|
|
@@ -4050,33 +3924,44 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
4050
3924
|
tools,
|
|
4051
3925
|
messages: currentMessages,
|
|
4052
3926
|
});
|
|
4053
|
-
const
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
tool_choice: {
|
|
4061
|
-
type: "tool",
|
|
4062
|
-
name: "final_result",
|
|
4063
|
-
},
|
|
4064
|
-
}),
|
|
4065
|
-
...(cachedFinal.system !== undefined && {
|
|
4066
|
-
system: cachedFinal.system,
|
|
4067
|
-
}),
|
|
4068
|
-
...(cachedFinal.tools &&
|
|
4069
|
-
cachedFinal.tools.length > 0 && {
|
|
4070
|
-
tools: cachedFinal.tools,
|
|
4071
|
-
}),
|
|
3927
|
+
const response = await this.withClaudeGeneration({
|
|
3928
|
+
stepNumber: maxSteps + 1,
|
|
3929
|
+
model: requestParams.model,
|
|
3930
|
+
maxTokens: terminalMaxTokens,
|
|
3931
|
+
temperature: requestParams.temperature,
|
|
3932
|
+
topP: requestParams.top_p,
|
|
3933
|
+
system: cachedFinal.system,
|
|
4072
3934
|
messages: cachedFinal.messages,
|
|
3935
|
+
}, async () => {
|
|
3936
|
+
const finalizationStream = await client.messages.stream({
|
|
3937
|
+
...requestParams,
|
|
3938
|
+
max_tokens: terminalMaxTokens,
|
|
3939
|
+
// Resized, not inherited: terminalMaxTokens can sit below the
|
|
3940
|
+
// budget requestParams' thinking was sized for.
|
|
3941
|
+
thinking: this.buildClaudeThinkingParam(options, terminalMaxTokens, forceFinalResult),
|
|
3942
|
+
...(forceFinalResult && {
|
|
3943
|
+
tool_choice: {
|
|
3944
|
+
type: "tool",
|
|
3945
|
+
name: "final_result",
|
|
3946
|
+
},
|
|
3947
|
+
}),
|
|
3948
|
+
...(cachedFinal.system !== undefined && {
|
|
3949
|
+
system: cachedFinal.system,
|
|
3950
|
+
}),
|
|
3951
|
+
...(cachedFinal.tools &&
|
|
3952
|
+
cachedFinal.tools.length > 0 && {
|
|
3953
|
+
tools: cachedFinal.tools,
|
|
3954
|
+
}),
|
|
3955
|
+
messages: cachedFinal.messages,
|
|
3956
|
+
});
|
|
3957
|
+
// Mid-flight aborts are covered by the shared abortHandler via
|
|
3958
|
+
// activeStream; already-fired aborts were handled by the
|
|
3959
|
+
// terminal-entry re-check above.
|
|
3960
|
+
activeStream = finalizationStream;
|
|
3961
|
+
const message = await finalizationStream.finalMessage();
|
|
3962
|
+
activeStream = undefined;
|
|
3963
|
+
return message;
|
|
4073
3964
|
});
|
|
4074
|
-
// Mid-flight aborts are covered by the shared abortHandler via
|
|
4075
|
-
// activeStream; already-fired aborts were handled by the
|
|
4076
|
-
// terminal-entry re-check above.
|
|
4077
|
-
activeStream = finalizationStream;
|
|
4078
|
-
const response = await finalizationStream.finalMessage();
|
|
4079
|
-
activeStream = undefined;
|
|
4080
3965
|
usage.input += response.usage?.input_tokens || 0;
|
|
4081
3966
|
usage.output += response.usage?.output_tokens || 0;
|
|
4082
3967
|
turnCacheUsage.read +=
|
|
@@ -4184,26 +4069,37 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
4184
4069
|
// is rebuilt for the shrunk limit rather than inherited
|
|
4185
4070
|
// through the spread below.
|
|
4186
4071
|
const backstopThinking = this.buildClaudeThinkingParam(options, terminalMaxTokens, false);
|
|
4187
|
-
const
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
// backstopThinking is truthy would leave that stale value
|
|
4194
|
-
// in place instead of clearing it.
|
|
4195
|
-
thinking: backstopThinking,
|
|
4196
|
-
tool_choice: { type: "none" },
|
|
4072
|
+
const response = await this.withClaudeGeneration({
|
|
4073
|
+
stepNumber: maxSteps + 1,
|
|
4074
|
+
model: requestParams.model,
|
|
4075
|
+
maxTokens: terminalMaxTokens,
|
|
4076
|
+
temperature: requestParams.temperature,
|
|
4077
|
+
topP: requestParams.top_p,
|
|
4197
4078
|
system: cachedBackstop.system,
|
|
4198
|
-
...(cachedBackstop.tools &&
|
|
4199
|
-
cachedBackstop.tools.length > 0 && {
|
|
4200
|
-
tools: cachedBackstop.tools,
|
|
4201
|
-
}),
|
|
4202
4079
|
messages: cachedBackstop.messages,
|
|
4080
|
+
}, async () => {
|
|
4081
|
+
const backstopStream = await client.messages.stream({
|
|
4082
|
+
...requestParams,
|
|
4083
|
+
max_tokens: terminalMaxTokens,
|
|
4084
|
+
// Assigned outright, never a conditional spread: spreading
|
|
4085
|
+
// requestParams above may already carry a `thinking` sized
|
|
4086
|
+
// for the original max_tokens, and only adding a key when
|
|
4087
|
+
// backstopThinking is truthy would leave that stale value
|
|
4088
|
+
// in place instead of clearing it.
|
|
4089
|
+
thinking: backstopThinking,
|
|
4090
|
+
tool_choice: { type: "none" },
|
|
4091
|
+
system: cachedBackstop.system,
|
|
4092
|
+
...(cachedBackstop.tools &&
|
|
4093
|
+
cachedBackstop.tools.length > 0 && {
|
|
4094
|
+
tools: cachedBackstop.tools,
|
|
4095
|
+
}),
|
|
4096
|
+
messages: cachedBackstop.messages,
|
|
4097
|
+
});
|
|
4098
|
+
activeStream = backstopStream;
|
|
4099
|
+
const message = await backstopStream.finalMessage();
|
|
4100
|
+
activeStream = undefined;
|
|
4101
|
+
return message;
|
|
4203
4102
|
});
|
|
4204
|
-
activeStream = backstopStream;
|
|
4205
|
-
const response = await backstopStream.finalMessage();
|
|
4206
|
-
activeStream = undefined;
|
|
4207
4103
|
usage.input += response.usage?.input_tokens || 0;
|
|
4208
4104
|
usage.output += response.usage?.output_tokens || 0;
|
|
4209
4105
|
turnCacheUsage.read +=
|
|
@@ -4363,53 +4259,59 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
4363
4259
|
if (turnCacheUsage.creation > 0) {
|
|
4364
4260
|
usage.cacheCreationTokens = turnCacheUsage.creation;
|
|
4365
4261
|
}
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
turnSpan.setAttribute(LANGFUSE_ATTR.OBSERVATION_OUTPUT, turnOutputAttribute);
|
|
4373
|
-
turnSpan.setAttribute(LANGFUSE_ATTR.TRACE_OUTPUT, turnOutputAttribute);
|
|
4374
|
-
// Turn usage is metadata-only (not usage_details) — see the note at the
|
|
4375
|
-
// top of this method on why it must not contribute to the cost rollup.
|
|
4376
|
-
turnSpan.setAttribute(LANGFUSE_ATTR.OBSERVATION_METADATA, spanJsonAttribute({
|
|
4377
|
-
toolsOffered: offeredToolNames,
|
|
4378
|
-
toolCount: offeredToolNames.length,
|
|
4379
|
-
maxSteps,
|
|
4380
|
-
steps: step,
|
|
4381
|
-
toolCallCount: metadata.totalToolExecutions,
|
|
4382
|
-
toolsCalled: toolsUsedRef.filter((name) => name !== "final_result"),
|
|
4383
|
-
structuredOutput: useFinalResultTool,
|
|
4384
|
-
...(stopReasonRef.value ? { stopReason: stopReasonRef.value } : {}),
|
|
4385
|
-
usage: {
|
|
4386
|
-
input: usage.input,
|
|
4387
|
-
output: usage.output,
|
|
4388
|
-
input_cached_tokens: turnCacheUsage.read,
|
|
4389
|
-
input_cache_creation: turnCacheUsage.creation,
|
|
4390
|
-
input_cache_creation_5m: turnCacheUsage.creation5m,
|
|
4391
|
-
input_cache_creation_1h: turnCacheUsage.creation1h,
|
|
4262
|
+
observe(turnSpan, "span", {
|
|
4263
|
+
output: {
|
|
4264
|
+
text: aggregatedTurnText,
|
|
4265
|
+
...(structuredOutputRef.value
|
|
4266
|
+
? { structuredOutput: structuredOutputRef.value }
|
|
4267
|
+
: {}),
|
|
4392
4268
|
},
|
|
4393
|
-
|
|
4269
|
+
// Turn usage is metadata-only (not usage_details) — see the note at the
|
|
4270
|
+
// top of this method on why it must not contribute to the cost rollup.
|
|
4271
|
+
metadata: {
|
|
4272
|
+
toolsOffered: offeredToolNames,
|
|
4273
|
+
toolCount: offeredToolNames.length,
|
|
4274
|
+
maxSteps,
|
|
4275
|
+
steps: step,
|
|
4276
|
+
toolCallCount: metadata.totalToolExecutions,
|
|
4277
|
+
toolsCalled: toolsUsedRef.filter((name) => name !== "final_result"),
|
|
4278
|
+
structuredOutput: useFinalResultTool,
|
|
4279
|
+
...(stopReasonRef.value ? { stopReason: stopReasonRef.value } : {}),
|
|
4280
|
+
usage: {
|
|
4281
|
+
input: usage.input,
|
|
4282
|
+
output: usage.output,
|
|
4283
|
+
input_cached_tokens: turnCacheUsage.read,
|
|
4284
|
+
input_cache_creation: turnCacheUsage.creation,
|
|
4285
|
+
input_cache_creation_5m: turnCacheUsage.creation5m,
|
|
4286
|
+
input_cache_creation_1h: turnCacheUsage.creation1h,
|
|
4287
|
+
},
|
|
4288
|
+
},
|
|
4289
|
+
});
|
|
4394
4290
|
// How the turn ended, on the trace itself, so an evaluator can select
|
|
4395
4291
|
// only turns that finished ("completed") and skip capped, timed-out,
|
|
4396
4292
|
// aborted and failed ones. A trace holding several turns keeps the last
|
|
4397
4293
|
// one's; each turn's own is in its observation metadata above.
|
|
4398
4294
|
if (stopReasonRef.value) {
|
|
4399
|
-
turnSpan
|
|
4295
|
+
observeTrace(turnSpan, {
|
|
4296
|
+
metadata: { stopReason: stopReasonRef.value },
|
|
4297
|
+
});
|
|
4400
4298
|
}
|
|
4401
4299
|
turnSpan.setStatus({ code: SpanStatusCode.OK });
|
|
4402
4300
|
channel.close();
|
|
4403
4301
|
}
|
|
4404
4302
|
catch (err) {
|
|
4405
|
-
turnSpan
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4303
|
+
observe(turnSpan, "span", {
|
|
4304
|
+
level: "ERROR",
|
|
4305
|
+
statusMessage: err instanceof Error ? err.message : String(err),
|
|
4306
|
+
metadata: {
|
|
4307
|
+
toolsOffered: offeredToolNames,
|
|
4308
|
+
toolCount: offeredToolNames.length,
|
|
4309
|
+
maxSteps,
|
|
4310
|
+
structuredOutput: useFinalResultTool,
|
|
4311
|
+
stopReason: "provider-error",
|
|
4312
|
+
},
|
|
4313
|
+
});
|
|
4314
|
+
observeTrace(turnSpan, { metadata: { stopReason: "provider-error" } });
|
|
4413
4315
|
turnSpan.setStatus({
|
|
4414
4316
|
code: SpanStatusCode.ERROR,
|
|
4415
4317
|
message: err instanceof Error ? err.message : String(err),
|
|
@@ -4785,7 +4687,7 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
4785
4687
|
let hitStepLimit = false;
|
|
4786
4688
|
let hitContextLimit = false;
|
|
4787
4689
|
let synthesizedFinalAnswer = false;
|
|
4788
|
-
|
|
4690
|
+
const modelFinished = false;
|
|
4789
4691
|
const currentMessages = [...messages];
|
|
4790
4692
|
// In-loop context guard: stop calling tools when the accumulated
|
|
4791
4693
|
// conversation approaches the model's real context window, instead of
|
|
@@ -4855,6 +4757,7 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
4855
4757
|
// engine must never spend it.
|
|
4856
4758
|
const baseAdapter = createAnthropicLoopAdapter({
|
|
4857
4759
|
client,
|
|
4760
|
+
provider: this.providerName,
|
|
4858
4761
|
maxSteps: agenticStepBudget,
|
|
4859
4762
|
toolsRecord: options.tools ?? {},
|
|
4860
4763
|
toolFailureBreaker: {
|
|
@@ -5130,7 +5033,15 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
5130
5033
|
tools,
|
|
5131
5034
|
messages: currentMessages,
|
|
5132
5035
|
});
|
|
5133
|
-
const response = await
|
|
5036
|
+
const response = await this.withClaudeGeneration({
|
|
5037
|
+
stepNumber: maxSteps + 1,
|
|
5038
|
+
model: requestParams.model,
|
|
5039
|
+
maxTokens: terminalMaxTokens,
|
|
5040
|
+
temperature: requestParams.temperature,
|
|
5041
|
+
topP: requestParams.top_p,
|
|
5042
|
+
system: cachedFinal.system,
|
|
5043
|
+
messages: cachedFinal.messages,
|
|
5044
|
+
}, () => withTimeout(client.messages.create({
|
|
5134
5045
|
...requestParams,
|
|
5135
5046
|
max_tokens: terminalMaxTokens,
|
|
5136
5047
|
// Resized, not inherited: terminalMaxTokens can sit below the
|
|
@@ -5150,7 +5061,7 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
5150
5061
|
tools: cachedFinal.tools,
|
|
5151
5062
|
}),
|
|
5152
5063
|
messages: cachedFinal.messages,
|
|
5153
|
-
}, { signal: internalAbort.signal }), generateTimeoutMs, "Anthropic finalization call timed out");
|
|
5064
|
+
}, { signal: internalAbort.signal }), generateTimeoutMs, "Anthropic finalization call timed out"));
|
|
5154
5065
|
totalInputTokens += response.usage?.input_tokens || 0;
|
|
5155
5066
|
totalOutputTokens += response.usage?.output_tokens || 0;
|
|
5156
5067
|
totalCacheReadTokens += response.usage?.cache_read_input_tokens || 0;
|
|
@@ -5242,7 +5153,15 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
5242
5153
|
// rebuilt for the shrunk limit rather than inherited through the
|
|
5243
5154
|
// spread below.
|
|
5244
5155
|
const backstopThinking = this.buildClaudeThinkingParam(options, terminalMaxTokens, false);
|
|
5245
|
-
const response = await
|
|
5156
|
+
const response = await this.withClaudeGeneration({
|
|
5157
|
+
stepNumber: maxSteps + 1,
|
|
5158
|
+
model: requestParams.model,
|
|
5159
|
+
maxTokens: terminalMaxTokens,
|
|
5160
|
+
temperature: requestParams.temperature,
|
|
5161
|
+
topP: requestParams.top_p,
|
|
5162
|
+
system: cachedBackstop.system,
|
|
5163
|
+
messages: cachedBackstop.messages,
|
|
5164
|
+
}, () => withTimeout(client.messages.create({
|
|
5246
5165
|
...requestParams,
|
|
5247
5166
|
max_tokens: terminalMaxTokens,
|
|
5248
5167
|
// Assigned outright, never a conditional spread: spreading
|
|
@@ -5258,7 +5177,7 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
5258
5177
|
tools: cachedBackstop.tools,
|
|
5259
5178
|
}),
|
|
5260
5179
|
messages: cachedBackstop.messages,
|
|
5261
|
-
}, { signal: internalAbort.signal }), generateTimeoutMs, "Anthropic backstop call timed out");
|
|
5180
|
+
}, { signal: internalAbort.signal }), generateTimeoutMs, "Anthropic backstop call timed out"));
|
|
5262
5181
|
totalInputTokens += response.usage?.input_tokens || 0;
|
|
5263
5182
|
totalOutputTokens += response.usage?.output_tokens || 0;
|
|
5264
5183
|
totalCacheReadTokens +=
|
|
@@ -5569,6 +5488,14 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
5569
5488
|
// For non-image models, call the parent stream method
|
|
5570
5489
|
return super.stream(optionsOrPrompt);
|
|
5571
5490
|
}
|
|
5491
|
+
observeMediaGeneration(model, run) {
|
|
5492
|
+
const call = { provider: this.providerName, model };
|
|
5493
|
+
return withGeneration(call, async (span) => {
|
|
5494
|
+
const result = await run();
|
|
5495
|
+
observeGeneration(span, { ...call, usage: result?.usage });
|
|
5496
|
+
return result;
|
|
5497
|
+
});
|
|
5498
|
+
}
|
|
5572
5499
|
/**
|
|
5573
5500
|
* Override generate to route ALL models to native SDKs
|
|
5574
5501
|
* No more @ai-sdk/google-vertex dependency
|
|
@@ -5585,12 +5512,11 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
5585
5512
|
// Vertex's generate() bypasses BaseProvider.generate entirely, so
|
|
5586
5513
|
// without this wrapping the provider span never gets emitted.
|
|
5587
5514
|
return withClientSpan({
|
|
5588
|
-
name: "
|
|
5515
|
+
name: "provider.generate",
|
|
5589
5516
|
tracer: tracers.provider,
|
|
5517
|
+
detached: true,
|
|
5590
5518
|
attributes: {
|
|
5591
|
-
[ATTR.
|
|
5592
|
-
[ATTR.GEN_AI_MODEL]: modelName,
|
|
5593
|
-
[ATTR.GEN_AI_OPERATION]: "generate",
|
|
5519
|
+
[ATTR.NL_MODEL]: modelName,
|
|
5594
5520
|
[ATTR.NL_PROVIDER]: this.providerName,
|
|
5595
5521
|
},
|
|
5596
5522
|
}, async (generateSpan) => {
|
|
@@ -5610,8 +5536,7 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
5610
5536
|
// of producing video bytes.
|
|
5611
5537
|
if (requestKind === "video") {
|
|
5612
5538
|
logger.info("[GoogleVertex] Routing video-mode generate to handleVideoGeneration", { model: modelName });
|
|
5613
|
-
const videoResult = await this.handleVideoGeneration(options, generateStartTime);
|
|
5614
|
-
this.attachUsageAndCostAttributes(generateSpan, modelName, videoResult?.usage);
|
|
5539
|
+
const videoResult = await this.observeMediaGeneration(modelName, () => this.handleVideoGeneration(options, generateStartTime));
|
|
5615
5540
|
this.emitGenerationEnd(modelName, videoResult, generateStartTime, true);
|
|
5616
5541
|
return videoResult;
|
|
5617
5542
|
}
|
|
@@ -5632,8 +5557,7 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
5632
5557
|
// gemini-3.1-flash-image-preview on the text path.
|
|
5633
5558
|
if (requestKind === "image") {
|
|
5634
5559
|
logger.info("[GoogleVertex] Routing image generation model to executeImageGeneration", { model: modelName });
|
|
5635
|
-
const imageResult = await this.executeImageGeneration(options);
|
|
5636
|
-
this.attachUsageAndCostAttributes(generateSpan, modelName, imageResult?.usage);
|
|
5560
|
+
const imageResult = await this.observeMediaGeneration(modelName, () => this.executeImageGeneration(options));
|
|
5637
5561
|
this.emitGenerationEnd(modelName, imageResult, generateStartTime, true);
|
|
5638
5562
|
return imageResult;
|
|
5639
5563
|
}
|
|
@@ -5654,7 +5578,7 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
5654
5578
|
// span here. Without this the test:tracing "Message Build Span"
|
|
5655
5579
|
// assertion has to skip on every native-Vertex run.
|
|
5656
5580
|
const processedOptions = await withSpan({
|
|
5657
|
-
name: "
|
|
5581
|
+
name: "message.build",
|
|
5658
5582
|
tracer: tracers.provider,
|
|
5659
5583
|
attributes: {
|
|
5660
5584
|
[ATTR.NL_PROVIDER]: this.providerName,
|
|
@@ -5681,7 +5605,7 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
5681
5605
|
: {}),
|
|
5682
5606
|
prompt: inputPrompt,
|
|
5683
5607
|
});
|
|
5684
|
-
generateSpan.setAttribute(
|
|
5608
|
+
generateSpan.setAttribute(LangfuseOtelSpanAttributes.OBSERVATION_INPUT, generationInputAttribute);
|
|
5685
5609
|
try {
|
|
5686
5610
|
let result;
|
|
5687
5611
|
// Wrap the actual native generate call in `neurolink.executeGeneration`
|
|
@@ -5690,15 +5614,15 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
5690
5614
|
// native @google/genai / @anthropic-ai/vertex-sdk path. This
|
|
5691
5615
|
// provider overrides generate(), so the span is added here.
|
|
5692
5616
|
result = await withSpan({
|
|
5693
|
-
name: "
|
|
5617
|
+
name: "executeGeneration",
|
|
5694
5618
|
tracer: tracers.provider,
|
|
5619
|
+
detached: true,
|
|
5695
5620
|
attributes: {
|
|
5696
|
-
[ATTR.
|
|
5697
|
-
|
|
5698
|
-
"neurolink.path": isAnthropicModel(modelName)
|
|
5621
|
+
[ATTR.NL_MODEL]: modelName,
|
|
5622
|
+
path: isAnthropicModel(modelName)
|
|
5699
5623
|
? "native.anthropic"
|
|
5700
5624
|
: "native.google-genai",
|
|
5701
|
-
[
|
|
5625
|
+
[LangfuseOtelSpanAttributes.OBSERVATION_INPUT]: generationInputAttribute,
|
|
5702
5626
|
},
|
|
5703
5627
|
}, async (executionSpan) => {
|
|
5704
5628
|
let nativeResult;
|
|
@@ -5716,16 +5640,15 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
5716
5640
|
});
|
|
5717
5641
|
nativeResult = await this.runNativeGenerateWithMiddleware(mergedOptions, modelName, (effectiveOptions) => this.executeNativeGemini3GenerateWithSchemaFallback(effectiveOptions, modelName));
|
|
5718
5642
|
}
|
|
5719
|
-
executionSpan.setAttribute(
|
|
5643
|
+
executionSpan.setAttribute(LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT, spanContentAttribute(nativeResult?.content ?? ""));
|
|
5720
5644
|
return nativeResult;
|
|
5721
5645
|
});
|
|
5722
|
-
this.attachUsageAndCostAttributes(generateSpan, modelName, result?.usage);
|
|
5723
5646
|
// Pipe through TTS-of-AI-response when caller asks for it. The
|
|
5724
5647
|
// shared `synthesizeAIResponseIfNeeded` no-ops when tts is not
|
|
5725
5648
|
// enabled / useAiResponse is false, so the cost is zero on
|
|
5726
5649
|
// non-TTS paths.
|
|
5727
5650
|
result = await this.synthesizeAIResponseIfNeeded(result, options);
|
|
5728
|
-
generateSpan.setAttribute(
|
|
5651
|
+
generateSpan.setAttribute(LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT, spanContentAttribute(result?.content ?? ""));
|
|
5729
5652
|
// Fire onFinish lifecycle callback for the native generate path.
|
|
5730
5653
|
// Pipeline A providers get this for free via the AI SDK middleware
|
|
5731
5654
|
// wrapper (LifecycleMiddleware); native @google/genai bypasses
|
|
@@ -6389,54 +6312,35 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
6389
6312
|
}
|
|
6390
6313
|
return wrapped;
|
|
6391
6314
|
}
|
|
6392
|
-
|
|
6393
|
-
|
|
6394
|
-
|
|
6395
|
-
|
|
6396
|
-
|
|
6397
|
-
|
|
6398
|
-
|
|
6399
|
-
|
|
6400
|
-
|
|
6401
|
-
|
|
6402
|
-
|
|
6403
|
-
|
|
6404
|
-
|
|
6405
|
-
|
|
6406
|
-
|
|
6407
|
-
|
|
6408
|
-
|
|
6409
|
-
|
|
6410
|
-
|
|
6411
|
-
|
|
6412
|
-
|
|
6413
|
-
|
|
6414
|
-
|
|
6415
|
-
|
|
6416
|
-
|
|
6417
|
-
|
|
6418
|
-
}
|
|
6419
|
-
if (cacheCreationTokens > 0) {
|
|
6420
|
-
span.setAttribute("gen_ai.usage.cache_creation_input_tokens", cacheCreationTokens);
|
|
6421
|
-
}
|
|
6422
|
-
try {
|
|
6423
|
-
// Pass cache tokens through so calculateCost prices the ~0.1x cache-read
|
|
6424
|
-
// and ~1.25x cache-write tiers instead of billing cached content at the
|
|
6425
|
-
// full input rate.
|
|
6426
|
-
const cost = calculateCost(this.providerName, modelName, {
|
|
6427
|
-
input: inputTokens,
|
|
6428
|
-
output: outputTokens,
|
|
6429
|
-
total: totalTokens,
|
|
6430
|
-
cacheReadTokens,
|
|
6431
|
-
cacheCreationTokens,
|
|
6315
|
+
withClaudeGeneration(sent, send) {
|
|
6316
|
+
const call = { provider: this.providerName, model: sent.model };
|
|
6317
|
+
return withStepSpan(sent.stepNumber, () => withGeneration({
|
|
6318
|
+
...call,
|
|
6319
|
+
modelParameters: {
|
|
6320
|
+
temperature: sent.temperature,
|
|
6321
|
+
maxTokens: sent.maxTokens,
|
|
6322
|
+
topP: sent.topP,
|
|
6323
|
+
},
|
|
6324
|
+
input: {
|
|
6325
|
+
system: sent.system,
|
|
6326
|
+
messages: sanitizeAnthropicMessagesForTrace(sent.messages),
|
|
6327
|
+
},
|
|
6328
|
+
}, async (span) => {
|
|
6329
|
+
const response = await send();
|
|
6330
|
+
observeGeneration(span, {
|
|
6331
|
+
...call,
|
|
6332
|
+
responseModel: response.model,
|
|
6333
|
+
output: response.content,
|
|
6334
|
+
usage: {
|
|
6335
|
+
inputTokens: response.usage?.input_tokens,
|
|
6336
|
+
outputTokens: response.usage?.output_tokens,
|
|
6337
|
+
cacheReadTokens: response.usage?.cache_read_input_tokens ?? undefined,
|
|
6338
|
+
cacheCreationTokens: response.usage?.cache_creation_input_tokens ?? undefined,
|
|
6339
|
+
},
|
|
6340
|
+
finishReason: response.stop_reason ?? undefined,
|
|
6432
6341
|
});
|
|
6433
|
-
|
|
6434
|
-
|
|
6435
|
-
}
|
|
6436
|
-
}
|
|
6437
|
-
catch {
|
|
6438
|
-
// Pricing table miss is not fatal — leave the attribute unset.
|
|
6439
|
-
}
|
|
6342
|
+
return response;
|
|
6343
|
+
}));
|
|
6440
6344
|
}
|
|
6441
6345
|
/**
|
|
6442
6346
|
* Emit `generation:end` so the Pipeline B observability listener creates
|