@mate-academy/llm-gateway 8.1.0 → 8.2.1
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/README.md +68 -4
- package/dist/LLMService.typedefs.d.ts +20 -0
- package/dist/LLMService.typedefs.js.map +1 -1
- package/dist/client/LLMAgent.d.ts +17 -4
- package/dist/client/LLMAgent.js +4 -0
- package/dist/client/LLMAgent.js.map +1 -1
- package/dist/client/LLMClientEngine.d.ts +81 -0
- package/dist/client/LLMClientEngine.js +350 -0
- package/dist/client/LLMClientEngine.js.map +1 -0
- package/dist/client/agentRun.typedefs.d.ts +18 -3
- package/dist/client/agentRun.typedefs.js +1 -0
- package/dist/client/agentRun.typedefs.js.map +1 -1
- package/dist/client/agentRunner.d.ts +38 -3
- package/dist/client/agentRunner.js +206 -80
- package/dist/client/agentRunner.js.map +1 -1
- package/dist/client/codegen/fetchPromptRecords.d.ts +1 -1
- package/dist/client/codegen/index.d.ts +1 -0
- package/dist/client/codegen/index.js +1 -0
- package/dist/client/codegen/index.js.map +1 -1
- package/dist/client/codegen/renderSnapshot.constants.d.ts +12 -0
- package/dist/client/codegen/renderSnapshot.constants.js +21 -0
- package/dist/client/codegen/renderSnapshot.constants.js.map +1 -0
- package/dist/client/codegen/renderSnapshot.d.ts +6 -18
- package/dist/client/codegen/renderSnapshot.js +152 -125
- package/dist/client/codegen/renderSnapshot.js.map +1 -1
- package/dist/client/codegen/renderSnapshot.typedefs.d.ts +20 -0
- package/dist/client/codegen/renderSnapshot.typedefs.js +3 -0
- package/dist/client/codegen/renderSnapshot.typedefs.js.map +1 -0
- package/dist/client/coerceToolResult.d.ts +10 -0
- package/dist/client/coerceToolResult.js +38 -0
- package/dist/client/coerceToolResult.js.map +1 -0
- package/dist/client/createLLMClient.d.ts +1 -40
- package/dist/client/createLLMClient.js +5 -329
- package/dist/client/createLLMClient.js.map +1 -1
- package/dist/client/index.d.ts +3 -1
- package/dist/client/index.js +3 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/llmClient.typedefs.d.ts +180 -0
- package/dist/client/{createLLMClient.typedefs.js → llmClient.typedefs.js} +1 -1
- package/dist/client/llmClient.typedefs.js.map +1 -0
- package/dist/client/plainMessages.d.ts +1 -1
- package/dist/client/promptRegistry.runtime.d.ts +6 -0
- package/dist/client/promptRegistry.runtime.js +7 -1
- package/dist/client/promptRegistry.runtime.js.map +1 -1
- package/dist/client/promptVariables.d.ts +7 -0
- package/dist/client/promptVariables.js +13 -0
- package/dist/client/promptVariables.js.map +1 -0
- package/dist/client/snapshotRuntime.d.ts +17 -0
- package/dist/client/snapshotRuntime.js +42 -0
- package/dist/client/snapshotRuntime.js.map +1 -0
- package/dist/client/snapshotRuntime.typedefs.d.ts +13 -0
- package/dist/client/snapshotRuntime.typedefs.js +3 -0
- package/dist/client/snapshotRuntime.typedefs.js.map +1 -0
- package/dist/providers/LLMAPI/services/LLMAPIAssistance.service.d.ts +0 -2
- package/dist/providers/LLMAPI/services/LLMAPIAssistance.service.js +37 -25
- package/dist/providers/LLMAPI/services/LLMAPIAssistance.service.js.map +1 -1
- package/dist/providers/LLMAPI/services/LLMAPICompletion.service.d.ts +0 -2
- package/dist/providers/LLMAPI/services/LLMAPICompletion.service.js +15 -17
- package/dist/providers/LLMAPI/services/LLMAPICompletion.service.js.map +1 -1
- package/dist/providers/LLMAPI/utilities/index.d.ts +1 -1
- package/dist/providers/LLMAPI/utilities/index.js +3 -4
- package/dist/providers/LLMAPI/utilities/index.js.map +1 -1
- package/dist/providers/LLMAPI/utilities/validateStructuredResponse.d.ts +10 -0
- package/dist/providers/LLMAPI/utilities/validateStructuredResponse.js +21 -0
- package/dist/providers/LLMAPI/utilities/validateStructuredResponse.js.map +1 -0
- package/dist/services/LLMBaseService.abstract.js +2 -2
- package/dist/services/LLMBaseService.abstract.js.map +1 -1
- package/dist/utilities/index.d.ts +0 -1
- package/dist/utilities/index.js +0 -1
- package/dist/utilities/index.js.map +1 -1
- package/dist/utilities/llmTracing/llmTracing.typedefs.d.ts +8 -3
- package/dist/utilities/tools/LLMTool.d.ts +3 -12
- package/dist/utilities/tools/LLMTool.js.map +1 -1
- package/package.json +2 -1
- package/dist/client/createLLMClient.typedefs.d.ts +0 -91
- package/dist/client/createLLMClient.typedefs.js.map +0 -1
- package/dist/providers/LLMAPI/utilities/LLMAPIJsonFixer.d.ts +0 -24
- package/dist/providers/LLMAPI/utilities/LLMAPIJsonFixer.js +0 -237
- package/dist/providers/LLMAPI/utilities/LLMAPIJsonFixer.js.map +0 -1
- package/dist/utilities/traceIO.d.ts +0 -17
- package/dist/utilities/traceIO.js +0 -48
- package/dist/utilities/traceIO.js.map +0 -1
package/README.md
CHANGED
|
@@ -124,10 +124,10 @@ content, model, params, usage, cost) at the same success/error points the metric
|
|
|
124
124
|
reporter already wraps — one on success, one (level `ERROR`) on failure;
|
|
125
125
|
assistance tool loops emit a child `tool` observation per tool call. Calls that
|
|
126
126
|
carry no per-call trace context (text-to-speech, file and chat management) emit
|
|
127
|
-
none, on either branch. Observation input/output are
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
an LLM call.
|
|
127
|
+
none, on either branch. Observation input/output are recorded in full — the
|
|
128
|
+
prompt and the completion are what a trace exists to show, so they are never
|
|
129
|
+
truncated. All of this is fire-and-forget — a tracing failure never blocks or
|
|
130
|
+
fails an LLM call.
|
|
131
131
|
|
|
132
132
|
Pass `traceContext.prompt` (`{ name, version, isFallback }`) to name the
|
|
133
133
|
observation after the managed prompt and link it to that prompt version in
|
|
@@ -315,6 +315,62 @@ if (turn.stopReason === LLMToolLoopStopReasons.TerminalTool) {
|
|
|
315
315
|
`[a-zA-Z0-9_-]+` (it is exposed to the model as a tool); an invalid name
|
|
316
316
|
fails the run upfront with `LLMConfigError` instead of being silently
|
|
317
317
|
rewritten.
|
|
318
|
+
- **Typed delegation inputs.** A subagent declaring an `inputSchema` is exposed
|
|
319
|
+
to the model with that schema instead of the default single `prompt` string,
|
|
320
|
+
and the arguments the parent model fills join the subagent's own `variables`
|
|
321
|
+
(winning over them), so its prompt can reference them as `{{topicRef}}` and
|
|
322
|
+
the validation gate checks them like any other variable. Use it whenever the
|
|
323
|
+
delegated work needs identifiers the parent must not paraphrase — an entity
|
|
324
|
+
ref, a changeset id, an enum choice. Without `inputSchema` nothing changes:
|
|
325
|
+
one free-form `prompt` argument, which is also the nested run's input.
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
const theoryWriter = llm.defineAgent({
|
|
329
|
+
prompt: LLMPrompt.ContentEditorWriteTheory,
|
|
330
|
+
name: 'write_theory',
|
|
331
|
+
description: 'Authors one theory document into the current changeset',
|
|
332
|
+
inputSchema: LLMSchema.object({
|
|
333
|
+
topicRef: LLMSchema.string().describe('Changeset entity ref of the topic'),
|
|
334
|
+
audienceLevel: LLMSchema.enum(['beginner', 'intermediate', 'advanced']),
|
|
335
|
+
outline: LLMSchema.array(LLMSchema.string()),
|
|
336
|
+
}),
|
|
337
|
+
variables: { changesetId },
|
|
338
|
+
tools: [getEntityTool, saveTheoryTool],
|
|
339
|
+
});
|
|
340
|
+
```
|
|
341
|
+
- **One place normalizes tool output.** Every tool result and every delegation
|
|
342
|
+
result passes through the package's coercion before the model sees it, so a
|
|
343
|
+
tool that returns an object, a number or nothing cannot put `[object Object]`
|
|
344
|
+
into the conversation. A client may add its own `normalizeToolResult` on top —
|
|
345
|
+
the natural place to wrap untrusted material (a fetched web page) so no tool
|
|
346
|
+
author can forget to:
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
const llm = createLLMClient({
|
|
350
|
+
// ...
|
|
351
|
+
normalizeToolResult: ({ tool, result }) => (
|
|
352
|
+
UNTRUSTED_TOOLS.has(tool)
|
|
353
|
+
? { content: `<untrusted source="${tool}">\n${toText(result)}\n</untrusted>` }
|
|
354
|
+
: result
|
|
355
|
+
),
|
|
356
|
+
});
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
The normalized value is what reaches both the model and the
|
|
360
|
+
`tool_call_completed` event, so a persisted run history records exactly what
|
|
361
|
+
the model read.
|
|
362
|
+
- **Preconditions gate a call before it runs.** A tool may declare
|
|
363
|
+
`canExecute(args)` returning `{ allowed: true }` or
|
|
364
|
+
`{ allowed: false, reason }`. It is checked before `execute`, so a refused
|
|
365
|
+
call never has its side effect. A refusal is ordinary control flow, not an
|
|
366
|
+
error: the model is told the reason as that call's result, the run continues,
|
|
367
|
+
and the iteration budget is untouched — so the model can ask the user or take
|
|
368
|
+
another path. The invocation reports `tool_call_denied` instead of
|
|
369
|
+
`tool_call_completed`, and a refused delegation reports the same event with
|
|
370
|
+
`tool` naming the subagent. Use it for permission, quota and state checks that
|
|
371
|
+
depend on the arguments (an authorization check before a write, a stale-hash
|
|
372
|
+
guard, a per-run write budget) — enforcement then lives in the tool's
|
|
373
|
+
definition rather than in prompt instructions the model may ignore.
|
|
318
374
|
|
|
319
375
|
The plain-object form above is complete on its own. `llm.defineAgent` is
|
|
320
376
|
optional sugar for reusable, composable definitions: it returns an immutable
|
|
@@ -374,6 +430,14 @@ fallback. It is gitignored exactly like GraphQL generated files: regenerated
|
|
|
374
430
|
locally on demand, in CI before image builds (with a cached-artifact fallback so a
|
|
375
431
|
Langfuse outage never fails a build), and refreshed non-blocking at API pod boot.
|
|
376
432
|
|
|
433
|
+
The emitted file contains declarations only. Its compile helpers delegate to
|
|
434
|
+
`compileTextFromSnapshot` / `compileMessagesFromSnapshot`, imported from this
|
|
435
|
+
package rather than copied into the output as source text, so the substitution
|
|
436
|
+
logic has one implementation with its own tests. The declarations themselves are
|
|
437
|
+
built with ts-morph through its structural API, not by string concatenation —
|
|
438
|
+
ts-morph is a dependency of the `codegen` entry point only, so importing the
|
|
439
|
+
package's main entry never pulls it in.
|
|
440
|
+
|
|
377
441
|
`generateSnapshot` (exported from `@mate-academy/llm-gateway/codegen`) takes an
|
|
378
442
|
injected `LLMPromptCatalog` instead of constructing a Langfuse SDK client
|
|
379
443
|
itself — the caller owns the catalog's credentials and its Langfuse project.
|
|
@@ -491,11 +491,31 @@ export interface LLMStructuredToolResult {
|
|
|
491
491
|
* images.
|
|
492
492
|
*/
|
|
493
493
|
export type LLMToolResult = string | LLMStructuredToolResult;
|
|
494
|
+
/**
|
|
495
|
+
* The verdict of a tool's `canExecute` precondition. A refusal carries the
|
|
496
|
+
* `reason` the model is told, which is why it is required: a denial the model
|
|
497
|
+
* cannot explain to itself is a denial it will retry blindly.
|
|
498
|
+
*/
|
|
499
|
+
export type LLMToolGateResult = {
|
|
500
|
+
allowed: true;
|
|
501
|
+
} | {
|
|
502
|
+
allowed: false;
|
|
503
|
+
reason: string;
|
|
504
|
+
};
|
|
494
505
|
export interface LLMToolDefinition<Schema extends LLMSchemaInterface = LLMSchemaInterface> {
|
|
495
506
|
name: string;
|
|
496
507
|
description: string;
|
|
497
508
|
parameters: Schema;
|
|
498
509
|
execute: (args: InferSchema<Schema>) => Promise<LLMToolResult> | LLMToolResult;
|
|
510
|
+
/**
|
|
511
|
+
* Decides whether this call may run, on the arguments the model supplied.
|
|
512
|
+
* Checked before `execute`, so a refused call never has its side effect. A
|
|
513
|
+
* refusal is ordinary control flow, not an error: the model is told the
|
|
514
|
+
* `reason` as the call's result and the run continues with its iteration
|
|
515
|
+
* budget untouched. Use it for permission, quota, and state preconditions
|
|
516
|
+
* that depend on the arguments rather than on the tool being present at all.
|
|
517
|
+
*/
|
|
518
|
+
canExecute?: (args: InferSchema<Schema>) => Promise<LLMToolGateResult> | LLMToolGateResult;
|
|
499
519
|
/**
|
|
500
520
|
* Ends the turn once this tool runs. The round executes in full (every call
|
|
501
521
|
* of that round, including parallel ones), but its results are not fed back
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LLMService.typedefs.js","sourceRoot":"","sources":["../src/LLMService.typedefs.ts"],"names":[],"mappings":";;;AAmCA;;GAEG;AACH,IAAY,YAIX;AAJD,WAAY,YAAY;IACtB,iCAAiB,CAAA;IACjB,yDAAyC,CAAA;IACzC,iCAAiB,CAAA;AACnB,CAAC,EAJW,YAAY,4BAAZ,YAAY,QAIvB;AAoBD;;GAEG;AACH,IAAY,WAKX;AALD,WAAY,WAAW;IACrB,wCAAyB,CAAA;IACzB,wCAAyB,CAAA;IACzB,8CAA+B,CAAA;IAC/B,8CAA+B,CAAA;AACjC,CAAC,EALW,WAAW,2BAAX,WAAW,QAKtB;AAiLD;;GAEG;AACH,IAAY,QAGX;AAHD,WAAY,QAAQ;IAClB,yBAAa,CAAA;IACb,mCAAuB,CAAA;AACzB,CAAC,EAHW,QAAQ,wBAAR,QAAQ,QAGnB;AAED;;GAEG;AACH,IAAY,qBAIX;AAJD,WAAY,qBAAqB;IAC/B,sCAAa,CAAA;IACb,gDAAuB,CAAA;IACvB,kDAAyB,CAAA;AAC3B,CAAC,EAJW,qBAAqB,qCAArB,qBAAqB,QAIhC;AAkLD;;GAEG;AACH,IAAY,sBAeX;AAfD,WAAY,sBAAsB;IAChC,iDAAuB,CAAA;IACvB,mDAAyB,CAAA;IACzB,iDAAuB,CAAA;IACvB,iDAAuB,CAAA;IACvB,mDAAyB,CAAA;IACzB,yDAA+B,CAAA;IAC/B,iDAAuB,CAAA;IACvB,mDAAyB,CAAA;IACzB,oDAA0B,CAAA;IAC1B,iDAAuB,CAAA;IACvB,mDAAyB,CAAA;IACzB,iDAAuB,CAAA;IACvB,mDAAyB,CAAA;IACzB,iDAAuB,CAAA;AACzB,CAAC,EAfW,sBAAsB,sCAAtB,sBAAsB,QAejC;
|
|
1
|
+
{"version":3,"file":"LLMService.typedefs.js","sourceRoot":"","sources":["../src/LLMService.typedefs.ts"],"names":[],"mappings":";;;AAmCA;;GAEG;AACH,IAAY,YAIX;AAJD,WAAY,YAAY;IACtB,iCAAiB,CAAA;IACjB,yDAAyC,CAAA;IACzC,iCAAiB,CAAA;AACnB,CAAC,EAJW,YAAY,4BAAZ,YAAY,QAIvB;AAoBD;;GAEG;AACH,IAAY,WAKX;AALD,WAAY,WAAW;IACrB,wCAAyB,CAAA;IACzB,wCAAyB,CAAA;IACzB,8CAA+B,CAAA;IAC/B,8CAA+B,CAAA;AACjC,CAAC,EALW,WAAW,2BAAX,WAAW,QAKtB;AAiLD;;GAEG;AACH,IAAY,QAGX;AAHD,WAAY,QAAQ;IAClB,yBAAa,CAAA;IACb,mCAAuB,CAAA;AACzB,CAAC,EAHW,QAAQ,wBAAR,QAAQ,QAGnB;AAED;;GAEG;AACH,IAAY,qBAIX;AAJD,WAAY,qBAAqB;IAC/B,sCAAa,CAAA;IACb,gDAAuB,CAAA;IACvB,kDAAyB,CAAA;AAC3B,CAAC,EAJW,qBAAqB,qCAArB,qBAAqB,QAIhC;AAkLD;;GAEG;AACH,IAAY,sBAeX;AAfD,WAAY,sBAAsB;IAChC,iDAAuB,CAAA;IACvB,mDAAyB,CAAA;IACzB,iDAAuB,CAAA;IACvB,iDAAuB,CAAA;IACvB,mDAAyB,CAAA;IACzB,yDAA+B,CAAA;IAC/B,iDAAuB,CAAA;IACvB,mDAAyB,CAAA;IACzB,oDAA0B,CAAA;IAC1B,iDAAuB,CAAA;IACvB,mDAAyB,CAAA;IACzB,iDAAuB,CAAA;IACvB,mDAAyB,CAAA;IACzB,iDAAuB,CAAA;AACzB,CAAC,EAfW,sBAAsB,sCAAtB,sBAAsB,QAejC;AA4OD;;;;;GAKG;AACH,IAAY,sBAIX;AAJD,WAAY,sBAAsB;IAChC,iDAAuB,CAAA;IACvB,0DAAgC,CAAA;IAChC,wDAA8B,CAAA;AAChC,CAAC,EAJW,sBAAsB,sCAAtB,sBAAsB,QAIjC"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { type LLMToolDefinition } from '../LLMService.typedefs';
|
|
2
|
+
import { type LLMSchemaInterface } from '../utilities/schema';
|
|
2
3
|
import { type LLMPromptRegistry } from '../client/defineLLMPrompts';
|
|
3
4
|
import { type LLMVariableValue } from '../client/promptSnapshot.typedefs';
|
|
4
|
-
import { type LLMPromptVariables } from '../client/
|
|
5
|
+
import { type LLMDelegationGate, type LLMPromptVariables, type LLMSubagentVariableSource } from '../client/llmClient.typedefs';
|
|
5
6
|
/**
|
|
6
7
|
* A bounded specialist the root agent may delegate to, as a plain object.
|
|
7
8
|
* `prompt` names the registry entry whose Langfuse prompt is the subagent's
|
|
@@ -15,10 +16,10 @@ export type LLMAgentSubagentDefinition<Registry extends LLMPromptRegistry, Varia
|
|
|
15
16
|
prompt: Key;
|
|
16
17
|
name: string;
|
|
17
18
|
description: string;
|
|
18
|
-
variables: LLMPromptVariables<VariableMap[Key]>;
|
|
19
19
|
tools?: LLMToolDefinition[];
|
|
20
|
+
canExecute?: LLMDelegationGate;
|
|
20
21
|
maxToolIterations?: number;
|
|
21
|
-
}
|
|
22
|
+
} & LLMSubagentVariableSource<VariableMap[Key]>;
|
|
22
23
|
}[keyof Registry];
|
|
23
24
|
/**
|
|
24
25
|
* Anything accepted in a `subagents` list: a plain definition object or an
|
|
@@ -31,7 +32,8 @@ export type LLMAgentSubagent<Registry extends LLMPromptRegistry, VariableMap ext
|
|
|
31
32
|
* its model). `name`/`description` are required when the agent is used as a
|
|
32
33
|
* subagent — the name becomes the delegation tool the parent model calls.
|
|
33
34
|
* `variables` given here are a base merged under the per-run variables (for a
|
|
34
|
-
* subagent they are the only source
|
|
35
|
+
* subagent they are the only source, unless `inputSchema` lets the parent
|
|
36
|
+
* model supply more).
|
|
35
37
|
*/
|
|
36
38
|
export interface LLMAgentDefinition<Registry extends LLMPromptRegistry, VariableMap extends Record<keyof Registry, string>, Key extends keyof Registry> {
|
|
37
39
|
prompt: Key;
|
|
@@ -40,6 +42,15 @@ export interface LLMAgentDefinition<Registry extends LLMPromptRegistry, Variable
|
|
|
40
42
|
variables?: LLMPromptVariables<VariableMap[Key]>;
|
|
41
43
|
tools?: LLMToolDefinition[];
|
|
42
44
|
subagents?: LLMAgentSubagent<Registry, VariableMap>[];
|
|
45
|
+
/**
|
|
46
|
+
* The arguments the parent model must supply when delegating to this agent,
|
|
47
|
+
* replacing the default single `prompt` string. Each argument joins the
|
|
48
|
+
* subagent's prompt variables (over the definition's own), so the delegated
|
|
49
|
+
* prompt can reference them as `{{name}}`. Only meaningful as a subagent.
|
|
50
|
+
*/
|
|
51
|
+
inputSchema?: LLMSchemaInterface;
|
|
52
|
+
/** Refuses a delegation call before it runs; see `LLMDelegationGate`. */
|
|
53
|
+
canExecute?: LLMDelegationGate;
|
|
43
54
|
maxToolIterations?: number;
|
|
44
55
|
}
|
|
45
56
|
/**
|
|
@@ -58,6 +69,8 @@ export declare class LLMAgent<Registry extends LLMPromptRegistry = LLMPromptRegi
|
|
|
58
69
|
readonly variables: Record<string, LLMVariableValue> | undefined;
|
|
59
70
|
readonly tools: LLMToolDefinition[] | undefined;
|
|
60
71
|
readonly subagents: LLMAgentSubagent<Registry, VariableMap>[] | undefined;
|
|
72
|
+
readonly inputSchema: LLMSchemaInterface | undefined;
|
|
73
|
+
readonly canExecute: LLMDelegationGate | undefined;
|
|
61
74
|
readonly maxToolIterations: number | undefined;
|
|
62
75
|
constructor(definition: LLMAgentDefinition<Registry, VariableMap, Key>);
|
|
63
76
|
}
|
package/dist/client/LLMAgent.js
CHANGED
|
@@ -17,6 +17,8 @@ class LLMAgent {
|
|
|
17
17
|
variables;
|
|
18
18
|
tools;
|
|
19
19
|
subagents;
|
|
20
|
+
inputSchema;
|
|
21
|
+
canExecute;
|
|
20
22
|
maxToolIterations;
|
|
21
23
|
constructor(definition) {
|
|
22
24
|
this.prompt = definition.prompt;
|
|
@@ -25,6 +27,8 @@ class LLMAgent {
|
|
|
25
27
|
this.variables = definition.variables;
|
|
26
28
|
this.tools = definition.tools;
|
|
27
29
|
this.subagents = definition.subagents;
|
|
30
|
+
this.inputSchema = definition.inputSchema;
|
|
31
|
+
this.canExecute = definition.canExecute;
|
|
28
32
|
this.maxToolIterations = definition.maxToolIterations;
|
|
29
33
|
}
|
|
30
34
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LLMAgent.js","sourceRoot":"","sources":["../../src/client/LLMAgent.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"LLMAgent.js","sourceRoot":"","sources":["../../src/client/LLMAgent.ts"],"names":[],"mappings":";;;AA2EA;;;;;;;;GAQG;AACH,MAAa,QAAQ;IAMV,MAAM,CAAM;IAEZ,IAAI,CAAqB;IAEzB,WAAW,CAAqB;IAEhC,SAAS,CAA+C;IAExD,KAAK,CAAkC;IAEvC,SAAS,CAAwD;IAEjE,WAAW,CAAiC;IAE5C,UAAU,CAAgC;IAE1C,iBAAiB,CAAqB;IAE/C,YAAY,UAA0D;QACpE,IAAI,CAAC,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC;QAChC,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC;QAC5B,IAAI,CAAC,WAAW,GAAG,UAAU,CAAC,WAAW,CAAC;QAC1C,IAAI,CAAC,SAAS,GAAG,UAAU,CAAC,SAAS,CAAC;QACtC,IAAI,CAAC,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC;QAC9B,IAAI,CAAC,SAAS,GAAG,UAAU,CAAC,SAAS,CAAC;QACtC,IAAI,CAAC,WAAW,GAAG,UAAU,CAAC,WAAW,CAAC;QAC1C,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC,UAAU,CAAC;QACxC,IAAI,CAAC,iBAAiB,GAAG,UAAU,CAAC,iBAAiB,CAAC;IACxD,CAAC;CACF;AAnCD,4BAmCC"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { type LLMCallTraceContext, type LLMToolResult } from '../LLMService.typedefs';
|
|
2
|
+
import { type LLMReporterInterface } from '../utilities/reporter';
|
|
3
|
+
import { type LLMSchemaInterface } from '../utilities/schema';
|
|
4
|
+
import { type LLMPromptBinding, type LLMPromptRegistry } from '../client/defineLLMPrompts';
|
|
5
|
+
import { type LLMAgentInlineRunOptions, type LLMAgentRunResult } from '../client/agentRun.typedefs';
|
|
6
|
+
import { LLMAgent, type LLMAgentDefinition } from '../client/LLMAgent';
|
|
7
|
+
import { type LLMAgentCallServices, type LLMPromptResolutionCall } from '../client/agentRunner';
|
|
8
|
+
import { type LLMClientCallContext, type LLMClientEngineConfig, type LLMGenerateOptions, type LLMGenerateResult, type LLMPlainMessage, type LLMPromptDescription, type LLMToolResultContext } from '../client/llmClient.typedefs';
|
|
9
|
+
import { type LLMVariableValue } from '../client/promptSnapshot.typedefs';
|
|
10
|
+
import { type ResolvedPrompt } from '../client/promptRegistry.runtime';
|
|
11
|
+
/**
|
|
12
|
+
* The client's single implementation. `createLLMClient` owns its construction
|
|
13
|
+
* and exposes only the `LLMClient` surface, so every call path — `generate`,
|
|
14
|
+
* `countTokens`, `runAgent`, `describePrompt` — shares one prompt resolution,
|
|
15
|
+
* one credentials lookup, one reporter/trace context, and one error taxonomy.
|
|
16
|
+
*/
|
|
17
|
+
export declare class LLMClientEngine<Registry extends LLMPromptRegistry> implements LLMAgentCallServices {
|
|
18
|
+
private readonly gateway;
|
|
19
|
+
private readonly registry;
|
|
20
|
+
private readonly runtime;
|
|
21
|
+
private readonly credentials;
|
|
22
|
+
private readonly reporter;
|
|
23
|
+
private readonly logger;
|
|
24
|
+
private readonly staticContext;
|
|
25
|
+
private readonly mode;
|
|
26
|
+
private readonly toolResultNormalizer;
|
|
27
|
+
private readonly agentRunner;
|
|
28
|
+
constructor(config: LLMClientEngineConfig<Registry>);
|
|
29
|
+
generate<Key extends keyof Registry, Schema extends LLMSchemaInterface = never>(prompt: Key, options: LLMGenerateOptions<Record<string, LLMVariableValue>, Schema>): Promise<unknown>;
|
|
30
|
+
defineAgent<Key extends keyof Registry>(definition: LLMAgentDefinition<Registry, Record<keyof Registry, string>, Key>): LLMAgent<Registry, Record<keyof Registry, string>, Key>;
|
|
31
|
+
runAgent<Key extends keyof Registry>(promptOrAgent: Key | LLMAgent<Registry, Record<keyof Registry, string>>, options: LLMAgentInlineRunOptions<Record<string, LLMVariableValue>, Registry, Record<keyof Registry, string>>): Promise<LLMAgentRunResult<LLMGenerateResult<Registry[Key]>>>;
|
|
32
|
+
countTokens<Key extends keyof Registry>(prompt: Key, options: {
|
|
33
|
+
variables: Record<string, LLMVariableValue>;
|
|
34
|
+
history?: LLMPlainMessage[];
|
|
35
|
+
}): Promise<number>;
|
|
36
|
+
describePrompt<Key extends keyof Registry>(prompt: Key): Promise<LLMPromptDescription>;
|
|
37
|
+
resolvePrompt(promptName: string, binding: LLMPromptBinding, variables: Record<string, LLMVariableValue>, call: LLMPromptResolutionCall): Promise<ResolvedPrompt>;
|
|
38
|
+
/**
|
|
39
|
+
* The single place a tool's return value becomes what the model reads. The
|
|
40
|
+
* package's own coercion always runs first, so a client-supplied normalizer
|
|
41
|
+
* never sees a malformed value and cannot reintroduce one.
|
|
42
|
+
*/
|
|
43
|
+
normalizeToolResult(context: LLMToolResultContext): LLMToolResult;
|
|
44
|
+
private runCompletion;
|
|
45
|
+
/**
|
|
46
|
+
* Builds the provider message envelope from the resolved prompt. A `text`
|
|
47
|
+
* prompt is one user message. A `chat` prompt preserves the structure the
|
|
48
|
+
* prompt declares: a leading `system` message maps to `instructions`, the final
|
|
49
|
+
* `user` message is the request, and any messages between them join the
|
|
50
|
+
* call-supplied history in order.
|
|
51
|
+
*/
|
|
52
|
+
private buildMessageEnvelope;
|
|
53
|
+
private findLastUserIndex;
|
|
54
|
+
private callProvider;
|
|
55
|
+
unwrapResult(resolved: ResolvedPrompt, schema: LLMSchemaInterface | undefined, sendResult: {
|
|
56
|
+
text?: string;
|
|
57
|
+
data?: unknown;
|
|
58
|
+
parseError?: string;
|
|
59
|
+
}): unknown;
|
|
60
|
+
toTaxonomyError(error: unknown, resolved: ResolvedPrompt, abortSignal?: AbortSignal): Error;
|
|
61
|
+
buildAssistanceService(resolved: ResolvedPrompt, promptName: string): Promise<import("..").LLMAssistanceService<import("../LLMService.typedefs").LLMProviders, LLMReporterInterface<any>>>;
|
|
62
|
+
getBinding(promptName: string): LLMPromptBinding | undefined;
|
|
63
|
+
private buildCompletionService;
|
|
64
|
+
private resolveProviderOptions;
|
|
65
|
+
private buildUserMessage;
|
|
66
|
+
private buildHistory;
|
|
67
|
+
private toCompletionMessage;
|
|
68
|
+
buildReporterContext(resolved: ResolvedPrompt, callContext: LLMClientCallContext | undefined): Record<string, unknown> | undefined;
|
|
69
|
+
/**
|
|
70
|
+
* Builds the per-call trace context every client path threads into the gateway
|
|
71
|
+
* service. The structural `prompt` names the observation and carries the
|
|
72
|
+
* managed prompt version, so the generation the service records on success and
|
|
73
|
+
* on error is the single one Langfuse links to that prompt — the client never
|
|
74
|
+
* records a second observation of its own.
|
|
75
|
+
*/
|
|
76
|
+
buildTraceContext(resolved: ResolvedPrompt, callContext: LLMClientCallContext | undefined): LLMCallTraceContext;
|
|
77
|
+
private assertOverridesAllowed;
|
|
78
|
+
private serializeCallContext;
|
|
79
|
+
private extractStatus;
|
|
80
|
+
private isRetryableStatus;
|
|
81
|
+
}
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LLMClientEngine = void 0;
|
|
4
|
+
const LLMService_typedefs_1 = require("../LLMService.typedefs");
|
|
5
|
+
const LLMAgent_1 = require("../client/LLMAgent");
|
|
6
|
+
const agentRunner_1 = require("../client/agentRunner");
|
|
7
|
+
const plainMessages_1 = require("../client/plainMessages");
|
|
8
|
+
const coerceToolResult_1 = require("../client/coerceToolResult");
|
|
9
|
+
const promptVariables_1 = require("../client/promptVariables");
|
|
10
|
+
const errors_1 = require("../client/errors");
|
|
11
|
+
/**
|
|
12
|
+
* The client's single implementation. `createLLMClient` owns its construction
|
|
13
|
+
* and exposes only the `LLMClient` surface, so every call path — `generate`,
|
|
14
|
+
* `countTokens`, `runAgent`, `describePrompt` — shares one prompt resolution,
|
|
15
|
+
* one credentials lookup, one reporter/trace context, and one error taxonomy.
|
|
16
|
+
*/
|
|
17
|
+
class LLMClientEngine {
|
|
18
|
+
gateway;
|
|
19
|
+
registry;
|
|
20
|
+
runtime;
|
|
21
|
+
credentials;
|
|
22
|
+
reporter;
|
|
23
|
+
logger;
|
|
24
|
+
staticContext;
|
|
25
|
+
mode;
|
|
26
|
+
toolResultNormalizer;
|
|
27
|
+
agentRunner;
|
|
28
|
+
constructor(config) {
|
|
29
|
+
this.gateway = config.gateway;
|
|
30
|
+
this.registry = config.registry;
|
|
31
|
+
this.runtime = config.runtime;
|
|
32
|
+
this.credentials = config.credentials;
|
|
33
|
+
this.reporter = config.reporter;
|
|
34
|
+
this.logger = config.logger;
|
|
35
|
+
this.staticContext = config.staticContext;
|
|
36
|
+
this.mode = config.mode;
|
|
37
|
+
this.toolResultNormalizer = config.normalizeToolResult;
|
|
38
|
+
this.agentRunner = new agentRunner_1.LLMAgentRunner(this, config.logger);
|
|
39
|
+
}
|
|
40
|
+
async generate(prompt, options) {
|
|
41
|
+
const promptName = String(prompt);
|
|
42
|
+
const binding = this.registry[prompt];
|
|
43
|
+
const schema = options.schema ?? binding.schema;
|
|
44
|
+
this.assertOverridesAllowed(promptName, options.overrides);
|
|
45
|
+
const resolved = await this.resolvePrompt(promptName, binding, options.variables, {
|
|
46
|
+
purpose: LLMService_typedefs_1.LLMPurposes.Completion,
|
|
47
|
+
overrides: options.overrides,
|
|
48
|
+
});
|
|
49
|
+
return this.runCompletion(resolved, schema, options);
|
|
50
|
+
}
|
|
51
|
+
defineAgent(definition) {
|
|
52
|
+
return new LLMAgent_1.LLMAgent(definition);
|
|
53
|
+
}
|
|
54
|
+
async runAgent(promptOrAgent, options) {
|
|
55
|
+
const agent = promptOrAgent instanceof LLMAgent_1.LLMAgent ? promptOrAgent : undefined;
|
|
56
|
+
const promptKey = (agent ? agent.prompt : promptOrAgent);
|
|
57
|
+
const promptName = String(promptKey);
|
|
58
|
+
const binding = this.registry[promptKey];
|
|
59
|
+
this.assertOverridesAllowed(promptName, options.overrides);
|
|
60
|
+
const runResult = await this.agentRunner.run({
|
|
61
|
+
agentName: agent?.name ?? promptName,
|
|
62
|
+
promptName,
|
|
63
|
+
binding,
|
|
64
|
+
variables: { ...agent?.variables, ...options.variables },
|
|
65
|
+
input: options.input,
|
|
66
|
+
history: options.history,
|
|
67
|
+
tools: agent ? agent.tools : options.tools,
|
|
68
|
+
subagents: (agent ? agent.subagents : options.subagents),
|
|
69
|
+
maxToolIterations: agent
|
|
70
|
+
? agent.maxToolIterations
|
|
71
|
+
: options.maxToolIterations,
|
|
72
|
+
context: options.context,
|
|
73
|
+
abortSignal: options.abortSignal,
|
|
74
|
+
overrides: options.overrides,
|
|
75
|
+
onEvent: options.onEvent,
|
|
76
|
+
});
|
|
77
|
+
return runResult;
|
|
78
|
+
}
|
|
79
|
+
async countTokens(prompt, options) {
|
|
80
|
+
const promptName = String(prompt);
|
|
81
|
+
const binding = this.registry[prompt];
|
|
82
|
+
const resolved = await this.resolvePrompt(promptName, binding, options.variables, { purpose: LLMService_typedefs_1.LLMPurposes.Completion });
|
|
83
|
+
const service = await this.buildCompletionService(resolved, promptName);
|
|
84
|
+
const envelope = this.buildMessageEnvelope(resolved, options.history);
|
|
85
|
+
return service.countTokens([...envelope.history, envelope.message], resolved.model);
|
|
86
|
+
}
|
|
87
|
+
async describePrompt(prompt) {
|
|
88
|
+
const promptName = String(prompt);
|
|
89
|
+
const binding = this.registry[prompt];
|
|
90
|
+
const metadata = await this.runtime.describe({
|
|
91
|
+
name: promptName,
|
|
92
|
+
kind: binding.kind === 'chat' ? 'chat' : 'text',
|
|
93
|
+
requires: binding.requires ?? [],
|
|
94
|
+
purpose: LLMService_typedefs_1.LLMPurposes.Completion,
|
|
95
|
+
});
|
|
96
|
+
if (!metadata) {
|
|
97
|
+
throw new errors_1.LLMConfigError(`unable to resolve routing metadata for "${promptName}" (live and `
|
|
98
|
+
+ 'fallback both unusable)', { promptName });
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
provider: metadata.provider,
|
|
102
|
+
model: metadata.model,
|
|
103
|
+
promptVersion: metadata.promptVersion,
|
|
104
|
+
isFallback: metadata.isFallback,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
async resolvePrompt(promptName, binding, variables, call) {
|
|
108
|
+
const resolved = await this.runtime.resolve({
|
|
109
|
+
name: promptName,
|
|
110
|
+
kind: binding.kind === 'chat' ? 'chat' : 'text',
|
|
111
|
+
variables: (0, promptVariables_1.stringifyPromptVariables)(variables),
|
|
112
|
+
requires: [
|
|
113
|
+
...(binding.requires ?? []),
|
|
114
|
+
...(call.additionalRequires ?? []),
|
|
115
|
+
],
|
|
116
|
+
purpose: call.purpose,
|
|
117
|
+
versionOverride: call.overrides?.promptVersion,
|
|
118
|
+
labelOverride: call.overrides?.promptLabel,
|
|
119
|
+
});
|
|
120
|
+
if (!resolved) {
|
|
121
|
+
throw new errors_1.LLMConfigError(`unable to resolve a valid prompt for "${promptName}" (live and fallback unusable)`, { promptName });
|
|
122
|
+
}
|
|
123
|
+
return resolved;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* The single place a tool's return value becomes what the model reads. The
|
|
127
|
+
* package's own coercion always runs first, so a client-supplied normalizer
|
|
128
|
+
* never sees a malformed value and cannot reintroduce one.
|
|
129
|
+
*/
|
|
130
|
+
normalizeToolResult(context) {
|
|
131
|
+
const coerced = (0, coerceToolResult_1.coerceToolResult)(context.result);
|
|
132
|
+
if (!this.toolResultNormalizer) {
|
|
133
|
+
return coerced;
|
|
134
|
+
}
|
|
135
|
+
return (0, coerceToolResult_1.coerceToolResult)(this.toolResultNormalizer({ ...context, result: coerced }));
|
|
136
|
+
}
|
|
137
|
+
async runCompletion(resolved, schema, options) {
|
|
138
|
+
const service = await this.buildCompletionService(resolved, resolved.name);
|
|
139
|
+
const envelope = this.buildMessageEnvelope(resolved, options.history);
|
|
140
|
+
const reporterContext = this.buildReporterContext(resolved, options.context);
|
|
141
|
+
const traceContext = this.buildTraceContext(resolved, options.context);
|
|
142
|
+
const sendResult = await this.callProvider(() => (schema
|
|
143
|
+
? service.sendMessage({
|
|
144
|
+
message: envelope.message,
|
|
145
|
+
model: resolved.model,
|
|
146
|
+
history: envelope.history,
|
|
147
|
+
instructions: envelope.instructions,
|
|
148
|
+
schema,
|
|
149
|
+
abortSignal: options.abortSignal,
|
|
150
|
+
reporterContext,
|
|
151
|
+
traceContext,
|
|
152
|
+
})
|
|
153
|
+
: service.sendMessage({
|
|
154
|
+
message: envelope.message,
|
|
155
|
+
model: resolved.model,
|
|
156
|
+
history: envelope.history,
|
|
157
|
+
instructions: envelope.instructions,
|
|
158
|
+
abortSignal: options.abortSignal,
|
|
159
|
+
reporterContext,
|
|
160
|
+
traceContext,
|
|
161
|
+
})), resolved, options.abortSignal);
|
|
162
|
+
return this.unwrapResult(resolved, schema, sendResult);
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Builds the provider message envelope from the resolved prompt. A `text`
|
|
166
|
+
* prompt is one user message. A `chat` prompt preserves the structure the
|
|
167
|
+
* prompt declares: a leading `system` message maps to `instructions`, the final
|
|
168
|
+
* `user` message is the request, and any messages between them join the
|
|
169
|
+
* call-supplied history in order.
|
|
170
|
+
*/
|
|
171
|
+
buildMessageEnvelope(resolved, callHistory) {
|
|
172
|
+
if (resolved.kind !== 'chat') {
|
|
173
|
+
return {
|
|
174
|
+
message: this.buildUserMessage(resolved.text),
|
|
175
|
+
history: this.buildHistory(callHistory),
|
|
176
|
+
instructions: undefined,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
const systemMessage = resolved.messages.find((message) => message.role === 'system');
|
|
180
|
+
const conversation = resolved.messages.filter((message) => message.role !== 'system');
|
|
181
|
+
const lastUserIndex = this.findLastUserIndex(conversation);
|
|
182
|
+
const promptHistory = conversation
|
|
183
|
+
.filter((_, index) => index !== lastUserIndex)
|
|
184
|
+
.map((message) => this.toCompletionMessage(message.role, message.content));
|
|
185
|
+
const requestText = lastUserIndex >= 0
|
|
186
|
+
? conversation[lastUserIndex].content
|
|
187
|
+
: resolved.text;
|
|
188
|
+
return {
|
|
189
|
+
message: this.buildUserMessage(requestText),
|
|
190
|
+
history: [...promptHistory, ...this.buildHistory(callHistory)],
|
|
191
|
+
instructions: systemMessage?.content,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
findLastUserIndex(messages) {
|
|
195
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
196
|
+
if (messages[index].role === 'user') {
|
|
197
|
+
return index;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return -1;
|
|
201
|
+
}
|
|
202
|
+
async callProvider(send, resolved, abortSignal) {
|
|
203
|
+
try {
|
|
204
|
+
const result = await send();
|
|
205
|
+
if (result && typeof result === 'object' && 'error' in result) {
|
|
206
|
+
throw result.error;
|
|
207
|
+
}
|
|
208
|
+
return result;
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
throw this.toTaxonomyError(error, resolved, abortSignal);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
unwrapResult(resolved, schema, sendResult) {
|
|
215
|
+
if (!schema) {
|
|
216
|
+
return sendResult.text ?? '';
|
|
217
|
+
}
|
|
218
|
+
if (sendResult.parseError || sendResult.data === undefined) {
|
|
219
|
+
throw new errors_1.LLMSchemaValidationError(`structured output for "${resolved.name}" failed schema validation`, {
|
|
220
|
+
promptName: resolved.name,
|
|
221
|
+
provider: resolved.config.provider,
|
|
222
|
+
model: resolved.config.model,
|
|
223
|
+
rawText: sendResult.text ?? '',
|
|
224
|
+
parseDetails: sendResult.parseError ?? 'no data returned',
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
return sendResult.data;
|
|
228
|
+
}
|
|
229
|
+
toTaxonomyError(error, resolved, abortSignal) {
|
|
230
|
+
const context = {
|
|
231
|
+
promptName: resolved.name,
|
|
232
|
+
provider: resolved.config.provider,
|
|
233
|
+
model: resolved.config.model,
|
|
234
|
+
cause: error,
|
|
235
|
+
};
|
|
236
|
+
if (abortSignal?.aborted) {
|
|
237
|
+
return new errors_1.LLMAbortedError('LLM call aborted', context);
|
|
238
|
+
}
|
|
239
|
+
const status = this.extractStatus(error);
|
|
240
|
+
return new errors_1.LLMProviderError(error instanceof Error ? error.message : String(error), { ...context, retryable: this.isRetryableStatus(status), status });
|
|
241
|
+
}
|
|
242
|
+
async buildAssistanceService(resolved, promptName) {
|
|
243
|
+
const providerOptions = await this.resolveProviderOptions(resolved, promptName);
|
|
244
|
+
return this.gateway.getAssistanceService({
|
|
245
|
+
provider: resolved.config.provider,
|
|
246
|
+
options: providerOptions,
|
|
247
|
+
logger: this.logger,
|
|
248
|
+
reporter: this.reporter,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
getBinding(promptName) {
|
|
252
|
+
return this.registry[promptName];
|
|
253
|
+
}
|
|
254
|
+
async buildCompletionService(resolved, promptName) {
|
|
255
|
+
const providerOptions = await this.resolveProviderOptions(resolved, promptName);
|
|
256
|
+
return this.gateway.getCompletionService({
|
|
257
|
+
provider: resolved.config.provider,
|
|
258
|
+
options: providerOptions,
|
|
259
|
+
logger: this.logger,
|
|
260
|
+
reporter: this.reporter,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
async resolveProviderOptions(resolved, promptName) {
|
|
264
|
+
return this.credentials({
|
|
265
|
+
provider: resolved.config.provider,
|
|
266
|
+
promptName,
|
|
267
|
+
product: this.staticContext.product,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
buildUserMessage(text) {
|
|
271
|
+
return this.toCompletionMessage('user', text);
|
|
272
|
+
}
|
|
273
|
+
buildHistory(history) {
|
|
274
|
+
return (0, plainMessages_1.plainMessagesToContentMessages)(history);
|
|
275
|
+
}
|
|
276
|
+
toCompletionMessage(role, text) {
|
|
277
|
+
return {
|
|
278
|
+
role: role === 'assistant' ? LLMService_typedefs_1.LLMRoles.Assistant : LLMService_typedefs_1.LLMRoles.User,
|
|
279
|
+
content: [{ type: LLMService_typedefs_1.LLMMessageContentType.TEXT, text }],
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
buildReporterContext(resolved, callContext) {
|
|
283
|
+
if (!this.reporter) {
|
|
284
|
+
return undefined;
|
|
285
|
+
}
|
|
286
|
+
return {
|
|
287
|
+
...this.staticContext,
|
|
288
|
+
...this.serializeCallContext(callContext),
|
|
289
|
+
promptName: resolved.name,
|
|
290
|
+
promptVersion: resolved.version,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Builds the per-call trace context every client path threads into the gateway
|
|
295
|
+
* service. The structural `prompt` names the observation and carries the
|
|
296
|
+
* managed prompt version, so the generation the service records on success and
|
|
297
|
+
* on error is the single one Langfuse links to that prompt — the client never
|
|
298
|
+
* records a second observation of its own.
|
|
299
|
+
*/
|
|
300
|
+
buildTraceContext(resolved, callContext) {
|
|
301
|
+
const metadata = {
|
|
302
|
+
promptVersion: resolved.version,
|
|
303
|
+
isFallback: resolved.isFallback,
|
|
304
|
+
};
|
|
305
|
+
const userId = callContext?.userId;
|
|
306
|
+
if (userId !== undefined && userId !== null) {
|
|
307
|
+
metadata.userId = userId;
|
|
308
|
+
}
|
|
309
|
+
return {
|
|
310
|
+
prompt: {
|
|
311
|
+
name: resolved.name,
|
|
312
|
+
version: resolved.version,
|
|
313
|
+
isFallback: resolved.isFallback,
|
|
314
|
+
},
|
|
315
|
+
metadata,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
assertOverridesAllowed(promptName, overrides) {
|
|
319
|
+
if (this.mode === 'experiment' || !overrides) {
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
const hasOverride = Object.values(overrides).some((value) => value !== undefined);
|
|
323
|
+
if (hasOverride) {
|
|
324
|
+
throw new errors_1.LLMConfigError(`per-call overrides are rejected by a production-mode client for "${promptName}"`, { promptName });
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
serializeCallContext(callContext) {
|
|
328
|
+
if (!callContext) {
|
|
329
|
+
return {};
|
|
330
|
+
}
|
|
331
|
+
return Object.fromEntries(Object.entries(callContext).filter((entry) => (entry[1] !== undefined && entry[1] !== null)));
|
|
332
|
+
}
|
|
333
|
+
extractStatus(error) {
|
|
334
|
+
if (error
|
|
335
|
+
&& typeof error === 'object'
|
|
336
|
+
&& 'status' in error
|
|
337
|
+
&& typeof error.status === 'number') {
|
|
338
|
+
return error.status;
|
|
339
|
+
}
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
isRetryableStatus(status) {
|
|
343
|
+
if (status === null) {
|
|
344
|
+
return true;
|
|
345
|
+
}
|
|
346
|
+
return status === 429 || status >= 500;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
exports.LLMClientEngine = LLMClientEngine;
|
|
350
|
+
//# sourceMappingURL=LLMClientEngine.js.map
|