@librechat/agents 4.0.1 → 4.0.3
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/agents/AgentContext.cjs +3 -2
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/decisions/deadline.cjs +52 -0
- package/dist/cjs/decisions/deadline.cjs.map +1 -0
- package/dist/cjs/decisions/dialect.cjs +88 -0
- package/dist/cjs/decisions/dialect.cjs.map +1 -0
- package/dist/cjs/decisions/http.cjs +77 -0
- package/dist/cjs/decisions/http.cjs.map +1 -0
- package/dist/cjs/decisions/index.cjs +7 -0
- package/dist/cjs/decisions/presets.cjs +82 -0
- package/dist/cjs/decisions/presets.cjs.map +1 -0
- package/dist/cjs/decisions/questions.cjs +70 -0
- package/dist/cjs/decisions/questions.cjs.map +1 -0
- package/dist/cjs/decisions/structuredChat.cjs +172 -0
- package/dist/cjs/decisions/structuredChat.cjs.map +1 -0
- package/dist/cjs/decisions/structuredOutput.cjs +18 -0
- package/dist/cjs/decisions/structuredOutput.cjs.map +1 -0
- package/dist/cjs/decisions/traceMarker.cjs +6 -0
- package/dist/cjs/decisions/traceMarker.cjs.map +1 -0
- package/dist/cjs/decisions/transport.cjs +166 -0
- package/dist/cjs/decisions/transport.cjs.map +1 -0
- package/dist/cjs/decisions/types.cjs +44 -0
- package/dist/cjs/decisions/types.cjs.map +1 -0
- package/dist/cjs/graphs/Graph.cjs +28 -9
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/langfuseToolOutputTracing.cjs +6 -1
- package/dist/cjs/langfuseToolOutputTracing.cjs.map +1 -1
- package/dist/cjs/llm/google/index.cjs +1 -1
- package/dist/cjs/llm/invoke.cjs +34 -12
- package/dist/cjs/llm/invoke.cjs.map +1 -1
- package/dist/cjs/llm/openai/index.cjs +7 -0
- package/dist/cjs/llm/openai/index.cjs.map +1 -1
- package/dist/cjs/llm/providerTextBoundary.cjs +355 -0
- package/dist/cjs/llm/providerTextBoundary.cjs.map +1 -0
- package/dist/cjs/llm/providerTextChunk.cjs +14 -0
- package/dist/cjs/llm/providerTextChunk.cjs.map +1 -0
- package/dist/cjs/llm/providerTextControls.cjs +236 -0
- package/dist/cjs/llm/providerTextControls.cjs.map +1 -0
- package/dist/cjs/llm/stream/smoother.cjs +2 -1
- package/dist/cjs/llm/stream/smoother.cjs.map +1 -1
- package/dist/cjs/main.cjs +49 -1
- package/dist/cjs/protection/providerText.cjs +224 -0
- package/dist/cjs/protection/providerText.cjs.map +1 -0
- package/dist/cjs/protection/providerTextInput.cjs +25 -0
- package/dist/cjs/protection/providerTextInput.cjs.map +1 -0
- package/dist/cjs/rerank/index.cjs +2 -0
- package/dist/cjs/rerank/search.cjs +45 -0
- package/dist/cjs/rerank/search.cjs.map +1 -0
- package/dist/cjs/rerank/systemone.cjs +70 -0
- package/dist/cjs/rerank/systemone.cjs.map +1 -0
- package/dist/cjs/run.cjs +11 -1
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/stream.cjs +42 -14
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/summarization/node.cjs +26 -8
- package/dist/cjs/summarization/node.cjs.map +1 -1
- package/dist/cjs/tools/SubagentTool.cjs +11 -3
- package/dist/cjs/tools/SubagentTool.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +6 -5
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/search/rerankers.cjs +1 -0
- package/dist/cjs/tools/search/search.cjs +3 -3
- package/dist/cjs/tools/search/search.cjs.map +1 -1
- package/dist/cjs/tools/search/tool.cjs +4 -3
- package/dist/cjs/tools/search/tool.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutionRegistry.cjs +13 -2
- package/dist/cjs/tools/subagent/SubagentExecutionRegistry.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +149 -26
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentReplay.cjs +4 -2
- package/dist/cjs/tools/subagent/SubagentReplay.cjs.map +1 -1
- package/dist/cjs/tools/subagent/childGraphConfig.cjs +3 -1
- package/dist/cjs/tools/subagent/childGraphConfig.cjs.map +1 -1
- package/dist/cjs/tools/subagent/diagnostics.cjs +88 -2
- package/dist/cjs/tools/subagent/diagnostics.cjs.map +1 -1
- package/dist/cjs/tools/subagent/hostArgs.cjs +214 -0
- package/dist/cjs/tools/subagent/hostArgs.cjs.map +1 -0
- package/dist/cjs/tools/subagent/index.cjs +2 -0
- package/dist/cjs/tools/toolErrorContent.cjs +1 -1
- package/dist/esm/agents/AgentContext.mjs +3 -2
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/decisions/deadline.mjs +52 -0
- package/dist/esm/decisions/deadline.mjs.map +1 -0
- package/dist/esm/decisions/dialect.mjs +87 -0
- package/dist/esm/decisions/dialect.mjs.map +1 -0
- package/dist/esm/decisions/http.mjs +75 -0
- package/dist/esm/decisions/http.mjs.map +1 -0
- package/dist/esm/decisions/index.mjs +8 -0
- package/dist/esm/decisions/presets.mjs +77 -0
- package/dist/esm/decisions/presets.mjs.map +1 -0
- package/dist/esm/decisions/questions.mjs +67 -0
- package/dist/esm/decisions/questions.mjs.map +1 -0
- package/dist/esm/decisions/structuredChat.mjs +172 -0
- package/dist/esm/decisions/structuredChat.mjs.map +1 -0
- package/dist/esm/decisions/structuredOutput.mjs +18 -0
- package/dist/esm/decisions/structuredOutput.mjs.map +1 -0
- package/dist/esm/decisions/traceMarker.mjs +6 -0
- package/dist/esm/decisions/traceMarker.mjs.map +1 -0
- package/dist/esm/decisions/transport.mjs +164 -0
- package/dist/esm/decisions/transport.mjs.map +1 -0
- package/dist/esm/decisions/types.mjs +39 -0
- package/dist/esm/decisions/types.mjs.map +1 -0
- package/dist/esm/graphs/Graph.mjs +28 -9
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/langfuseToolOutputTracing.mjs +6 -1
- package/dist/esm/langfuseToolOutputTracing.mjs.map +1 -1
- package/dist/esm/llm/google/index.mjs +1 -1
- package/dist/esm/llm/invoke.mjs +34 -12
- package/dist/esm/llm/invoke.mjs.map +1 -1
- package/dist/esm/llm/openai/index.mjs +7 -0
- package/dist/esm/llm/openai/index.mjs.map +1 -1
- package/dist/esm/llm/providerTextBoundary.mjs +355 -0
- package/dist/esm/llm/providerTextBoundary.mjs.map +1 -0
- package/dist/esm/llm/providerTextChunk.mjs +13 -0
- package/dist/esm/llm/providerTextChunk.mjs.map +1 -0
- package/dist/esm/llm/providerTextControls.mjs +230 -0
- package/dist/esm/llm/providerTextControls.mjs.map +1 -0
- package/dist/esm/llm/stream/smoother.mjs +2 -1
- package/dist/esm/llm/stream/smoother.mjs.map +1 -1
- package/dist/esm/main.mjs +16 -2
- package/dist/esm/protection/providerText.mjs +218 -0
- package/dist/esm/protection/providerText.mjs.map +1 -0
- package/dist/esm/protection/providerTextInput.mjs +24 -0
- package/dist/esm/protection/providerTextInput.mjs.map +1 -0
- package/dist/esm/rerank/index.mjs +3 -0
- package/dist/esm/rerank/search.mjs +45 -0
- package/dist/esm/rerank/search.mjs.map +1 -0
- package/dist/esm/rerank/systemone.mjs +70 -0
- package/dist/esm/rerank/systemone.mjs.map +1 -0
- package/dist/esm/run.mjs +11 -1
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/stream.mjs +42 -14
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/summarization/node.mjs +26 -8
- package/dist/esm/summarization/node.mjs.map +1 -1
- package/dist/esm/tools/SubagentTool.mjs +11 -3
- package/dist/esm/tools/SubagentTool.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +6 -5
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/search/rerankers.mjs +1 -1
- package/dist/esm/tools/search/search.mjs +3 -3
- package/dist/esm/tools/search/search.mjs.map +1 -1
- package/dist/esm/tools/search/tool.mjs +4 -3
- package/dist/esm/tools/search/tool.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentExecutionRegistry.mjs +13 -2
- package/dist/esm/tools/subagent/SubagentExecutionRegistry.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +149 -26
- package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentReplay.mjs +4 -2
- package/dist/esm/tools/subagent/SubagentReplay.mjs.map +1 -1
- package/dist/esm/tools/subagent/childGraphConfig.mjs +3 -1
- package/dist/esm/tools/subagent/childGraphConfig.mjs.map +1 -1
- package/dist/esm/tools/subagent/diagnostics.mjs +84 -3
- package/dist/esm/tools/subagent/diagnostics.mjs.map +1 -1
- package/dist/esm/tools/subagent/hostArgs.mjs +207 -0
- package/dist/esm/tools/subagent/hostArgs.mjs.map +1 -0
- package/dist/esm/tools/subagent/index.mjs +2 -0
- package/dist/esm/tools/toolErrorContent.mjs +1 -1
- package/dist/types/decisions/deadline.d.ts +3 -0
- package/dist/types/decisions/dialect.d.ts +11 -0
- package/dist/types/decisions/http.d.ts +22 -0
- package/dist/types/decisions/index.d.ts +10 -0
- package/dist/types/decisions/presets.d.ts +17 -0
- package/dist/types/decisions/questions.d.ts +8 -0
- package/dist/types/decisions/structuredChat.d.ts +17 -0
- package/dist/types/decisions/structuredOutput.d.ts +4 -0
- package/dist/types/decisions/traceMarker.d.ts +2 -0
- package/dist/types/decisions/transport.d.ts +29 -0
- package/dist/types/decisions/types.d.ts +119 -0
- package/dist/types/graphs/Graph.d.ts +7 -3
- package/dist/types/index.d.ts +4 -0
- package/dist/types/llm/invoke.d.ts +4 -1
- package/dist/types/llm/providerTextBoundary.d.ts +4 -0
- package/dist/types/llm/providerTextChunk.d.ts +7 -0
- package/dist/types/llm/providerTextControls.d.ts +18 -0
- package/dist/types/protection/providerText.d.ts +77 -0
- package/dist/types/protection/providerTextInput.d.ts +3 -0
- package/dist/types/rerank/index.d.ts +4 -0
- package/dist/types/rerank/search.d.ts +7 -0
- package/dist/types/rerank/systemone.d.ts +13 -0
- package/dist/types/rerank/types.d.ts +24 -0
- package/dist/types/run.d.ts +2 -0
- package/dist/types/tools/BashExecutor.d.ts +4 -0
- package/dist/types/tools/CodeExecutor.d.ts +4 -0
- package/dist/types/tools/ReadFile.d.ts +4 -0
- package/dist/types/tools/SkillTool.d.ts +4 -0
- package/dist/types/tools/SubagentTool.d.ts +2 -0
- package/dist/types/tools/ToolSearch.d.ts +4 -0
- package/dist/types/tools/search/schema.d.ts +4 -0
- package/dist/types/tools/search/types.d.ts +3 -1
- package/dist/types/tools/subagent/SubagentExecutionRegistry.d.ts +6 -0
- package/dist/types/tools/subagent/SubagentExecutor.d.ts +22 -1
- package/dist/types/tools/subagent/SubagentReplay.d.ts +3 -1
- package/dist/types/tools/subagent/diagnostics.d.ts +56 -8
- package/dist/types/tools/subagent/hostArgs.d.ts +57 -0
- package/dist/types/tools/subagent/index.d.ts +3 -0
- package/dist/types/types/graph.d.ts +39 -0
- package/dist/types/types/run.d.ts +6 -2
- package/dist/types/types/tools.d.ts +2 -0
- package/package.json +4 -4
- package/src/agents/AgentContext.ts +3 -2
- package/src/decisions/deadline.ts +92 -0
- package/src/decisions/dialect.ts +200 -0
- package/src/decisions/http.ts +175 -0
- package/src/decisions/index.ts +14 -0
- package/src/decisions/presets.ts +111 -0
- package/src/decisions/questions.ts +128 -0
- package/src/decisions/structuredChat.ts +358 -0
- package/src/decisions/structuredOutput.ts +25 -0
- package/src/decisions/traceMarker.ts +2 -0
- package/src/decisions/transport.ts +335 -0
- package/src/decisions/types.ts +193 -0
- package/src/graphs/Graph.ts +57 -9
- package/src/index.ts +9 -0
- package/src/langfuseToolOutputTracing.ts +8 -1
- package/src/llm/invoke.ts +48 -7
- package/src/llm/openai/index.ts +8 -0
- package/src/llm/providerTextBoundary.ts +446 -0
- package/src/llm/providerTextChunk.ts +15 -0
- package/src/llm/providerTextControls.ts +173 -0
- package/src/llm/stream/smoother.ts +4 -2
- package/src/protection/providerText.ts +325 -0
- package/src/protection/providerTextInput.ts +21 -0
- package/src/rerank/index.ts +4 -0
- package/src/rerank/search.ts +85 -0
- package/src/rerank/systemone.ts +140 -0
- package/src/rerank/types.ts +28 -0
- package/src/run.ts +13 -0
- package/src/stream.ts +33 -7
- package/src/summarization/node.ts +32 -14
- package/src/tools/SubagentTool.ts +14 -3
- package/src/tools/ToolNode.ts +7 -3
- package/src/tools/search/search.ts +4 -4
- package/src/tools/search/tool.ts +27 -25
- package/src/tools/search/types.ts +4 -1
- package/src/tools/subagent/SubagentExecutionRegistry.ts +27 -2
- package/src/tools/subagent/SubagentExecutor.ts +323 -28
- package/src/tools/subagent/SubagentReplay.ts +10 -3
- package/src/tools/subagent/childGraphConfig.ts +12 -1
- package/src/tools/subagent/diagnostics.ts +180 -9
- package/src/tools/subagent/hostArgs.ts +413 -0
- package/src/tools/subagent/index.ts +20 -0
- package/src/types/graph.ts +44 -0
- package/src/types/run.ts +6 -2
- package/src/types/tools.ts +6 -1
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
BooleanCriteria,
|
|
3
|
+
BooleanQuestion,
|
|
4
|
+
ChoiceQuestion,
|
|
5
|
+
ScoreQuestion,
|
|
6
|
+
DecisionText,
|
|
7
|
+
DecisionQuestion,
|
|
8
|
+
} from './types';
|
|
9
|
+
import { DecisionError, isDecisionObject } from './types';
|
|
10
|
+
|
|
11
|
+
function validQuestionId(id: string): boolean {
|
|
12
|
+
if (id.length === 0 || id.length > 128) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
for (let index = 0; index < id.length; index++) {
|
|
16
|
+
if (id.charCodeAt(index) < 32) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function validBooleanCriteria(criteria: BooleanQuestion['criteria']): boolean {
|
|
24
|
+
if (criteria === undefined || typeof criteria === 'string') {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
if (!isDecisionObject(criteria)) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
for (const [side, description] of Object.entries(criteria)) {
|
|
31
|
+
if (
|
|
32
|
+
(side !== 'true' && side !== 'false') ||
|
|
33
|
+
(description !== undefined &&
|
|
34
|
+
typeof description !== 'string' &&
|
|
35
|
+
!Array.isArray(description) &&
|
|
36
|
+
!isDecisionObject(description))
|
|
37
|
+
) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function validateDecisionQuestions(
|
|
45
|
+
questions: Record<string, DecisionQuestion>,
|
|
46
|
+
provider: string
|
|
47
|
+
): Array<[string, DecisionQuestion]> {
|
|
48
|
+
if (!isDecisionObject(questions)) {
|
|
49
|
+
throw new DecisionError('bad_request', 'invalid decision model questions', {
|
|
50
|
+
provider,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
const supplied = Object.entries(questions);
|
|
54
|
+
if (supplied.length === 0) {
|
|
55
|
+
throw new DecisionError(
|
|
56
|
+
'bad_request',
|
|
57
|
+
'decision model requires questions',
|
|
58
|
+
{
|
|
59
|
+
provider,
|
|
60
|
+
}
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
const entries: Array<[string, DecisionQuestion]> = [];
|
|
64
|
+
for (const [id, question] of supplied) {
|
|
65
|
+
if (
|
|
66
|
+
!validQuestionId(id) ||
|
|
67
|
+
!isDecisionObject(question) ||
|
|
68
|
+
!['boolean', 'choice', 'score'].includes(question.type) ||
|
|
69
|
+
(question.type === 'boolean' &&
|
|
70
|
+
!validBooleanCriteria(question.criteria)) ||
|
|
71
|
+
(question.type === 'choice' &&
|
|
72
|
+
(!isDecisionObject(question.criteria) ||
|
|
73
|
+
Object.keys(question.criteria).length === 0)) ||
|
|
74
|
+
(question.type === 'score' &&
|
|
75
|
+
(!Array.isArray(question.criteria) || question.criteria.length < 2))
|
|
76
|
+
) {
|
|
77
|
+
throw new DecisionError(
|
|
78
|
+
'bad_request',
|
|
79
|
+
'invalid decision model question',
|
|
80
|
+
{
|
|
81
|
+
provider,
|
|
82
|
+
}
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
if (question.type === 'choice') {
|
|
86
|
+
entries.push([id, { ...question, criteria: { ...question.criteria } }]);
|
|
87
|
+
} else if (question.type === 'score') {
|
|
88
|
+
entries.push([id, { ...question, criteria: [...question.criteria] }]);
|
|
89
|
+
} else {
|
|
90
|
+
entries.push([
|
|
91
|
+
id,
|
|
92
|
+
{
|
|
93
|
+
...question,
|
|
94
|
+
...(isDecisionObject(question.criteria)
|
|
95
|
+
? { criteria: { ...question.criteria } }
|
|
96
|
+
: {}),
|
|
97
|
+
},
|
|
98
|
+
]);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return entries;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** A yes/no question; `criteria` describes what counts as yes (a string) or both sides. */
|
|
105
|
+
export function booleanQuestion(
|
|
106
|
+
instructions: DecisionText,
|
|
107
|
+
criteria?: BooleanCriteria | string
|
|
108
|
+
): BooleanQuestion {
|
|
109
|
+
return criteria == null
|
|
110
|
+
? { type: 'boolean', instructions }
|
|
111
|
+
: { type: 'boolean', instructions, criteria };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Pick one of the named options; a `null` description means the name speaks for itself. */
|
|
115
|
+
export function choiceQuestion(
|
|
116
|
+
instructions: DecisionText,
|
|
117
|
+
criteria: Record<string, DecisionText | null>
|
|
118
|
+
): ChoiceQuestion {
|
|
119
|
+
return { type: 'choice', instructions, criteria };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Rate against an ordered rubric, level 0 first. */
|
|
123
|
+
export function scoreQuestion(
|
|
124
|
+
instructions: DecisionText,
|
|
125
|
+
levels: DecisionText[]
|
|
126
|
+
): ScoreQuestion {
|
|
127
|
+
return { type: 'score', instructions, criteria: levels };
|
|
128
|
+
}
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import { HumanMessage, SystemMessage } from '@langchain/core/messages';
|
|
2
|
+
import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
|
3
|
+
import type {
|
|
4
|
+
DecisionModel,
|
|
5
|
+
DecisionQuestion,
|
|
6
|
+
DecisionResult,
|
|
7
|
+
DecisionRequest,
|
|
8
|
+
} from './types';
|
|
9
|
+
import type { DecisionOutputMethod } from './structuredOutput';
|
|
10
|
+
import { DecisionError, isDecisionObject, readDecisionUsage } from './types';
|
|
11
|
+
import { withDecisionStructuredOutput } from './structuredOutput';
|
|
12
|
+
import { failureForStatus, retryAfterMs } from './transport';
|
|
13
|
+
import { validateDecisionQuestions } from './questions';
|
|
14
|
+
import { DECISION_PROMPT_PREFIX } from './traceMarker';
|
|
15
|
+
import { withDecisionDeadline } from './deadline';
|
|
16
|
+
|
|
17
|
+
const MAX_QUESTIONS = 32;
|
|
18
|
+
const MAX_CHOICE_OPTIONS = 128;
|
|
19
|
+
const DEFAULT_TIMEOUT_MS = 20_000;
|
|
20
|
+
const PROVIDER_ID = 'structured-chat';
|
|
21
|
+
|
|
22
|
+
export function readChatUsage(raw: unknown): DecisionResult['usage'] {
|
|
23
|
+
if (!isDecisionObject(raw)) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
const usage = readDecisionUsage(raw.usage_metadata);
|
|
27
|
+
const response = isDecisionObject(raw.response_metadata)
|
|
28
|
+
? raw.response_metadata
|
|
29
|
+
: null;
|
|
30
|
+
const nested = isDecisionObject(response?.metadata)
|
|
31
|
+
? response.metadata
|
|
32
|
+
: null;
|
|
33
|
+
const bedrock = isDecisionObject(nested?.usage)
|
|
34
|
+
? nested.usage
|
|
35
|
+
: response?.usage;
|
|
36
|
+
if (
|
|
37
|
+
usage?.inputTokens === undefined ||
|
|
38
|
+
!isDecisionObject(bedrock) ||
|
|
39
|
+
usage.inputTokens !== bedrock.inputTokens
|
|
40
|
+
) {
|
|
41
|
+
return usage;
|
|
42
|
+
}
|
|
43
|
+
const read = bedrock.cacheReadInputTokens;
|
|
44
|
+
const write = bedrock.cacheWriteInputTokens;
|
|
45
|
+
if (
|
|
46
|
+
(read != null &&
|
|
47
|
+
(typeof read !== 'number' || !Number.isSafeInteger(read) || read < 0)) ||
|
|
48
|
+
(write != null &&
|
|
49
|
+
(typeof write !== 'number' || !Number.isSafeInteger(write) || write < 0))
|
|
50
|
+
) {
|
|
51
|
+
return usage;
|
|
52
|
+
}
|
|
53
|
+
const inputTokens =
|
|
54
|
+
usage.inputTokens +
|
|
55
|
+
(typeof read === 'number' ? read : 0) +
|
|
56
|
+
(typeof write === 'number' ? write : 0);
|
|
57
|
+
return Number.isSafeInteger(inputTokens) ? { ...usage, inputTokens } : usage;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function providerFailure(error: unknown, provider: string): DecisionError {
|
|
61
|
+
const details = isDecisionObject(error) ? error : null;
|
|
62
|
+
const metadata = isDecisionObject(details?.$metadata)
|
|
63
|
+
? details.$metadata
|
|
64
|
+
: null;
|
|
65
|
+
const rawStatus =
|
|
66
|
+
details?.status ?? details?.statusCode ?? metadata?.httpStatusCode;
|
|
67
|
+
const status =
|
|
68
|
+
typeof rawStatus === 'number' &&
|
|
69
|
+
Number.isInteger(rawStatus) &&
|
|
70
|
+
rawStatus >= 100 &&
|
|
71
|
+
rawStatus <= 599
|
|
72
|
+
? rawStatus
|
|
73
|
+
: undefined;
|
|
74
|
+
const failure = new DecisionError(
|
|
75
|
+
status == null ? 'network' : failureForStatus(status),
|
|
76
|
+
'structured decision model request failed',
|
|
77
|
+
{ provider, status }
|
|
78
|
+
);
|
|
79
|
+
const headers = details?.headers;
|
|
80
|
+
if (headers instanceof Headers) {
|
|
81
|
+
failure.retryAfterMs = retryAfterMs(headers.get('retry-after'));
|
|
82
|
+
}
|
|
83
|
+
return failure;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface StructuredChatDecisionModelOptions {
|
|
87
|
+
/** An already configured chat model whose chosen method enforces strict schemas. */
|
|
88
|
+
model: BaseChatModel;
|
|
89
|
+
modelId: string;
|
|
90
|
+
providerId?: string;
|
|
91
|
+
/** Verified OpenAI/Azure strict modes or Anthropic strict tools; other paths fail closed. */
|
|
92
|
+
method: DecisionOutputMethod;
|
|
93
|
+
timeoutMs?: number;
|
|
94
|
+
maxQuestions?: number;
|
|
95
|
+
onAnswered?: (label: string, ms: number) => void;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function decisionSchema(
|
|
99
|
+
entries: Array<[string, DecisionQuestion]>
|
|
100
|
+
): Record<string, unknown> {
|
|
101
|
+
const properties: Record<string, object> = Object.create(null);
|
|
102
|
+
for (const [id, question] of entries) {
|
|
103
|
+
if (question.type === 'boolean') {
|
|
104
|
+
properties[id] = {
|
|
105
|
+
type: 'object',
|
|
106
|
+
properties: { decision: { type: 'boolean' } },
|
|
107
|
+
required: ['decision'],
|
|
108
|
+
additionalProperties: false,
|
|
109
|
+
};
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
properties[id] = {
|
|
113
|
+
type: 'object',
|
|
114
|
+
properties: {
|
|
115
|
+
choice: { type: 'string', enum: Object.keys(question.criteria) },
|
|
116
|
+
},
|
|
117
|
+
required: ['choice'],
|
|
118
|
+
additionalProperties: false,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
type: 'object',
|
|
123
|
+
properties: {
|
|
124
|
+
answers: {
|
|
125
|
+
type: 'object',
|
|
126
|
+
properties,
|
|
127
|
+
required: entries.map(([id]) => id),
|
|
128
|
+
additionalProperties: false,
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
required: ['answers'],
|
|
132
|
+
additionalProperties: false,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Do not trust even a provider-parsed response: check every required id and choice locally. */
|
|
137
|
+
function readDecisions(
|
|
138
|
+
parsed: unknown,
|
|
139
|
+
entries: Array<[string, DecisionQuestion]>,
|
|
140
|
+
provider: string
|
|
141
|
+
): DecisionResult['answers'] {
|
|
142
|
+
const invalid = (): DecisionError =>
|
|
143
|
+
new DecisionError(
|
|
144
|
+
'malformed_response',
|
|
145
|
+
'invalid structured decision model answer',
|
|
146
|
+
{
|
|
147
|
+
provider,
|
|
148
|
+
}
|
|
149
|
+
);
|
|
150
|
+
if (
|
|
151
|
+
!isDecisionObject(parsed) ||
|
|
152
|
+
Object.keys(parsed).length !== 1 ||
|
|
153
|
+
!isDecisionObject(parsed.answers) ||
|
|
154
|
+
Object.keys(parsed.answers).length !== entries.length
|
|
155
|
+
) {
|
|
156
|
+
throw invalid();
|
|
157
|
+
}
|
|
158
|
+
const answers: DecisionResult['answers'] = Object.create(null);
|
|
159
|
+
for (const [id, question] of entries) {
|
|
160
|
+
if (!Object.hasOwn(parsed.answers, id)) {
|
|
161
|
+
throw invalid();
|
|
162
|
+
}
|
|
163
|
+
const raw = parsed.answers[id];
|
|
164
|
+
if (!isDecisionObject(raw) || Object.keys(raw).length !== 1) {
|
|
165
|
+
throw invalid();
|
|
166
|
+
}
|
|
167
|
+
if (question.type === 'boolean') {
|
|
168
|
+
if (typeof raw.decision !== 'boolean') {
|
|
169
|
+
throw invalid();
|
|
170
|
+
}
|
|
171
|
+
answers[id] = {
|
|
172
|
+
type: 'boolean',
|
|
173
|
+
decision: raw.decision,
|
|
174
|
+
probability: null,
|
|
175
|
+
};
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (
|
|
179
|
+
question.type !== 'choice' ||
|
|
180
|
+
typeof raw.choice !== 'string' ||
|
|
181
|
+
!Object.hasOwn(question.criteria, raw.choice)
|
|
182
|
+
) {
|
|
183
|
+
throw invalid();
|
|
184
|
+
}
|
|
185
|
+
answers[id] = {
|
|
186
|
+
type: 'choice',
|
|
187
|
+
choice: raw.choice,
|
|
188
|
+
confidence: null,
|
|
189
|
+
probabilities: null,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
return answers;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function questionsForChat(
|
|
196
|
+
questions: DecisionRequest['questions'],
|
|
197
|
+
provider: string,
|
|
198
|
+
maxQuestions: number
|
|
199
|
+
): Array<[string, DecisionQuestion]> {
|
|
200
|
+
const entries = validateDecisionQuestions(questions, provider);
|
|
201
|
+
let choices = 0;
|
|
202
|
+
for (const [, question] of entries) {
|
|
203
|
+
if (question.type === 'score') {
|
|
204
|
+
throw new DecisionError(
|
|
205
|
+
'unsupported_question',
|
|
206
|
+
'chat decision cannot score expected values',
|
|
207
|
+
{ provider }
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
if (question.type === 'choice') {
|
|
211
|
+
choices += Object.keys(question.criteria).length;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (entries.length > maxQuestions || choices > MAX_CHOICE_OPTIONS) {
|
|
215
|
+
throw new DecisionError(
|
|
216
|
+
'unsupported_question',
|
|
217
|
+
'decision model question batch too large',
|
|
218
|
+
{ provider }
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
return entries;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** Strict provider schema or strict tool calling, without an agent loop or fabricated probabilities. */
|
|
225
|
+
export function createStructuredChatDecisionModel(
|
|
226
|
+
options: StructuredChatDecisionModelOptions
|
|
227
|
+
): DecisionModel {
|
|
228
|
+
const provider = options.providerId ?? PROVIDER_ID;
|
|
229
|
+
if (!['jsonSchema', 'functionCalling'].includes(options.method)) {
|
|
230
|
+
throw new DecisionError(
|
|
231
|
+
'unsupported_mode',
|
|
232
|
+
'unsupported structured decision model mode',
|
|
233
|
+
{
|
|
234
|
+
provider,
|
|
235
|
+
}
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
if (!options.modelId.trim()) {
|
|
239
|
+
throw new DecisionError(
|
|
240
|
+
'bad_request',
|
|
241
|
+
'decision model requires a model id',
|
|
242
|
+
{
|
|
243
|
+
provider,
|
|
244
|
+
}
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
const maxQuestions = options.maxQuestions ?? MAX_QUESTIONS;
|
|
248
|
+
if (
|
|
249
|
+
!Number.isSafeInteger(maxQuestions) ||
|
|
250
|
+
maxQuestions < 1 ||
|
|
251
|
+
maxQuestions > MAX_QUESTIONS
|
|
252
|
+
) {
|
|
253
|
+
throw new DecisionError(
|
|
254
|
+
'bad_request',
|
|
255
|
+
'invalid decision model question limit',
|
|
256
|
+
{
|
|
257
|
+
provider,
|
|
258
|
+
}
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
const { model, method } = options;
|
|
262
|
+
const modelId = options.modelId.trim();
|
|
263
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
264
|
+
const onAnswered = options.onAnswered;
|
|
265
|
+
|
|
266
|
+
return {
|
|
267
|
+
id: provider,
|
|
268
|
+
model: modelId,
|
|
269
|
+
async decide(request: DecisionRequest): Promise<DecisionResult> {
|
|
270
|
+
const started = performance.now();
|
|
271
|
+
return withDecisionDeadline(
|
|
272
|
+
provider,
|
|
273
|
+
request.timeoutMs ?? timeoutMs,
|
|
274
|
+
request.signal,
|
|
275
|
+
async (signal, waitFor) => {
|
|
276
|
+
let entries: Array<[string, DecisionQuestion]>;
|
|
277
|
+
let input: string;
|
|
278
|
+
try {
|
|
279
|
+
entries = questionsForChat(
|
|
280
|
+
request.questions,
|
|
281
|
+
provider,
|
|
282
|
+
maxQuestions
|
|
283
|
+
);
|
|
284
|
+
input =
|
|
285
|
+
DECISION_PROMPT_PREFIX +
|
|
286
|
+
JSON.stringify({
|
|
287
|
+
state: request.state,
|
|
288
|
+
questions: Object.fromEntries(entries),
|
|
289
|
+
});
|
|
290
|
+
} catch (error) {
|
|
291
|
+
if (error instanceof DecisionError) {
|
|
292
|
+
throw error;
|
|
293
|
+
}
|
|
294
|
+
throw new DecisionError(
|
|
295
|
+
'bad_request',
|
|
296
|
+
'invalid decision model request',
|
|
297
|
+
{
|
|
298
|
+
provider,
|
|
299
|
+
}
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
let structured: ReturnType<typeof model.withStructuredOutput>;
|
|
303
|
+
try {
|
|
304
|
+
structured = withDecisionStructuredOutput(
|
|
305
|
+
model,
|
|
306
|
+
decisionSchema(entries),
|
|
307
|
+
method
|
|
308
|
+
);
|
|
309
|
+
} catch {
|
|
310
|
+
throw new DecisionError(
|
|
311
|
+
'unsupported_mode',
|
|
312
|
+
'model cannot enforce the requested strict mode',
|
|
313
|
+
{
|
|
314
|
+
provider,
|
|
315
|
+
}
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
await waitFor(Promise.resolve());
|
|
319
|
+
let output: Awaited<ReturnType<typeof structured.invoke>>;
|
|
320
|
+
try {
|
|
321
|
+
output = await waitFor(
|
|
322
|
+
structured.invoke(
|
|
323
|
+
[
|
|
324
|
+
new SystemMessage(
|
|
325
|
+
'Classify the state using the questions. Return only the required decisions. Do not estimate probabilities, confidence, or token usage.'
|
|
326
|
+
),
|
|
327
|
+
new HumanMessage(input),
|
|
328
|
+
],
|
|
329
|
+
{ signal }
|
|
330
|
+
)
|
|
331
|
+
);
|
|
332
|
+
} catch (error) {
|
|
333
|
+
if (error instanceof DecisionError) {
|
|
334
|
+
throw error;
|
|
335
|
+
}
|
|
336
|
+
throw providerFailure(error, provider);
|
|
337
|
+
}
|
|
338
|
+
const answers = readDecisions(output.parsed, entries, provider);
|
|
339
|
+
const result: DecisionResult = {
|
|
340
|
+
model: modelId,
|
|
341
|
+
answers,
|
|
342
|
+
usage: readChatUsage(output.raw),
|
|
343
|
+
};
|
|
344
|
+
await waitFor(Promise.resolve());
|
|
345
|
+
try {
|
|
346
|
+
onAnswered?.(
|
|
347
|
+
request.label ?? 'decide',
|
|
348
|
+
performance.now() - started
|
|
349
|
+
);
|
|
350
|
+
} catch {
|
|
351
|
+
// A callback error must not make a valid decision fail.
|
|
352
|
+
}
|
|
353
|
+
return result;
|
|
354
|
+
}
|
|
355
|
+
);
|
|
356
|
+
},
|
|
357
|
+
};
|
|
358
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
|
2
|
+
|
|
3
|
+
export type DecisionOutputMethod = 'jsonSchema' | 'functionCalling';
|
|
4
|
+
|
|
5
|
+
/** Binds only verified enforcement paths; a model profile alone is not a strict-output guarantee. */
|
|
6
|
+
export function withDecisionStructuredOutput(
|
|
7
|
+
model: BaseChatModel,
|
|
8
|
+
schema: Record<string, unknown>,
|
|
9
|
+
method: DecisionOutputMethod
|
|
10
|
+
): ReturnType<BaseChatModel['withStructuredOutput']> {
|
|
11
|
+
const options = {
|
|
12
|
+
name: 'DecideQuestions',
|
|
13
|
+
method,
|
|
14
|
+
includeRaw: true as const,
|
|
15
|
+
};
|
|
16
|
+
const adapter = model._llmType();
|
|
17
|
+
if (
|
|
18
|
+
adapter === 'openai' ||
|
|
19
|
+
adapter === 'azure_openai' ||
|
|
20
|
+
(adapter === 'anthropic' && method === 'functionCalling')
|
|
21
|
+
) {
|
|
22
|
+
return model.withStructuredOutput(schema, { ...options, strict: true });
|
|
23
|
+
}
|
|
24
|
+
throw new Error('unverified strict decision model adapter');
|
|
25
|
+
}
|