@llumiverse/drivers 1.0.0 → 1.1.0-dev.20260427.054520Z
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/lib/cjs/bedrock/index.js +90 -10
- package/lib/cjs/bedrock/index.js.map +1 -1
- package/lib/cjs/openai/index.js +2 -0
- package/lib/cjs/openai/index.js.map +1 -1
- package/lib/cjs/vertexai/index.js +31 -22
- package/lib/cjs/vertexai/index.js.map +1 -1
- package/lib/cjs/vertexai/models/claude.js +99 -26
- package/lib/cjs/vertexai/models/claude.js.map +1 -1
- package/lib/cjs/vertexai/models/gemini.js +35 -335
- package/lib/cjs/vertexai/models/gemini.js.map +1 -1
- package/lib/esm/bedrock/index.js +90 -10
- package/lib/esm/bedrock/index.js.map +1 -1
- package/lib/esm/openai/index.js +2 -0
- package/lib/esm/openai/index.js.map +1 -1
- package/lib/esm/vertexai/index.js +31 -22
- package/lib/esm/vertexai/index.js.map +1 -1
- package/lib/esm/vertexai/models/claude.js +99 -28
- package/lib/esm/vertexai/models/claude.js.map +1 -1
- package/lib/esm/vertexai/models/gemini.js +36 -336
- package/lib/esm/vertexai/models/gemini.js.map +1 -1
- package/lib/types/bedrock/index.d.ts +5 -2
- package/lib/types/bedrock/index.d.ts.map +1 -1
- package/lib/types/openai/index.d.ts.map +1 -1
- package/lib/types/vertexai/index.d.ts +4 -1
- package/lib/types/vertexai/index.d.ts.map +1 -1
- package/lib/types/vertexai/models/claude.d.ts +16 -0
- package/lib/types/vertexai/models/claude.d.ts.map +1 -1
- package/lib/types/vertexai/models/gemini.d.ts +4 -8
- package/lib/types/vertexai/models/gemini.d.ts.map +1 -1
- package/package.json +8 -8
- package/src/bedrock/index.ts +104 -12
- package/src/bedrock/streaming-tool-use.test.ts +250 -0
- package/src/openai/index.ts +2 -0
- package/src/vertexai/index.ts +32 -22
- package/src/vertexai/models/claude-streaming-spacing.test.ts +174 -0
- package/src/vertexai/models/claude.ts +120 -29
- package/src/vertexai/models/gemini-conversation-mutation.test.ts +174 -0
- package/src/vertexai/models/gemini.ts +48 -391
|
@@ -23,6 +23,8 @@ export declare class VertexAIDriver extends AbstractDriver<VertexAIDriverOptions
|
|
|
23
23
|
anthropicClient: AnthropicVertex | undefined;
|
|
24
24
|
fetchClient: FetchClient | undefined;
|
|
25
25
|
googleGenAI: GoogleGenAI | undefined;
|
|
26
|
+
googleGenAIRegion: string | undefined;
|
|
27
|
+
googleGenAIFlex: boolean | undefined;
|
|
26
28
|
llamaClient: FetchClient & {
|
|
27
29
|
region?: string;
|
|
28
30
|
} | undefined;
|
|
@@ -32,7 +34,8 @@ export declare class VertexAIDriver extends AbstractDriver<VertexAIDriverOptions
|
|
|
32
34
|
private authClientPromise;
|
|
33
35
|
constructor(options: VertexAIDriverOptions);
|
|
34
36
|
private getAuthClient;
|
|
35
|
-
getGoogleGenAIClient(region?: string): GoogleGenAI;
|
|
37
|
+
getGoogleGenAIClient(region?: string, flex?: boolean): GoogleGenAI;
|
|
38
|
+
private buildGoogleGenAIClient;
|
|
36
39
|
getFetchClient(): FetchClient;
|
|
37
40
|
getLLamaClient(region?: string): FetchClient;
|
|
38
41
|
getAnthropicClient(region?: string): Promise<AnthropicVertex>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/vertexai/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,EAAE,uBAAuB,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AAC5E,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAC5D,OAAO,EACH,OAAO,EACP,cAAc,EACd,UAAU,EACV,qBAAqB,EAErB,aAAa,EACb,iBAAiB,EACjB,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,sBAAsB,EACtB,kBAAkB,EAClB,aAAa,EAQhB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAc,UAAU,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAKhF,OAAO,EAAyB,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEzE,MAAM,WAAW,qBAAsB,SAAQ,aAAa;IACxD,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;CACzC;AAED,MAAM,WAAW,qBAAqB;IAClC,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;CACpB;AAGD,MAAM,MAAM,cAAc,GAAG,YAAY,GAAG,qBAAqB,CAAC;AAElE,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,UAG1C;AAED,qBAAa,cAAe,SAAQ,cAAc,CAAC,qBAAqB,EAAE,cAAc,CAAC;IACrF,MAAM,CAAC,QAAQ,SAAc;IAC7B,QAAQ,SAA2B;IAEnC,UAAU,EAAE,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC;IACnD,eAAe,EAAE,eAAe,GAAG,SAAS,CAAC;IAC7C,WAAW,EAAE,WAAW,GAAG,SAAS,CAAC;IACrC,WAAW,EAAE,WAAW,GAAG,SAAS,CAAC;IACrC,WAAW,EAAE,WAAW,GAAG;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;IAC3D,WAAW,EAAE,OAAO,CAAC,wBAAwB,GAAG,SAAS,CAAC;IAC1D,YAAY,EAAE,uBAAuB,GAAG,SAAS,CAAC;IAElD,UAAU,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC;IAC5B,OAAO,CAAC,iBAAiB,CAAkC;gBAE/C,OAAO,EAAE,qBAAqB;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/vertexai/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -77,6 +77,22 @@ export declare class ClaudeModelDefinition implements ModelDefinition<ClaudeProm
|
|
|
77
77
|
* we need to merge them before sending to the API.
|
|
78
78
|
*/
|
|
79
79
|
export declare function mergeConsecutiveUserMessages(messages: MessageParam[]): MessageParam[];
|
|
80
|
+
/**
|
|
81
|
+
* Update the conversation messages
|
|
82
|
+
* @param prompt
|
|
83
|
+
* @param response
|
|
84
|
+
* @returns
|
|
85
|
+
*/
|
|
86
|
+
export declare function updateConversation(conversation: ClaudePrompt | undefined | null, prompt: ClaudePrompt): ClaudePrompt;
|
|
87
|
+
/**
|
|
88
|
+
* Sanitize messages by removing empty text blocks.
|
|
89
|
+
* Claude API rejects messages with empty text content blocks ("text content blocks must be non-empty").
|
|
90
|
+
* This handles cases where streaming was interrupted and left empty text blocks.
|
|
91
|
+
*
|
|
92
|
+
* - Filters out empty text blocks from each message's content
|
|
93
|
+
* - Removes messages entirely if they have no content after filtering
|
|
94
|
+
*/
|
|
95
|
+
export declare function sanitizeMessages(messages: MessageParam[]): MessageParam[];
|
|
80
96
|
/**
|
|
81
97
|
* Fix orphaned tool_use blocks in the conversation.
|
|
82
98
|
* @exported for testing
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claude.d.ts","sourceRoot":"","sources":["../../../../src/vertexai/models/claude.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,YAAY,EAAmE,YAAY,EAAE,cAAc,EAAwB,MAAM,sCAAsC,CAAC;AAGzL,OAAO,EACH,OAAO,EAAE,UAAU,EAAE,qBAAqB,EAAE,gBAAgB,EAK5D,eAAe,EAAE,sBAAsB,EAE3B,aAAa,EAGzB,OAAO,EAGV,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAIpD,CAAA;AAED,eAAO,MAAM,2BAA2B,UAGvC,CAAC;AAEF,UAAU,YAAY;IAClB,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,MAAM,CAAC,EAAE,cAAc,EAAE,CAAC;CAC7B;
|
|
1
|
+
{"version":3,"file":"claude.d.ts","sourceRoot":"","sources":["../../../../src/vertexai/models/claude.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,YAAY,EAAmE,YAAY,EAAE,cAAc,EAAwB,MAAM,sCAAsC,CAAC;AAGzL,OAAO,EACH,OAAO,EAAE,UAAU,EAAE,qBAAqB,EAAE,gBAAgB,EAK5D,eAAe,EAAE,sBAAsB,EAE3B,aAAa,EAGzB,OAAO,EAGV,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAIpD,CAAA;AAED,eAAO,MAAM,2BAA2B,UAGvC,CAAC;AAEF,UAAU,YAAY;IAClB,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,MAAM,CAAC,EAAE,cAAc,EAAE,CAAC;CAC7B;AA+BD,wBAAgB,YAAY,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,OAAO,EAAE,GAAG,SAAS,CAc3E;AA8FD,qBAAa,qBAAsB,YAAW,eAAe,CAAC,YAAY,CAAC;IAEvE,KAAK,EAAE,OAAO,CAAA;gBAEF,OAAO,EAAE,MAAM;IAUrB,YAAY,CAAC,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,aAAa,EAAE,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,YAAY,CAAC;IAuGlH,qBAAqB,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,UAAU,CAAC;IAwDnH,2BAA2B,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,aAAa,CAAC,qBAAqB,CAAC,CAAC;IA+HzJ;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,qBAAqB,CACjB,OAAO,EAAE,cAAc,EACvB,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,sBAAsB,GAChC,eAAe;IA8DlB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAQ3B;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,OAAO,CAAC,sBAAsB;CAuCjC;AAYD;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,YAAY,EAAE,GAAG,YAAY,EAAE,CA6CrF;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,YAAY,GAAG,SAAS,GAAG,IAAI,EAAE,MAAM,EAAE,YAAY,GAAG,YAAY,CAgBpH;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,YAAY,EAAE,GAAG,YAAY,EAAE,CA+BzE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,YAAY,EAAE,GAAG,YAAY,EAAE,CA2E3E;AAuID;;GAEG;AACH,wBAAgB,+BAA+B,CAAC,QAAQ,EAAE,YAAY,EAAE,GAAG,OAAO,CAUjF;AAED;;;;GAIG;AACH,wBAAgB,6BAA6B,CAAC,QAAQ,EAAE,YAAY,EAAE,GAAG,YAAY,EAAE,CA8CtF"}
|
|
@@ -1,15 +1,11 @@
|
|
|
1
|
-
import { Content, GenerateContentResponseUsageMetadata } from "@google/genai";
|
|
2
|
-
import { AIModel, Completion, CompletionChunkObject, ExecutionOptions, ExecutionTokenUsage, LlumiverseError, LlumiverseErrorContext, PromptSegment } from "@llumiverse/core";
|
|
3
|
-
import { GenerateContentPrompt, VertexAIDriver } from "../index.js";
|
|
4
|
-
import { ModelDefinition } from "../models.js";
|
|
1
|
+
import { type Content, type GenerateContentResponseUsageMetadata } from "@google/genai";
|
|
2
|
+
import { type AIModel, type Completion, type CompletionChunkObject, type ExecutionOptions, type ExecutionTokenUsage, LlumiverseError, type LlumiverseErrorContext, type PromptSegment } from "@llumiverse/core";
|
|
3
|
+
import type { GenerateContentPrompt, VertexAIDriver } from "../index.js";
|
|
4
|
+
import type { ModelDefinition } from "../models.js";
|
|
5
5
|
export declare function mergeConsecutiveRole(contents: Content[] | undefined): Content[];
|
|
6
6
|
export declare class GeminiModelDefinition implements ModelDefinition<GenerateContentPrompt> {
|
|
7
7
|
model: AIModel;
|
|
8
8
|
constructor(modelId: string);
|
|
9
|
-
preValidationProcessing(result: Completion, options: ExecutionOptions): {
|
|
10
|
-
result: Completion;
|
|
11
|
-
options: ExecutionOptions;
|
|
12
|
-
};
|
|
13
9
|
createPrompt(_driver: VertexAIDriver, segments: PromptSegment[], options: ExecutionOptions): Promise<GenerateContentPrompt>;
|
|
14
10
|
usageMetadataToTokenUsage(usageMetadata: GenerateContentResponseUsageMetadata | undefined): ExecutionTokenUsage;
|
|
15
11
|
requestTextCompletion(driver: VertexAIDriver, prompt: GenerateContentPrompt, options: ExecutionOptions): Promise<Completion>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gemini.d.ts","sourceRoot":"","sources":["../../../../src/vertexai/models/gemini.ts"],"names":[],"mappings":"AACA,OAAO,EACH,OAAO,
|
|
1
|
+
{"version":3,"file":"gemini.d.ts","sourceRoot":"","sources":["../../../../src/vertexai/models/gemini.ts"],"names":[],"mappings":"AACA,OAAO,EACH,KAAK,OAAO,EACZ,KAAK,oCAAoC,EAM5C,MAAM,eAAe,CAAC;AACvB,OAAO,EACH,KAAK,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,qBAAqB,EAAyB,KAAK,gBAAgB,EACvG,KAAK,mBAAmB,EAKP,eAAe,EAAE,KAAK,sBAAsB,EAC7D,KAAK,aAAa,EAOrB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,KAAK,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AACzE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AA2KpD,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,SAAS,GAAG,OAAO,EAAE,CA2B/E;AAuED,qBAAa,qBAAsB,YAAW,eAAe,CAAC,qBAAqB,CAAC;IAEhF,KAAK,EAAE,OAAO,CAAA;gBAEF,OAAO,EAAE,MAAM;IAUrB,YAAY,CAAC,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,aAAa,EAAE,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,qBAAqB,CAAC;IA+HjI,yBAAyB,CAAC,aAAa,EAAE,oCAAoC,GAAG,SAAS,GAAG,mBAAmB;IA+BzG,qBAAqB,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,qBAAqB,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,UAAU,CAAC;IA4G5H,2BAA2B,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,qBAAqB,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,aAAa,CAAC,qBAAqB,CAAC,CAAC;IAkFlK;;;;;;;;;;;;;;;;;;;OAmBG;IACH,qBAAqB,CACjB,OAAO,EAAE,cAAc,EACvB,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,sBAAsB,GAChC,eAAe;IAwClB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAUxB;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,OAAO,CAAC,sBAAsB;IAgB9B;;;;OAIG;IACH,OAAO,CAAC,gBAAgB;CAkB3B;AAGD;;;;GAIG;AACH,wBAAgB,gCAAgC,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE,CAsB/E"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llumiverse/drivers",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.1.0-dev.20260427.054520Z",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "LLM driver implementations. Currently supported are: openai, huggingface, bedrock, replicate.",
|
|
6
6
|
"files": [
|
|
@@ -44,11 +44,11 @@
|
|
|
44
44
|
"dotenv": "^16.6.1",
|
|
45
45
|
"rimraf": "^6.1.2",
|
|
46
46
|
"ts-dual-module": "^0.6.3",
|
|
47
|
-
"typescript": "^
|
|
47
|
+
"typescript": "^6.0.2",
|
|
48
48
|
"vitest": "^4.0.18"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@anthropic-ai/sdk": "^0.
|
|
51
|
+
"@anthropic-ai/sdk": "^0.85.0",
|
|
52
52
|
"@anthropic-ai/vertex-sdk": "^0.14.4",
|
|
53
53
|
"@aws-sdk/client-bedrock": "^3.985.0",
|
|
54
54
|
"@aws-sdk/client-bedrock-runtime": "^3.985.0",
|
|
@@ -63,18 +63,18 @@
|
|
|
63
63
|
"@azure/identity": "^4.13.0",
|
|
64
64
|
"@azure/openai": "2.0.0",
|
|
65
65
|
"@google-cloud/aiplatform": "^6.5.0",
|
|
66
|
-
"@google/genai": "^1.
|
|
66
|
+
"@google/genai": "^1.49.0",
|
|
67
67
|
"@huggingface/inference": "4.13.11",
|
|
68
68
|
"@vertesia/api-fetch-client": "^0.82.4",
|
|
69
69
|
"eventsource": "^4.1.0",
|
|
70
|
-
"google-auth-library": "^10.
|
|
70
|
+
"google-auth-library": "^10.6.2",
|
|
71
71
|
"groq-sdk": "^0.37.0",
|
|
72
72
|
"mnemonist": "^0.40.3",
|
|
73
73
|
"node-web-stream-adapters": "^0.2.1",
|
|
74
|
-
"openai": "^6.
|
|
74
|
+
"openai": "^6.33.0",
|
|
75
75
|
"replicate": "^1.4.0",
|
|
76
|
-
"@llumiverse/common": "1.0.
|
|
77
|
-
"@llumiverse/core": "1.0.
|
|
76
|
+
"@llumiverse/common": "1.1.0-dev.20260427.054520Z",
|
|
77
|
+
"@llumiverse/core": "1.1.0-dev.20260427.054520Z"
|
|
78
78
|
},
|
|
79
79
|
"ts_dual_module": {
|
|
80
80
|
"outDir": "lib"
|
package/src/bedrock/index.ts
CHANGED
|
@@ -86,9 +86,10 @@ export interface BedrockDriverOptions extends DriverOptions {
|
|
|
86
86
|
training_role_arn?: string;
|
|
87
87
|
|
|
88
88
|
/**
|
|
89
|
-
* The credentials to use to access AWS
|
|
89
|
+
* The credentials to use to access AWS (IAM access key + secret)
|
|
90
90
|
*/
|
|
91
91
|
credentials?: AwsCredentialIdentity | Provider<AwsCredentialIdentity>;
|
|
92
|
+
|
|
92
93
|
}
|
|
93
94
|
|
|
94
95
|
//Used to get a max_token value when not specified in the model options. Claude requires it to be set.
|
|
@@ -144,6 +145,9 @@ function isClaudeVersionGTE(modelString: string, targetMajor: number, targetMino
|
|
|
144
145
|
|
|
145
146
|
export type BedrockPrompt = NovaMessagesPrompt | ConverseRequest | TwelvelabsPegasusRequest;
|
|
146
147
|
|
|
148
|
+
type BedrockSystemBlock = NonNullable<ConverseRequest['system']>[number];
|
|
149
|
+
type BedrockToolEntry = NonNullable<NonNullable<ConverseRequest['toolConfig']>['tools']>[number];
|
|
150
|
+
|
|
147
151
|
export class BedrockDriver extends AbstractDriver<BedrockDriverOptions, BedrockPrompt> {
|
|
148
152
|
|
|
149
153
|
static PROVIDER = "bedrock";
|
|
@@ -372,9 +376,16 @@ export class BedrockDriver extends AbstractDriver<BedrockDriverOptions, BedrockP
|
|
|
372
376
|
const completionResult: CompletionChunkObject = {
|
|
373
377
|
result: reasoning + resultText ? [{ type: "text", value: reasoning + resultText }] : [],
|
|
374
378
|
token_usage: {
|
|
375
|
-
|
|
379
|
+
// Bedrock's inputTokens already excludes cache-read tokens,
|
|
380
|
+
// so prompt_new is inputTokens directly (no subtraction needed).
|
|
381
|
+
// prompt is the total including cached + cache_write for consistency
|
|
382
|
+
// with the Vertex Claude driver.
|
|
383
|
+
prompt_new: result.usage?.inputTokens,
|
|
384
|
+
prompt: result.usage ? (result.usage.inputTokens ?? 0) + (result.usage.cacheReadInputTokens ?? 0) + (result.usage.cacheWriteInputTokens ?? 0) : undefined,
|
|
376
385
|
result: result.usage?.outputTokens,
|
|
377
386
|
total: result.usage?.totalTokens,
|
|
387
|
+
prompt_cached: result.usage?.cacheReadInputTokens ?? undefined,
|
|
388
|
+
prompt_cache_write: result.usage?.cacheWriteInputTokens ?? undefined,
|
|
378
389
|
},
|
|
379
390
|
finish_reason: converseFinishReason(result.stopReason),
|
|
380
391
|
};
|
|
@@ -382,20 +393,29 @@ export class BedrockDriver extends AbstractDriver<BedrockDriverOptions, BedrockP
|
|
|
382
393
|
return completionResult;
|
|
383
394
|
};
|
|
384
395
|
|
|
385
|
-
getExtractedStream(result: ConverseStreamOutput, _prompt?: BedrockPrompt, options?: ExecutionOptions): CompletionChunkObject {
|
|
396
|
+
getExtractedStream(result: ConverseStreamOutput, _prompt?: BedrockPrompt, options?: ExecutionOptions, streamingToolBlocks?: Map<number, { id: string; name: string }>): CompletionChunkObject {
|
|
386
397
|
let output: string = "";
|
|
387
398
|
let reasoning: string = "";
|
|
388
399
|
let stop_reason = "";
|
|
389
400
|
let token_usage: ExecutionTokenUsage | undefined;
|
|
401
|
+
let tool_use: ToolUse[] | undefined;
|
|
390
402
|
|
|
391
403
|
// Check if we should include thoughts (always true for reasoning-only models like DeepSeek R1)
|
|
392
404
|
const isReasoningModel = options?.model?.includes('deepseek') && options?.model?.includes('r1');
|
|
393
405
|
const shouldIncludeThoughts = isReasoningModel || (options && (options.model_options as BedrockClaudeOptions)?.include_thoughts);
|
|
394
406
|
|
|
395
|
-
// Handle content block start events (for reasoning blocks)
|
|
407
|
+
// Handle content block start events (for reasoning blocks and tool use)
|
|
396
408
|
if (result.contentBlockStart) {
|
|
397
|
-
|
|
398
|
-
|
|
409
|
+
if (result.contentBlockStart.start && 'toolUse' in result.contentBlockStart.start && result.contentBlockStart.start.toolUse) {
|
|
410
|
+
// Register new tool call block and emit an initial chunk so the accumulator can track it by id
|
|
411
|
+
const toolUseStart = result.contentBlockStart.start.toolUse;
|
|
412
|
+
const blockIndex = result.contentBlockStart.contentBlockIndex ?? -1;
|
|
413
|
+
const id = toolUseStart.toolUseId ?? '';
|
|
414
|
+
const name = toolUseStart.name ?? '';
|
|
415
|
+
streamingToolBlocks?.set(blockIndex, { id, name });
|
|
416
|
+
tool_use = [{ id, tool_name: name, tool_input: '' as any }];
|
|
417
|
+
} else if (result.contentBlockStart.start && 'reasoningContent' in result.contentBlockStart.start && shouldIncludeThoughts) {
|
|
418
|
+
// Handle redacted content at block start
|
|
399
419
|
const reasoningStart = result.contentBlockStart.start as any;
|
|
400
420
|
if (reasoningStart.reasoningContent?.redactedContent) {
|
|
401
421
|
const redactedData = new TextDecoder().decode(reasoningStart.reasoningContent.redactedContent);
|
|
@@ -404,10 +424,17 @@ export class BedrockDriver extends AbstractDriver<BedrockDriverOptions, BedrockP
|
|
|
404
424
|
}
|
|
405
425
|
}
|
|
406
426
|
|
|
407
|
-
// Handle content block deltas (text and
|
|
427
|
+
// Handle content block deltas (text, reasoning, and tool use)
|
|
408
428
|
if (result.contentBlockDelta) {
|
|
409
429
|
const delta = result.contentBlockDelta.delta;
|
|
410
|
-
if (delta?.
|
|
430
|
+
if (delta?.toolUse) {
|
|
431
|
+
// Emit tool input chunk; the accumulator in DefaultCompletionStream concatenates these strings
|
|
432
|
+
const blockIndex = result.contentBlockDelta.contentBlockIndex ?? -1;
|
|
433
|
+
const toolBlock = streamingToolBlocks?.get(blockIndex);
|
|
434
|
+
if (toolBlock && delta.toolUse.input !== undefined) {
|
|
435
|
+
tool_use = [{ id: toolBlock.id, tool_name: '', tool_input: delta.toolUse.input as any }];
|
|
436
|
+
}
|
|
437
|
+
} else if (delta?.text) {
|
|
411
438
|
output = delta.text;
|
|
412
439
|
} else if (delta?.reasoningContent && shouldIncludeThoughts) {
|
|
413
440
|
if (delta.reasoningContent.text) {
|
|
@@ -432,7 +459,9 @@ export class BedrockDriver extends AbstractDriver<BedrockDriverOptions, BedrockP
|
|
|
432
459
|
|
|
433
460
|
// Handle content block stop events
|
|
434
461
|
if (result.contentBlockStop) {
|
|
435
|
-
//
|
|
462
|
+
// Clean up tool block tracking entry
|
|
463
|
+
const blockIndex = result.contentBlockStop.contentBlockIndex ?? -1;
|
|
464
|
+
streamingToolBlocks?.delete(blockIndex);
|
|
436
465
|
// Add minimal spacing for reasoning blocks if not already present
|
|
437
466
|
if (reasoning && !reasoning.endsWith('\n\n') && shouldIncludeThoughts) {
|
|
438
467
|
reasoning += '\n\n';
|
|
@@ -445,9 +474,12 @@ export class BedrockDriver extends AbstractDriver<BedrockDriverOptions, BedrockP
|
|
|
445
474
|
|
|
446
475
|
if (result.metadata) {
|
|
447
476
|
token_usage = {
|
|
448
|
-
|
|
477
|
+
prompt_new: result.metadata.usage?.inputTokens,
|
|
478
|
+
prompt: result.metadata.usage ? (result.metadata.usage.inputTokens ?? 0) + (result.metadata.usage.cacheReadInputTokens ?? 0) + (result.metadata.usage.cacheWriteInputTokens ?? 0) : undefined,
|
|
449
479
|
result: result.metadata.usage?.outputTokens,
|
|
450
480
|
total: result.metadata.usage?.totalTokens,
|
|
481
|
+
prompt_cached: result.metadata.usage?.cacheReadInputTokens ?? undefined,
|
|
482
|
+
prompt_cache_write: result.metadata.usage?.cacheWriteInputTokens ?? undefined,
|
|
451
483
|
}
|
|
452
484
|
}
|
|
453
485
|
|
|
@@ -455,6 +487,7 @@ export class BedrockDriver extends AbstractDriver<BedrockDriverOptions, BedrockP
|
|
|
455
487
|
result: reasoning + output ? [{ type: "text", value: reasoning + output }] : [],
|
|
456
488
|
token_usage: token_usage,
|
|
457
489
|
finish_reason: converseFinishReason(stop_reason),
|
|
490
|
+
tool_use,
|
|
458
491
|
};
|
|
459
492
|
|
|
460
493
|
return completionResult;
|
|
@@ -824,8 +857,9 @@ export class BedrockDriver extends AbstractDriver<BedrockDriverOptions, BedrockP
|
|
|
824
857
|
throw new Error("[Bedrock] Stream not found in response");
|
|
825
858
|
}
|
|
826
859
|
|
|
860
|
+
const streamingToolBlocks = new Map<number, { id: string; name: string }>();
|
|
827
861
|
return transformAsyncIterator(stream, (streamSegment: ConverseStreamOutput) => {
|
|
828
|
-
return this.getExtractedStream(streamSegment, conversePrompt, options);
|
|
862
|
+
return this.getExtractedStream(streamSegment, conversePrompt, options, streamingToolBlocks);
|
|
829
863
|
});
|
|
830
864
|
|
|
831
865
|
}).catch((err) => {
|
|
@@ -1009,6 +1043,47 @@ export class BedrockDriver extends AbstractDriver<BedrockDriverOptions, BedrockP
|
|
|
1009
1043
|
request.messages = convertToolBlocksToText(request.messages);
|
|
1010
1044
|
}
|
|
1011
1045
|
|
|
1046
|
+
// Prompt caching: use three breakpoints so stable system blocks, tool definitions,
|
|
1047
|
+
// and the conversation history prefix can all be reused across Claude turns.
|
|
1048
|
+
if (options.model.includes('claude')) {
|
|
1049
|
+
// Always strip stale markers from prior turns
|
|
1050
|
+
if (request.messages) {
|
|
1051
|
+
request.messages = stripClaudeCachePoints(request.messages);
|
|
1052
|
+
}
|
|
1053
|
+
request.system = stripClaudeCachePointsFromSystem(request.system);
|
|
1054
|
+
if (request.toolConfig?.tools) {
|
|
1055
|
+
request.toolConfig = {
|
|
1056
|
+
...request.toolConfig,
|
|
1057
|
+
tools: stripClaudeCachePointsFromTools(request.toolConfig.tools),
|
|
1058
|
+
};
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
const claudeOptions = model_options as unknown as BedrockClaudeOptions;
|
|
1062
|
+
const cacheEnabled = claudeOptions?.cache_enabled === true;
|
|
1063
|
+
if (cacheEnabled) {
|
|
1064
|
+
const cacheTtl = claudeOptions?.cache_ttl;
|
|
1065
|
+
const cachePointBlock = { type: 'default' as const, ...(cacheTtl && { ttl: cacheTtl }) };
|
|
1066
|
+
|
|
1067
|
+
if (request.system && request.system.length > 0) {
|
|
1068
|
+
request.system = [...request.system, { cachePoint: cachePointBlock } satisfies BedrockSystemBlock];
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
if (request.toolConfig?.tools && request.toolConfig.tools.length > 0) {
|
|
1072
|
+
request.toolConfig.tools = [
|
|
1073
|
+
...request.toolConfig.tools,
|
|
1074
|
+
{ cachePoint: cachePointBlock } satisfies BedrockToolEntry,
|
|
1075
|
+
];
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
if (request.messages && request.messages.length >= 4) {
|
|
1079
|
+
const pivotMsg = request.messages[request.messages.length - 2];
|
|
1080
|
+
if (pivotMsg.content && Array.isArray(pivotMsg.content) && pivotMsg.content.length > 0) {
|
|
1081
|
+
pivotMsg.content = [...pivotMsg.content, { cachePoint: cachePointBlock }];
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1012
1087
|
return request;
|
|
1013
1088
|
}
|
|
1014
1089
|
|
|
@@ -1556,6 +1631,23 @@ function updateConversation(conversation: ConverseRequest, prompt: ConverseReque
|
|
|
1556
1631
|
};
|
|
1557
1632
|
}
|
|
1558
1633
|
|
|
1634
|
+
function stripClaudeCachePoints(messages: Message[]): Message[] {
|
|
1635
|
+
return messages.map(message => ({
|
|
1636
|
+
...message,
|
|
1637
|
+
content: message.content?.filter(block => !('cachePoint' in block)),
|
|
1638
|
+
}));
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
function stripClaudeCachePointsFromSystem(system?: ConverseRequest['system']): ConverseRequest['system'] | undefined {
|
|
1642
|
+
return (system?.filter(block => !('cachePoint' in (block as object))) ?? undefined) as ConverseRequest['system'] | undefined;
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
function stripClaudeCachePointsFromTools(
|
|
1646
|
+
tools?: NonNullable<NonNullable<ConverseRequest['toolConfig']>['tools']>
|
|
1647
|
+
): NonNullable<NonNullable<ConverseRequest['toolConfig']>['tools']> | undefined {
|
|
1648
|
+
return (tools?.filter(tool => !('cachePoint' in (tool as object))) ?? undefined) as NonNullable<NonNullable<ConverseRequest['toolConfig']>['tools']> | undefined;
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1559
1651
|
/**
|
|
1560
1652
|
* Fix orphaned toolUse blocks in the conversation.
|
|
1561
1653
|
*
|
|
@@ -1671,4 +1763,4 @@ function formatAmazonModalities(modalities: ModelModality[]): string[] {
|
|
|
1671
1763
|
}
|
|
1672
1764
|
}
|
|
1673
1765
|
return standardizedModalities;
|
|
1674
|
-
}
|
|
1766
|
+
}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AIModel,
|
|
3
|
+
Completion,
|
|
4
|
+
CompletionChunkObject,
|
|
5
|
+
DriverOptions,
|
|
6
|
+
EmbeddingsOptions,
|
|
7
|
+
EmbeddingsResult,
|
|
8
|
+
ExecutionOptions,
|
|
9
|
+
ModelSearchPayload,
|
|
10
|
+
PromptRole,
|
|
11
|
+
PromptSegment,
|
|
12
|
+
} from '@llumiverse/common';
|
|
13
|
+
import { AbstractDriver } from '@llumiverse/core';
|
|
14
|
+
import { beforeEach, describe, expect, it } from 'vitest';
|
|
15
|
+
import { BedrockDriver } from './index.js';
|
|
16
|
+
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Unit tests: getExtractedStream tool use handling
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
describe('BedrockDriver getExtractedStream — tool use', () => {
|
|
22
|
+
let driver: BedrockDriver;
|
|
23
|
+
let toolBlocks: Map<number, { id: string; name: string }>;
|
|
24
|
+
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
driver = new BedrockDriver({ region: 'us-east-1' });
|
|
27
|
+
toolBlocks = new Map();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('emits an initial tool_use chunk on contentBlockStart', () => {
|
|
31
|
+
const chunk = driver['getExtractedStream'](
|
|
32
|
+
{
|
|
33
|
+
contentBlockStart: {
|
|
34
|
+
contentBlockIndex: 1,
|
|
35
|
+
start: { toolUse: { toolUseId: 'tool-abc', name: 'my_tool' } },
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
undefined,
|
|
39
|
+
undefined,
|
|
40
|
+
toolBlocks
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
expect(chunk.tool_use).toHaveLength(1);
|
|
44
|
+
expect(chunk.tool_use![0]).toMatchObject({ id: 'tool-abc', tool_name: 'my_tool', tool_input: '' });
|
|
45
|
+
expect(toolBlocks.get(1)).toEqual({ id: 'tool-abc', name: 'my_tool' });
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('emits a delta tool_use chunk on contentBlockDelta', () => {
|
|
49
|
+
toolBlocks.set(1, { id: 'tool-abc', name: 'my_tool' });
|
|
50
|
+
|
|
51
|
+
const chunk = driver['getExtractedStream'](
|
|
52
|
+
{
|
|
53
|
+
contentBlockDelta: {
|
|
54
|
+
contentBlockIndex: 1,
|
|
55
|
+
delta: { toolUse: { input: '{"key":' } },
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
undefined,
|
|
59
|
+
undefined,
|
|
60
|
+
toolBlocks
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
expect(chunk.tool_use).toHaveLength(1);
|
|
64
|
+
expect(chunk.tool_use![0]).toMatchObject({ id: 'tool-abc', tool_name: '', tool_input: '{"key":' });
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('removes the block from the map on contentBlockStop', () => {
|
|
68
|
+
toolBlocks.set(1, { id: 'tool-abc', name: 'my_tool' });
|
|
69
|
+
|
|
70
|
+
driver['getExtractedStream'](
|
|
71
|
+
{ contentBlockStop: { contentBlockIndex: 1 } },
|
|
72
|
+
undefined,
|
|
73
|
+
undefined,
|
|
74
|
+
toolBlocks
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
expect(toolBlocks.has(1)).toBe(false);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('tracks two interleaved tool calls by independent contentBlockIndex', () => {
|
|
81
|
+
driver['getExtractedStream'](
|
|
82
|
+
{ contentBlockStart: { contentBlockIndex: 1, start: { toolUse: { toolUseId: 'id-1', name: 'tool_a' } } } },
|
|
83
|
+
undefined, undefined, toolBlocks
|
|
84
|
+
);
|
|
85
|
+
driver['getExtractedStream'](
|
|
86
|
+
{ contentBlockStart: { contentBlockIndex: 3, start: { toolUse: { toolUseId: 'id-2', name: 'tool_b' } } } },
|
|
87
|
+
undefined, undefined, toolBlocks
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
expect(toolBlocks.get(1)).toEqual({ id: 'id-1', name: 'tool_a' });
|
|
91
|
+
expect(toolBlocks.get(3)).toEqual({ id: 'id-2', name: 'tool_b' });
|
|
92
|
+
|
|
93
|
+
const chunk = driver['getExtractedStream'](
|
|
94
|
+
{ contentBlockDelta: { contentBlockIndex: 3, delta: { toolUse: { input: '"val"' } } } },
|
|
95
|
+
undefined, undefined, toolBlocks
|
|
96
|
+
);
|
|
97
|
+
expect(chunk.tool_use![0].id).toBe('id-2');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('still extracts text deltas when no tool use is present', () => {
|
|
101
|
+
const chunk = driver['getExtractedStream'](
|
|
102
|
+
{ contentBlockDelta: { contentBlockIndex: 0, delta: { text: 'hello' } } },
|
|
103
|
+
undefined,
|
|
104
|
+
undefined,
|
|
105
|
+
toolBlocks
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
expect(chunk.result).toEqual([{ type: 'text', value: 'hello' }]);
|
|
109
|
+
expect(chunk.tool_use).toBeUndefined();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('emits finish_reason "tool_use" from messageStop', () => {
|
|
113
|
+
const chunk = driver['getExtractedStream'](
|
|
114
|
+
{ messageStop: { stopReason: 'tool_use' } },
|
|
115
|
+
undefined,
|
|
116
|
+
undefined,
|
|
117
|
+
toolBlocks
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
expect(chunk.finish_reason).toBe('tool_use');
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
// Integration tests: full accumulation via driver.stream()
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
|
|
128
|
+
class FakeDriver extends AbstractDriver<DriverOptions, string> {
|
|
129
|
+
provider = 'fake';
|
|
130
|
+
chunks: CompletionChunkObject[] = [];
|
|
131
|
+
|
|
132
|
+
async requestTextCompletion(_prompt: string, _options: ExecutionOptions): Promise<Completion> {
|
|
133
|
+
throw new Error('not implemented');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async requestTextCompletionStream(_prompt: string, _options: ExecutionOptions): Promise<AsyncIterable<CompletionChunkObject>> {
|
|
137
|
+
const chunks = this.chunks;
|
|
138
|
+
return (async function* () { for (const c of chunks) yield c; })();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async listModels(_params?: ModelSearchPayload): Promise<AIModel[]> { return []; }
|
|
142
|
+
async validateConnection(): Promise<boolean> { return true; }
|
|
143
|
+
async generateEmbeddings(_options: EmbeddingsOptions): Promise<EmbeddingsResult> {
|
|
144
|
+
throw new Error('not implemented');
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const FAKE_SEGMENTS: PromptSegment[] = [{ role: PromptRole.user, content: 'test' }];
|
|
149
|
+
|
|
150
|
+
describe('driver.stream() — Bedrock tool use accumulation', () => {
|
|
151
|
+
it('assembles and JSON-parses tool_input from streamed chunks', async () => {
|
|
152
|
+
const driver = new FakeDriver({});
|
|
153
|
+
const options: ExecutionOptions = { model: 'test-model' };
|
|
154
|
+
|
|
155
|
+
// Simulate what the fixed getExtractedStream emits for one tool call
|
|
156
|
+
driver.chunks = [
|
|
157
|
+
{ result: [], tool_use: [{ id: 'tool-1', tool_name: 'do_thing', tool_input: '' as any }] },
|
|
158
|
+
{ result: [], tool_use: [{ id: 'tool-1', tool_name: '', tool_input: '{"param"' as any }] },
|
|
159
|
+
{ result: [], tool_use: [{ id: 'tool-1', tool_name: '', tool_input: ':"hello"}' as any }] },
|
|
160
|
+
{ result: [], finish_reason: 'tool_use' },
|
|
161
|
+
];
|
|
162
|
+
|
|
163
|
+
const stream = await driver.stream(FAKE_SEGMENTS, options);
|
|
164
|
+
for await (const _ of stream) { /* drain */ }
|
|
165
|
+
|
|
166
|
+
expect(stream.completion!.finish_reason).toBe('tool_use');
|
|
167
|
+
expect(stream.completion!.tool_use).toHaveLength(1);
|
|
168
|
+
expect(stream.completion!.tool_use![0]).toMatchObject({
|
|
169
|
+
id: 'tool-1',
|
|
170
|
+
tool_name: 'do_thing',
|
|
171
|
+
tool_input: { param: 'hello' },
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('handles two simultaneous tool calls', async () => {
|
|
176
|
+
const driver = new FakeDriver({});
|
|
177
|
+
const options: ExecutionOptions = { model: 'test-model' };
|
|
178
|
+
|
|
179
|
+
driver.chunks = [
|
|
180
|
+
{ result: [], tool_use: [{ id: 'id-a', tool_name: 'tool_a', tool_input: '' as any }] },
|
|
181
|
+
{ result: [], tool_use: [{ id: 'id-b', tool_name: 'tool_b', tool_input: '' as any }] },
|
|
182
|
+
{ result: [], tool_use: [{ id: 'id-a', tool_name: '', tool_input: '{"x":1}' as any }] },
|
|
183
|
+
{ result: [], tool_use: [{ id: 'id-b', tool_name: '', tool_input: '{"y":2}' as any }] },
|
|
184
|
+
{ result: [], finish_reason: 'tool_use' },
|
|
185
|
+
];
|
|
186
|
+
|
|
187
|
+
const stream = await driver.stream(FAKE_SEGMENTS, options);
|
|
188
|
+
for await (const _ of stream) { /* drain */ }
|
|
189
|
+
|
|
190
|
+
const toolUse = stream.completion!.tool_use!;
|
|
191
|
+
expect(toolUse).toHaveLength(2);
|
|
192
|
+
expect(toolUse.find(t => t.id === 'id-a')!.tool_input).toEqual({ x: 1 });
|
|
193
|
+
expect(toolUse.find(t => t.id === 'id-b')!.tool_input).toEqual({ y: 2 });
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('drops truncated tool calls when finish_reason is length', async () => {
|
|
197
|
+
const driver = new FakeDriver({});
|
|
198
|
+
const options: ExecutionOptions = { model: 'test-model' };
|
|
199
|
+
|
|
200
|
+
driver.chunks = [
|
|
201
|
+
{ result: [], tool_use: [{ id: 'trunc', tool_name: 'tool_c', tool_input: '' as any }] },
|
|
202
|
+
{ result: [], tool_use: [{ id: 'trunc', tool_name: '', tool_input: '{"incomplete' as any }] },
|
|
203
|
+
{ result: [], finish_reason: 'length' },
|
|
204
|
+
];
|
|
205
|
+
|
|
206
|
+
const stream = await driver.stream(FAKE_SEGMENTS, options);
|
|
207
|
+
for await (const _ of stream) { /* drain */ }
|
|
208
|
+
|
|
209
|
+
expect(stream.completion!.tool_use).toBeUndefined();
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
describe('BedrockDriver buildStreamingConversation', () => {
|
|
214
|
+
it('writes streamed text and tool use blocks back into the assistant message', () => {
|
|
215
|
+
const driver = new BedrockDriver({ region: 'us-east-1' });
|
|
216
|
+
const prompt = {
|
|
217
|
+
modelId: 'anthropic.claude-sonnet',
|
|
218
|
+
messages: [
|
|
219
|
+
{ role: 'user', content: [{ text: 'What is the weather in Paris?' }] },
|
|
220
|
+
],
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
const conversation = driver.buildStreamingConversation(
|
|
224
|
+
prompt as any,
|
|
225
|
+
[{ type: 'text', value: 'Let me check.' }] as any,
|
|
226
|
+
[{
|
|
227
|
+
id: 'tool-1',
|
|
228
|
+
tool_name: 'get_weather',
|
|
229
|
+
tool_input: { location: 'Paris' },
|
|
230
|
+
}],
|
|
231
|
+
{ model: 'anthropic.claude-sonnet' } as ExecutionOptions
|
|
232
|
+
) as any;
|
|
233
|
+
|
|
234
|
+
expect(conversation.messages).toHaveLength(2);
|
|
235
|
+
expect(conversation.messages[0]).toEqual(prompt.messages[0]);
|
|
236
|
+
expect(conversation.messages[1]).toEqual({
|
|
237
|
+
role: 'assistant',
|
|
238
|
+
content: [
|
|
239
|
+
{ text: 'Let me check.' },
|
|
240
|
+
{
|
|
241
|
+
toolUse: {
|
|
242
|
+
toolUseId: 'tool-1',
|
|
243
|
+
name: 'get_weather',
|
|
244
|
+
input: { location: 'Paris' },
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
],
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
});
|
package/src/openai/index.ts
CHANGED
|
@@ -793,6 +793,8 @@ function mapUsage(usage?: OpenAI.Responses.ResponseUsage | null): ExecutionToken
|
|
|
793
793
|
prompt: usage.input_tokens,
|
|
794
794
|
result: usage.output_tokens,
|
|
795
795
|
total: usage.total_tokens,
|
|
796
|
+
prompt_cached: usage.input_tokens_details?.cached_tokens ?? undefined,
|
|
797
|
+
prompt_new: usage.input_tokens - (usage.input_tokens_details?.cached_tokens ?? 0),
|
|
796
798
|
};
|
|
797
799
|
}
|
|
798
800
|
|