@jaypie/llm 1.2.38 → 1.2.40
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/dist/cjs/index.cjs +322 -57
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/observability/llmobs.d.ts +72 -0
- package/dist/esm/index.js +321 -57
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/observability/llmobs.d.ts +72 -0
- package/package.json +1 -1
package/dist/cjs/index.cjs
CHANGED
|
@@ -7,12 +7,15 @@ var kit = require('@jaypie/kit');
|
|
|
7
7
|
var RandomLib = require('random');
|
|
8
8
|
var openai = require('openai');
|
|
9
9
|
var zod = require('openai/helpers/zod');
|
|
10
|
+
var module$1 = require('module');
|
|
11
|
+
var url = require('url');
|
|
10
12
|
var pdfLib = require('pdf-lib');
|
|
11
13
|
var promises = require('fs/promises');
|
|
12
14
|
var path = require('path');
|
|
13
15
|
var aws = require('@jaypie/aws');
|
|
14
16
|
var openmeteo = require('openmeteo');
|
|
15
17
|
|
|
18
|
+
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
16
19
|
const FIRST_CLASS_PROVIDER = {
|
|
17
20
|
// https://docs.anthropic.com/en/docs/about-claude/models/overview
|
|
18
21
|
ANTHROPIC: {
|
|
@@ -56,8 +59,8 @@ const MODEL = {
|
|
|
56
59
|
GEMINI_PRO: "gemini-3.1-pro-preview",
|
|
57
60
|
// OpenAI
|
|
58
61
|
GPT: "gpt-5.5",
|
|
59
|
-
GPT_MINI: "gpt-5.
|
|
60
|
-
GPT_NANO: "gpt-5.
|
|
62
|
+
GPT_MINI: "gpt-5.4-mini",
|
|
63
|
+
GPT_NANO: "gpt-5.4-nano",
|
|
61
64
|
// xAI
|
|
62
65
|
GROK: "grok-latest",
|
|
63
66
|
};
|
|
@@ -2070,8 +2073,7 @@ class BedrockAdapter extends BaseProviderAdapter {
|
|
|
2070
2073
|
if (request.system) {
|
|
2071
2074
|
bedrockRequest.system = [{ text: request.system }];
|
|
2072
2075
|
}
|
|
2073
|
-
if (request.temperature !== undefined &&
|
|
2074
|
-
this.supportsTemperature(model)) {
|
|
2076
|
+
if (request.temperature !== undefined && this.supportsTemperature(model)) {
|
|
2075
2077
|
bedrockRequest.inferenceConfig = {
|
|
2076
2078
|
...bedrockRequest.inferenceConfig,
|
|
2077
2079
|
temperature: request.temperature,
|
|
@@ -2212,10 +2214,7 @@ class BedrockAdapter extends BaseProviderAdapter {
|
|
|
2212
2214
|
return {
|
|
2213
2215
|
...rest,
|
|
2214
2216
|
toolConfig: {
|
|
2215
|
-
tools: [
|
|
2216
|
-
...(rest.toolConfig?.tools ?? []),
|
|
2217
|
-
fakeTool,
|
|
2218
|
-
],
|
|
2217
|
+
tools: [...(rest.toolConfig?.tools ?? []), fakeTool],
|
|
2219
2218
|
},
|
|
2220
2219
|
};
|
|
2221
2220
|
}
|
|
@@ -2439,7 +2438,8 @@ class BedrockAdapter extends BaseProviderAdapter {
|
|
|
2439
2438
|
return this.extractStructuredOutput(response) !== undefined;
|
|
2440
2439
|
}
|
|
2441
2440
|
// Fake-tool path: last content block is a structured_output toolUse
|
|
2442
|
-
const content = (bedrockResponse.output?.message?.content ??
|
|
2441
|
+
const content = (bedrockResponse.output?.message?.content ??
|
|
2442
|
+
[]);
|
|
2443
2443
|
const last = content[content.length - 1];
|
|
2444
2444
|
return (!!last &&
|
|
2445
2445
|
"toolUse" in last &&
|
|
@@ -2448,14 +2448,16 @@ class BedrockAdapter extends BaseProviderAdapter {
|
|
|
2448
2448
|
extractStructuredOutput(response) {
|
|
2449
2449
|
const bedrockResponse = response;
|
|
2450
2450
|
if (bedrockResponse.__jaypieStructuredOutput) {
|
|
2451
|
-
const content = (bedrockResponse.output?.message?.content ??
|
|
2451
|
+
const content = (bedrockResponse.output?.message?.content ??
|
|
2452
|
+
[]);
|
|
2452
2453
|
const textBlock = content.find((b) => "text" in b);
|
|
2453
2454
|
if (!textBlock)
|
|
2454
2455
|
return undefined;
|
|
2455
2456
|
return extractJson(textBlock.text);
|
|
2456
2457
|
}
|
|
2457
2458
|
// Fake-tool path
|
|
2458
|
-
const content = (bedrockResponse.output?.message?.content ??
|
|
2459
|
+
const content = (bedrockResponse.output?.message?.content ??
|
|
2460
|
+
[]);
|
|
2459
2461
|
const last = content[content.length - 1];
|
|
2460
2462
|
if (last &&
|
|
2461
2463
|
"toolUse" in last &&
|
|
@@ -4838,6 +4840,198 @@ class Toolkit {
|
|
|
4838
4840
|
}
|
|
4839
4841
|
}
|
|
4840
4842
|
|
|
4843
|
+
//
|
|
4844
|
+
//
|
|
4845
|
+
// Constants
|
|
4846
|
+
//
|
|
4847
|
+
const ENV = {
|
|
4848
|
+
DD_LLMOBS_ENABLED: "DD_LLMOBS_ENABLED",
|
|
4849
|
+
};
|
|
4850
|
+
const MODULE = {
|
|
4851
|
+
// Computed at runtime so bundlers (esbuild) do not attempt to include
|
|
4852
|
+
// dd-trace, which is provided by the Datadog Lambda layer at runtime.
|
|
4853
|
+
DD_TRACE: ["dd", "trace"].join("-"),
|
|
4854
|
+
};
|
|
4855
|
+
//
|
|
4856
|
+
//
|
|
4857
|
+
// Helpers
|
|
4858
|
+
//
|
|
4859
|
+
// CJS/ESM compatible require - handles bundling to CJS where import.meta.url
|
|
4860
|
+
// becomes undefined (mirrors packages/testkit/src/mock/original.ts).
|
|
4861
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
4862
|
+
// @ts-ignore - __filename exists in CJS context when ESM is bundled to CJS
|
|
4863
|
+
const requireModule = typeof __filename !== "undefined"
|
|
4864
|
+
? module$1.createRequire(url.pathToFileURL(__filename).href)
|
|
4865
|
+
: module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
|
|
4866
|
+
let resolved = false;
|
|
4867
|
+
let cachedSdk$4 = null;
|
|
4868
|
+
/**
|
|
4869
|
+
* Lazily resolve the dd-trace `llmobs` SDK. Returns null (and never throws)
|
|
4870
|
+
* when dd-trace is absent or the SDK surface is unexpected. Cached after the
|
|
4871
|
+
* first attempt.
|
|
4872
|
+
*/
|
|
4873
|
+
function resolveLlmObs() {
|
|
4874
|
+
if (resolved) {
|
|
4875
|
+
return cachedSdk$4;
|
|
4876
|
+
}
|
|
4877
|
+
resolved = true;
|
|
4878
|
+
try {
|
|
4879
|
+
const ddTrace = requireModule(MODULE.DD_TRACE);
|
|
4880
|
+
const tracer = ddTrace?.default ?? ddTrace;
|
|
4881
|
+
const llmobs = tracer?.llmobs;
|
|
4882
|
+
if (llmobs &&
|
|
4883
|
+
typeof llmobs.trace === "function" &&
|
|
4884
|
+
typeof llmobs.annotate === "function") {
|
|
4885
|
+
cachedSdk$4 = llmobs;
|
|
4886
|
+
}
|
|
4887
|
+
}
|
|
4888
|
+
catch {
|
|
4889
|
+
cachedSdk$4 = null;
|
|
4890
|
+
}
|
|
4891
|
+
return cachedSdk$4;
|
|
4892
|
+
}
|
|
4893
|
+
//
|
|
4894
|
+
//
|
|
4895
|
+
// Main
|
|
4896
|
+
//
|
|
4897
|
+
/**
|
|
4898
|
+
* True when LLM Observability emission is opted in via `DD_LLMOBS_ENABLED`.
|
|
4899
|
+
* Accepts any truthy value except "false"/"0".
|
|
4900
|
+
*/
|
|
4901
|
+
function isLlmObsEnabled() {
|
|
4902
|
+
const value = process.env[ENV.DD_LLMOBS_ENABLED];
|
|
4903
|
+
if (!value) {
|
|
4904
|
+
return false;
|
|
4905
|
+
}
|
|
4906
|
+
const normalized = value.trim().toLowerCase();
|
|
4907
|
+
return normalized !== "" && normalized !== "false" && normalized !== "0";
|
|
4908
|
+
}
|
|
4909
|
+
/** Resolve the SDK only when enabled and available. */
|
|
4910
|
+
function activeSdk() {
|
|
4911
|
+
if (!isLlmObsEnabled()) {
|
|
4912
|
+
return null;
|
|
4913
|
+
}
|
|
4914
|
+
return resolveLlmObs();
|
|
4915
|
+
}
|
|
4916
|
+
/**
|
|
4917
|
+
* Run `fn` inside an LLM Observability span. When emission is disabled or
|
|
4918
|
+
* dd-trace is absent, `fn` is invoked directly with no overhead. The active
|
|
4919
|
+
* span is established for the duration of `fn`, so nested `withLlmObsSpan`
|
|
4920
|
+
* calls (and `annotateLlmObs`) attach as children/annotations automatically.
|
|
4921
|
+
*/
|
|
4922
|
+
async function withLlmObsSpan(options, fn) {
|
|
4923
|
+
const sdk = activeSdk();
|
|
4924
|
+
if (!sdk) {
|
|
4925
|
+
return fn();
|
|
4926
|
+
}
|
|
4927
|
+
let started = false;
|
|
4928
|
+
try {
|
|
4929
|
+
return (await sdk.trace(options, () => {
|
|
4930
|
+
started = true;
|
|
4931
|
+
return fn();
|
|
4932
|
+
}));
|
|
4933
|
+
}
|
|
4934
|
+
catch (error) {
|
|
4935
|
+
// If `fn` ran, propagate its error (the SDK re-throws after finishing the
|
|
4936
|
+
// span). Only swallow failures from the instrumentation itself, re-running
|
|
4937
|
+
// `fn` so observability never breaks the underlying call.
|
|
4938
|
+
if (started) {
|
|
4939
|
+
throw error;
|
|
4940
|
+
}
|
|
4941
|
+
getLogger$6().warn("[llmobs] span emission failed");
|
|
4942
|
+
getLogger$6().var({ error });
|
|
4943
|
+
return fn();
|
|
4944
|
+
}
|
|
4945
|
+
}
|
|
4946
|
+
/**
|
|
4947
|
+
* Annotate the currently active LLM Observability span. No-op when disabled.
|
|
4948
|
+
*/
|
|
4949
|
+
function annotateLlmObs(annotation) {
|
|
4950
|
+
const sdk = activeSdk();
|
|
4951
|
+
if (!sdk) {
|
|
4952
|
+
return;
|
|
4953
|
+
}
|
|
4954
|
+
try {
|
|
4955
|
+
sdk.annotate(undefined, annotation);
|
|
4956
|
+
}
|
|
4957
|
+
catch (error) {
|
|
4958
|
+
getLogger$6().warn("[llmobs] annotate failed");
|
|
4959
|
+
getLogger$6().var({ error });
|
|
4960
|
+
}
|
|
4961
|
+
}
|
|
4962
|
+
/**
|
|
4963
|
+
* Open a manual span that is finished later. Used by streaming, where work is
|
|
4964
|
+
* yielded incrementally and cannot be enclosed in a single callback. The span
|
|
4965
|
+
* is kept open via a deferred promise resolved by `finish()`. Returns null when
|
|
4966
|
+
* emission is disabled or dd-trace is absent.
|
|
4967
|
+
*
|
|
4968
|
+
* Note: because the span is held open across `yield` boundaries (outside the
|
|
4969
|
+
* SDK's AsyncLocalStorage scope), manual spans are emitted flat rather than
|
|
4970
|
+
* nested. Annotations are applied to the explicit span reference.
|
|
4971
|
+
*/
|
|
4972
|
+
function openLlmObsSpan(options) {
|
|
4973
|
+
const sdk = activeSdk();
|
|
4974
|
+
if (!sdk) {
|
|
4975
|
+
return null;
|
|
4976
|
+
}
|
|
4977
|
+
try {
|
|
4978
|
+
let capturedSpan;
|
|
4979
|
+
let release;
|
|
4980
|
+
const held = new Promise((resolve) => {
|
|
4981
|
+
release = resolve;
|
|
4982
|
+
});
|
|
4983
|
+
// trace() runs the callback synchronously, capturing the span; the returned
|
|
4984
|
+
// pending promise keeps the span open until finish() resolves it.
|
|
4985
|
+
void sdk.trace(options, (span) => {
|
|
4986
|
+
capturedSpan = span;
|
|
4987
|
+
return held;
|
|
4988
|
+
});
|
|
4989
|
+
let finished = false;
|
|
4990
|
+
return {
|
|
4991
|
+
annotate: (annotation) => {
|
|
4992
|
+
try {
|
|
4993
|
+
sdk.annotate(capturedSpan, annotation);
|
|
4994
|
+
}
|
|
4995
|
+
catch (error) {
|
|
4996
|
+
getLogger$6().warn("[llmobs] annotate failed");
|
|
4997
|
+
getLogger$6().var({ error });
|
|
4998
|
+
}
|
|
4999
|
+
},
|
|
5000
|
+
finish: () => {
|
|
5001
|
+
if (finished) {
|
|
5002
|
+
return;
|
|
5003
|
+
}
|
|
5004
|
+
finished = true;
|
|
5005
|
+
release?.();
|
|
5006
|
+
},
|
|
5007
|
+
};
|
|
5008
|
+
}
|
|
5009
|
+
catch (error) {
|
|
5010
|
+
getLogger$6().warn("[llmobs] span emission failed");
|
|
5011
|
+
getLogger$6().var({ error });
|
|
5012
|
+
return null;
|
|
5013
|
+
}
|
|
5014
|
+
}
|
|
5015
|
+
//
|
|
5016
|
+
//
|
|
5017
|
+
// Mapping Helpers
|
|
5018
|
+
//
|
|
5019
|
+
/**
|
|
5020
|
+
* Sum an `LlmUsage` array into LLM Observability metric keys. Returns undefined
|
|
5021
|
+
* when there is no usage data so callers can omit the `metrics` field.
|
|
5022
|
+
*/
|
|
5023
|
+
function usageToLlmObsMetrics(usage) {
|
|
5024
|
+
if (!usage || usage.length === 0) {
|
|
5025
|
+
return undefined;
|
|
5026
|
+
}
|
|
5027
|
+
const metrics = usage.reduce((sum, item) => ({
|
|
5028
|
+
input_tokens: sum.input_tokens + (item.input ?? 0),
|
|
5029
|
+
output_tokens: sum.output_tokens + (item.output ?? 0),
|
|
5030
|
+
total_tokens: sum.total_tokens + (item.total ?? 0),
|
|
5031
|
+
}), { input_tokens: 0, output_tokens: 0, total_tokens: 0 });
|
|
5032
|
+
return metrics;
|
|
5033
|
+
}
|
|
5034
|
+
|
|
4841
5035
|
//
|
|
4842
5036
|
//
|
|
4843
5037
|
// Main
|
|
@@ -5709,30 +5903,46 @@ class OperateLoop {
|
|
|
5709
5903
|
// Initialize state
|
|
5710
5904
|
const state = await this.initializeState(input, options);
|
|
5711
5905
|
const context = this.createContext(options);
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
//
|
|
5715
|
-
|
|
5716
|
-
state.
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5906
|
+
const modelName = options.model ?? this.adapter.defaultModel;
|
|
5907
|
+
// Enclosing LLM Observability span (no-op when DD_LLMOBS_ENABLED is unset).
|
|
5908
|
+
// Child llm/tool spans nest under it via the SDK's active-span context.
|
|
5909
|
+
return withLlmObsSpan({
|
|
5910
|
+
kind: state.toolkit ? "agent" : "llm",
|
|
5911
|
+
modelName,
|
|
5912
|
+
modelProvider: this.adapter.name,
|
|
5913
|
+
name: "jaypie.llm.operate",
|
|
5914
|
+
}, async () => {
|
|
5915
|
+
// Build initial request
|
|
5916
|
+
let request = this.buildInitialRequest(state, options);
|
|
5917
|
+
// Multi-turn loop
|
|
5918
|
+
while (state.currentTurn < state.maxTurns) {
|
|
5919
|
+
state.currentTurn++;
|
|
5920
|
+
// Execute one turn with retry logic
|
|
5921
|
+
const shouldContinue = await this.executeOneTurn(request, state, context, options);
|
|
5922
|
+
if (!shouldContinue) {
|
|
5923
|
+
break;
|
|
5924
|
+
}
|
|
5925
|
+
// Rebuild request with updated history for next turn
|
|
5926
|
+
request = {
|
|
5927
|
+
format: state.formattedFormat,
|
|
5928
|
+
instructions: options.instructions,
|
|
5929
|
+
messages: state.currentInput,
|
|
5930
|
+
model: modelName,
|
|
5931
|
+
providerOptions: options.providerOptions,
|
|
5932
|
+
system: options.system,
|
|
5933
|
+
temperature: options.temperature,
|
|
5934
|
+
tools: state.formattedTools,
|
|
5935
|
+
user: options.user,
|
|
5936
|
+
};
|
|
5721
5937
|
}
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
|
|
5726
|
-
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
temperature: options.temperature,
|
|
5731
|
-
tools: state.formattedTools,
|
|
5732
|
-
user: options.user,
|
|
5733
|
-
};
|
|
5734
|
-
}
|
|
5735
|
-
return state.responseBuilder.build();
|
|
5938
|
+
const response = state.responseBuilder.build();
|
|
5939
|
+
annotateLlmObs({
|
|
5940
|
+
inputData: input,
|
|
5941
|
+
metrics: usageToLlmObsMetrics(response.usage),
|
|
5942
|
+
outputData: response.content,
|
|
5943
|
+
});
|
|
5944
|
+
return response;
|
|
5945
|
+
});
|
|
5736
5946
|
}
|
|
5737
5947
|
//
|
|
5738
5948
|
// Private Methods
|
|
@@ -5834,20 +6044,34 @@ class OperateLoop {
|
|
|
5834
6044
|
options,
|
|
5835
6045
|
providerRequest,
|
|
5836
6046
|
});
|
|
5837
|
-
// Execute with retry
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
6047
|
+
// Execute with retry inside a child llm span (no-op when llmobs disabled).
|
|
6048
|
+
// RetryExecutor handles error hooks and throws appropriate errors.
|
|
6049
|
+
const { parsed, response } = await withLlmObsSpan({
|
|
6050
|
+
kind: "llm",
|
|
6051
|
+
modelName: options.model ?? this.adapter.defaultModel,
|
|
6052
|
+
modelProvider: this.adapter.name,
|
|
6053
|
+
name: "jaypie.llm.model",
|
|
6054
|
+
}, async () => {
|
|
6055
|
+
const response = await retryExecutor.execute((signal) => this.adapter.executeRequest(this.client, providerRequest, signal), {
|
|
6056
|
+
context: {
|
|
6057
|
+
input: state.currentInput,
|
|
6058
|
+
options,
|
|
6059
|
+
providerRequest,
|
|
6060
|
+
},
|
|
6061
|
+
hooks: context.hooks,
|
|
6062
|
+
});
|
|
6063
|
+
// Log what was returned from the model
|
|
6064
|
+
log.trace("[operate] Model response received");
|
|
6065
|
+
log.var({ "operate.response": response });
|
|
6066
|
+
// Parse response
|
|
6067
|
+
const parsed = this.adapter.parseResponse(response, options);
|
|
6068
|
+
annotateLlmObs({
|
|
6069
|
+
inputData: state.currentInput,
|
|
6070
|
+
metrics: usageToLlmObsMetrics(parsed.usage ? [parsed.usage] : undefined),
|
|
6071
|
+
outputData: parsed.content ?? "",
|
|
6072
|
+
});
|
|
6073
|
+
return { parsed, response };
|
|
5845
6074
|
});
|
|
5846
|
-
// Log what was returned from the model
|
|
5847
|
-
log.trace("[operate] Model response received");
|
|
5848
|
-
log.var({ "operate.response": response });
|
|
5849
|
-
// Parse response
|
|
5850
|
-
const parsed = this.adapter.parseResponse(response, options);
|
|
5851
6075
|
// Track usage
|
|
5852
6076
|
if (parsed.usage) {
|
|
5853
6077
|
state.responseBuilder.addUsage(parsed.usage);
|
|
@@ -5892,11 +6116,19 @@ class OperateLoop {
|
|
|
5892
6116
|
args: toolCall.arguments,
|
|
5893
6117
|
toolName: toolCall.name,
|
|
5894
6118
|
});
|
|
5895
|
-
// Call the tool
|
|
6119
|
+
// Call the tool inside a child tool span (no-op when disabled)
|
|
5896
6120
|
log.trace(`[operate] Calling tool - ${toolCall.name}`);
|
|
5897
|
-
const result = await
|
|
5898
|
-
|
|
5899
|
-
|
|
6121
|
+
const result = await withLlmObsSpan({ kind: "tool", name: toolCall.name }, async () => {
|
|
6122
|
+
const result = await state.toolkit.call({
|
|
6123
|
+
arguments: toolCall.arguments,
|
|
6124
|
+
name: toolCall.name,
|
|
6125
|
+
});
|
|
6126
|
+
annotateLlmObs({
|
|
6127
|
+
inputData: toolCall.arguments,
|
|
6128
|
+
metadata: { tool: toolCall.name },
|
|
6129
|
+
outputData: result,
|
|
6130
|
+
});
|
|
6131
|
+
return result;
|
|
5900
6132
|
});
|
|
5901
6133
|
// Execute afterEachTool hook
|
|
5902
6134
|
await this.hookRunnerInstance.runAfterTool(context.hooks, {
|
|
@@ -6146,7 +6378,7 @@ class StreamLoop {
|
|
|
6146
6378
|
}
|
|
6147
6379
|
// If we have tool calls, process them
|
|
6148
6380
|
if (toolCalls && toolCalls.length > 0 && state.toolkit) {
|
|
6149
|
-
yield* this.processToolCalls(toolCalls, state, context
|
|
6381
|
+
yield* this.processToolCalls(toolCalls, state, context);
|
|
6150
6382
|
// Check if we've reached max turns
|
|
6151
6383
|
if (state.currentTurn >= state.maxTurns) {
|
|
6152
6384
|
const error = new errors.TooManyRequestsError();
|
|
@@ -6253,6 +6485,16 @@ class StreamLoop {
|
|
|
6253
6485
|
options,
|
|
6254
6486
|
providerRequest,
|
|
6255
6487
|
});
|
|
6488
|
+
// Open a manual llm span held open across the streamed turn. Flat (not
|
|
6489
|
+
// nested) because it spans yield boundaries; no-op when llmobs disabled.
|
|
6490
|
+
const llmSpan = openLlmObsSpan({
|
|
6491
|
+
kind: "llm",
|
|
6492
|
+
modelName: options.model ?? this.adapter.defaultModel,
|
|
6493
|
+
modelProvider: this.adapter.name,
|
|
6494
|
+
name: "jaypie.llm.model",
|
|
6495
|
+
});
|
|
6496
|
+
const inputSnapshot = [...state.currentInput];
|
|
6497
|
+
let streamedText = "";
|
|
6256
6498
|
// Collect tool calls from the stream
|
|
6257
6499
|
const collectedToolCalls = [];
|
|
6258
6500
|
// Retry loop for connection-level failures
|
|
@@ -6272,6 +6514,7 @@ class StreamLoop {
|
|
|
6272
6514
|
// Pass through text chunks
|
|
6273
6515
|
if (chunk.type === exports.LlmStreamChunkType.Text) {
|
|
6274
6516
|
chunksYielded = true;
|
|
6517
|
+
streamedText += chunk.content ?? "";
|
|
6275
6518
|
yield chunk;
|
|
6276
6519
|
}
|
|
6277
6520
|
// Collect tool calls
|
|
@@ -6318,6 +6561,12 @@ class StreamLoop {
|
|
|
6318
6561
|
title: "Stream Error",
|
|
6319
6562
|
},
|
|
6320
6563
|
};
|
|
6564
|
+
llmSpan?.annotate({
|
|
6565
|
+
inputData: inputSnapshot,
|
|
6566
|
+
metrics: usageToLlmObsMetrics(state.usageItems),
|
|
6567
|
+
outputData: streamedText,
|
|
6568
|
+
});
|
|
6569
|
+
llmSpan?.finish();
|
|
6321
6570
|
return { shouldContinue: false };
|
|
6322
6571
|
}
|
|
6323
6572
|
// Check if we've exhausted retries or error is not retryable
|
|
@@ -6326,6 +6575,7 @@ class StreamLoop {
|
|
|
6326
6575
|
log.error(`Stream request failed after ${this.retryPolicy.maxRetries} retries`);
|
|
6327
6576
|
log.var({ error });
|
|
6328
6577
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
6578
|
+
llmSpan?.finish();
|
|
6329
6579
|
throw new errors.BadGatewayError(errorMessage);
|
|
6330
6580
|
}
|
|
6331
6581
|
const delay = this.retryPolicy.getDelayForAttempt(attempt);
|
|
@@ -6339,6 +6589,13 @@ class StreamLoop {
|
|
|
6339
6589
|
finally {
|
|
6340
6590
|
guard.remove();
|
|
6341
6591
|
}
|
|
6592
|
+
// Annotate and finish the streamed llm span (no-op when disabled)
|
|
6593
|
+
llmSpan?.annotate({
|
|
6594
|
+
inputData: inputSnapshot,
|
|
6595
|
+
metrics: usageToLlmObsMetrics(state.usageItems),
|
|
6596
|
+
outputData: streamedText,
|
|
6597
|
+
});
|
|
6598
|
+
llmSpan?.finish();
|
|
6342
6599
|
// Execute afterEachModelResponse hook
|
|
6343
6600
|
await this.hookRunnerInstance.runAfterModelResponse(context.hooks, {
|
|
6344
6601
|
content: "",
|
|
@@ -6373,7 +6630,7 @@ class StreamLoop {
|
|
|
6373
6630
|
}
|
|
6374
6631
|
return { shouldContinue: false };
|
|
6375
6632
|
}
|
|
6376
|
-
async *processToolCalls(toolCalls, state, context
|
|
6633
|
+
async *processToolCalls(toolCalls, state, context) {
|
|
6377
6634
|
const log = getLogger$6();
|
|
6378
6635
|
for (const toolCall of toolCalls) {
|
|
6379
6636
|
try {
|
|
@@ -6382,11 +6639,19 @@ class StreamLoop {
|
|
|
6382
6639
|
args: toolCall.arguments,
|
|
6383
6640
|
toolName: toolCall.name,
|
|
6384
6641
|
});
|
|
6385
|
-
// Call the tool
|
|
6642
|
+
// Call the tool inside a child tool span (no-op when disabled)
|
|
6386
6643
|
log.trace(`[stream] Calling tool - ${toolCall.name}`);
|
|
6387
|
-
const result = await
|
|
6388
|
-
|
|
6389
|
-
|
|
6644
|
+
const result = await withLlmObsSpan({ kind: "tool", name: toolCall.name }, async () => {
|
|
6645
|
+
const result = await state.toolkit.call({
|
|
6646
|
+
arguments: toolCall.arguments,
|
|
6647
|
+
name: toolCall.name,
|
|
6648
|
+
});
|
|
6649
|
+
annotateLlmObs({
|
|
6650
|
+
inputData: toolCall.arguments,
|
|
6651
|
+
metadata: { tool: toolCall.name },
|
|
6652
|
+
outputData: result,
|
|
6653
|
+
});
|
|
6654
|
+
return result;
|
|
6390
6655
|
});
|
|
6391
6656
|
// Execute afterEachTool hook
|
|
6392
6657
|
await this.hookRunnerInstance.runAfterTool(context.hooks, {
|