@node-llm/core 1.17.1 → 1.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. package/README.md +14 -11
  2. package/dist/agent/Agent.d.ts +41 -14
  3. package/dist/agent/Agent.d.ts.map +1 -1
  4. package/dist/agent/Agent.js +12 -3
  5. package/dist/aliases.d.ts +477 -283
  6. package/dist/aliases.d.ts.map +1 -1
  7. package/dist/aliases.js +546 -352
  8. package/dist/chat/Chat.d.ts +23 -13
  9. package/dist/chat/Chat.d.ts.map +1 -1
  10. package/dist/chat/Chat.js +59 -16
  11. package/dist/chat/ChatOptions.d.ts +28 -11
  12. package/dist/chat/ChatOptions.d.ts.map +1 -1
  13. package/dist/chat/ChatResponse.d.ts +5 -0
  14. package/dist/chat/ChatResponse.d.ts.map +1 -1
  15. package/dist/chat/ChatResponse.js +12 -4
  16. package/dist/chat/ChatStream.d.ts.map +1 -1
  17. package/dist/chat/ChatStream.js +20 -10
  18. package/dist/chat/PromptCache.d.ts +7 -0
  19. package/dist/chat/PromptCache.d.ts.map +1 -0
  20. package/dist/chat/PromptCache.js +7 -0
  21. package/dist/chat/Temperature.d.ts +7 -0
  22. package/dist/chat/Temperature.d.ts.map +1 -0
  23. package/dist/chat/Temperature.js +17 -0
  24. package/dist/chat/Tool.d.ts +52 -8
  25. package/dist/chat/Tool.d.ts.map +1 -1
  26. package/dist/chat/Tool.js +53 -5
  27. package/dist/chat/ToolCallOutcome.d.ts +18 -6
  28. package/dist/chat/ToolCallOutcome.d.ts.map +1 -1
  29. package/dist/chat/ToolCallOutcome.js +14 -0
  30. package/dist/chat/ToolHandler.d.ts +2 -2
  31. package/dist/chat/ToolHandler.d.ts.map +1 -1
  32. package/dist/chat/ToolResultWindow.d.ts +16 -0
  33. package/dist/chat/ToolResultWindow.d.ts.map +1 -0
  34. package/dist/chat/ToolResultWindow.js +25 -0
  35. package/dist/config.d.ts +42 -2
  36. package/dist/config.d.ts.map +1 -1
  37. package/dist/config.js +80 -27
  38. package/dist/constants.d.ts +7 -0
  39. package/dist/constants.d.ts.map +1 -1
  40. package/dist/constants.js +3 -1
  41. package/dist/errors/billing.d.ts +7 -0
  42. package/dist/errors/billing.d.ts.map +1 -0
  43. package/dist/errors/billing.js +24 -0
  44. package/dist/errors/index.d.ts +28 -0
  45. package/dist/errors/index.d.ts.map +1 -1
  46. package/dist/errors/index.js +32 -0
  47. package/dist/executor/Executor.d.ts.map +1 -1
  48. package/dist/executor/Executor.js +5 -3
  49. package/dist/index.d.ts +12 -3
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/index.js +4 -1
  52. package/dist/judge/Judge.d.ts +107 -0
  53. package/dist/judge/Judge.d.ts.map +1 -0
  54. package/dist/judge/Judge.js +104 -0
  55. package/dist/judge/Judgment.d.ts +116 -0
  56. package/dist/judge/Judgment.d.ts.map +1 -0
  57. package/dist/judge/Judgment.js +159 -0
  58. package/dist/judge/Question.d.ts +91 -0
  59. package/dist/judge/Question.d.ts.map +1 -0
  60. package/dist/judge/Question.js +180 -0
  61. package/dist/llm.d.ts +48 -1
  62. package/dist/llm.d.ts.map +1 -1
  63. package/dist/llm.js +111 -5
  64. package/dist/models/ModelRegistry.d.ts +23 -19
  65. package/dist/models/ModelRegistry.d.ts.map +1 -1
  66. package/dist/models/ModelRegistry.js +28 -2
  67. package/dist/models/models.json +15154 -4786
  68. package/dist/models/types.d.ts +14 -1
  69. package/dist/models/types.d.ts.map +1 -1
  70. package/dist/providers/BaseProvider.d.ts +2 -1
  71. package/dist/providers/BaseProvider.d.ts.map +1 -1
  72. package/dist/providers/BaseProvider.js +3 -0
  73. package/dist/providers/Provider.d.ts +60 -1
  74. package/dist/providers/Provider.d.ts.map +1 -1
  75. package/dist/providers/anthropic/AnthropicProvider.d.ts +2 -0
  76. package/dist/providers/anthropic/AnthropicProvider.d.ts.map +1 -1
  77. package/dist/providers/anthropic/AnthropicProvider.js +2 -0
  78. package/dist/providers/anthropic/Capabilities.d.ts +1 -0
  79. package/dist/providers/anthropic/Capabilities.d.ts.map +1 -1
  80. package/dist/providers/anthropic/Capabilities.js +3 -0
  81. package/dist/providers/anthropic/Chat.d.ts.map +1 -1
  82. package/dist/providers/anthropic/Chat.js +7 -75
  83. package/dist/providers/anthropic/Errors.d.ts.map +1 -1
  84. package/dist/providers/anthropic/Errors.js +4 -3
  85. package/dist/providers/anthropic/Streaming.d.ts.map +1 -1
  86. package/dist/providers/anthropic/Streaming.js +4 -66
  87. package/dist/providers/anthropic/Utils.d.ts +33 -1
  88. package/dist/providers/anthropic/Utils.d.ts.map +1 -1
  89. package/dist/providers/anthropic/Utils.js +114 -0
  90. package/dist/providers/anthropic/types.d.ts +17 -3
  91. package/dist/providers/anthropic/types.d.ts.map +1 -1
  92. package/dist/providers/azure/AzureProvider.d.ts +54 -0
  93. package/dist/providers/azure/AzureProvider.d.ts.map +1 -0
  94. package/dist/providers/azure/AzureProvider.js +110 -0
  95. package/dist/providers/azure/Capabilities.d.ts +16 -0
  96. package/dist/providers/azure/Capabilities.d.ts.map +1 -0
  97. package/dist/providers/azure/Capabilities.js +36 -0
  98. package/dist/providers/azure/Embedding.d.ts +11 -0
  99. package/dist/providers/azure/Embedding.d.ts.map +1 -0
  100. package/dist/providers/azure/Embedding.js +23 -0
  101. package/dist/providers/azure/Models.d.ts +18 -0
  102. package/dist/providers/azure/Models.d.ts.map +1 -0
  103. package/dist/providers/azure/Models.js +29 -0
  104. package/dist/providers/azure/index.d.ts +5 -0
  105. package/dist/providers/azure/index.d.ts.map +1 -0
  106. package/dist/providers/azure/index.js +28 -0
  107. package/dist/providers/bedrock/BedrockProvider.d.ts.map +1 -1
  108. package/dist/providers/bedrock/BedrockProvider.js +1 -0
  109. package/dist/providers/bedrock/Capabilities.d.ts +1 -0
  110. package/dist/providers/bedrock/Capabilities.d.ts.map +1 -1
  111. package/dist/providers/bedrock/Capabilities.js +3 -0
  112. package/dist/providers/bedrock/Chat.d.ts.map +1 -1
  113. package/dist/providers/bedrock/Chat.js +6 -2
  114. package/dist/providers/bedrock/Errors.d.ts.map +1 -1
  115. package/dist/providers/bedrock/Errors.js +4 -0
  116. package/dist/providers/bedrock/Streaming.d.ts.map +1 -1
  117. package/dist/providers/bedrock/Streaming.js +4 -1
  118. package/dist/providers/deepseek/Capabilities.d.ts +1 -0
  119. package/dist/providers/deepseek/Capabilities.d.ts.map +1 -1
  120. package/dist/providers/deepseek/Capabilities.js +3 -0
  121. package/dist/providers/deepseek/Chat.d.ts.map +1 -1
  122. package/dist/providers/deepseek/Chat.js +3 -2
  123. package/dist/providers/deepseek/DeepSeekProvider.d.ts +1 -0
  124. package/dist/providers/deepseek/DeepSeekProvider.d.ts.map +1 -1
  125. package/dist/providers/deepseek/DeepSeekProvider.js +1 -0
  126. package/dist/providers/deepseek/Errors.d.ts.map +1 -1
  127. package/dist/providers/deepseek/Errors.js +5 -4
  128. package/dist/providers/gemini/Capabilities.d.ts +1 -0
  129. package/dist/providers/gemini/Capabilities.d.ts.map +1 -1
  130. package/dist/providers/gemini/Capabilities.js +3 -0
  131. package/dist/providers/gemini/Chat.d.ts.map +1 -1
  132. package/dist/providers/gemini/Chat.js +3 -2
  133. package/dist/providers/gemini/Errors.d.ts.map +1 -1
  134. package/dist/providers/gemini/Errors.js +4 -3
  135. package/dist/providers/gemini/GeminiProvider.d.ts +1 -0
  136. package/dist/providers/gemini/GeminiProvider.d.ts.map +1 -1
  137. package/dist/providers/gemini/GeminiProvider.js +1 -0
  138. package/dist/providers/mistral/Capabilities.d.ts +1 -0
  139. package/dist/providers/mistral/Capabilities.d.ts.map +1 -1
  140. package/dist/providers/mistral/Capabilities.js +3 -0
  141. package/dist/providers/mistral/Chat.d.ts.map +1 -1
  142. package/dist/providers/mistral/Chat.js +3 -2
  143. package/dist/providers/mistral/Errors.d.ts.map +1 -1
  144. package/dist/providers/mistral/Errors.js +4 -3
  145. package/dist/providers/mistral/MistralProvider.d.ts +1 -0
  146. package/dist/providers/mistral/MistralProvider.d.ts.map +1 -1
  147. package/dist/providers/mistral/MistralProvider.js +1 -0
  148. package/dist/providers/ollama/OllamaProvider.d.ts.map +1 -1
  149. package/dist/providers/ollama/OllamaProvider.js +1 -0
  150. package/dist/providers/openai/Capabilities.d.ts +6 -0
  151. package/dist/providers/openai/Capabilities.d.ts.map +1 -1
  152. package/dist/providers/openai/Capabilities.js +10 -0
  153. package/dist/providers/openai/Chat.d.ts +3 -0
  154. package/dist/providers/openai/Chat.d.ts.map +1 -1
  155. package/dist/providers/openai/Chat.js +26 -7
  156. package/dist/providers/openai/Embedding.d.ts +2 -0
  157. package/dist/providers/openai/Embedding.d.ts.map +1 -1
  158. package/dist/providers/openai/Embedding.js +8 -5
  159. package/dist/providers/openai/Errors.d.ts.map +1 -1
  160. package/dist/providers/openai/Errors.js +4 -3
  161. package/dist/providers/openai/Models.d.ts +10 -0
  162. package/dist/providers/openai/Models.d.ts.map +1 -1
  163. package/dist/providers/openai/Models.js +12 -8
  164. package/dist/providers/openai/OpenAIProvider.d.ts +11 -1
  165. package/dist/providers/openai/OpenAIProvider.d.ts.map +1 -1
  166. package/dist/providers/openai/OpenAIProvider.js +36 -0
  167. package/dist/providers/openai/Responses.d.ts +30 -0
  168. package/dist/providers/openai/Responses.d.ts.map +1 -0
  169. package/dist/providers/openai/Responses.js +176 -0
  170. package/dist/providers/openai/Streaming.d.ts +2 -0
  171. package/dist/providers/openai/Streaming.d.ts.map +1 -1
  172. package/dist/providers/openai/Streaming.js +19 -5
  173. package/dist/providers/openrouter/Capabilities.d.ts +1 -0
  174. package/dist/providers/openrouter/Capabilities.d.ts.map +1 -1
  175. package/dist/providers/openrouter/Capabilities.js +3 -0
  176. package/dist/providers/openrouter/OpenRouterProvider.d.ts +1 -0
  177. package/dist/providers/openrouter/OpenRouterProvider.d.ts.map +1 -1
  178. package/dist/providers/openrouter/OpenRouterProvider.js +1 -0
  179. package/dist/providers/registry.d.ts +3 -1
  180. package/dist/providers/registry.d.ts.map +1 -1
  181. package/dist/providers/registry.js +3 -1
  182. package/dist/providers/typesafe/SystemOne.d.ts +11 -0
  183. package/dist/providers/typesafe/SystemOne.d.ts.map +1 -0
  184. package/dist/providers/typesafe/SystemOne.js +171 -0
  185. package/dist/providers/typesafe/TypeSafeProvider.d.ts +27 -0
  186. package/dist/providers/typesafe/TypeSafeProvider.d.ts.map +1 -0
  187. package/dist/providers/typesafe/TypeSafeProvider.js +76 -0
  188. package/dist/providers/typesafe/index.d.ts +4 -0
  189. package/dist/providers/typesafe/index.d.ts.map +1 -0
  190. package/dist/providers/typesafe/index.js +19 -0
  191. package/dist/providers/xai/Capabilities.d.ts +1 -0
  192. package/dist/providers/xai/Capabilities.d.ts.map +1 -1
  193. package/dist/providers/xai/Capabilities.js +3 -0
  194. package/dist/providers/xai/Chat.d.ts.map +1 -1
  195. package/dist/providers/xai/Chat.js +3 -2
  196. package/dist/providers/xai/Errors.d.ts.map +1 -1
  197. package/dist/providers/xai/Errors.js +5 -2
  198. package/dist/providers/xai/XAIProvider.d.ts +1 -0
  199. package/dist/providers/xai/XAIProvider.d.ts.map +1 -1
  200. package/dist/providers/xai/XAIProvider.js +1 -0
  201. package/dist/schema/Schema.d.ts +3 -3
  202. package/dist/schema/Schema.d.ts.map +1 -1
  203. package/dist/schema/to-json-schema.d.ts +2 -2
  204. package/dist/schema/to-json-schema.d.ts.map +1 -1
  205. package/dist/schema/to-json-schema.js +3 -8
  206. package/dist/schema/zod.d.ts +32 -0
  207. package/dist/schema/zod.d.ts.map +1 -0
  208. package/dist/schema/zod.js +31 -0
  209. package/dist/types/Middleware.d.ts +9 -0
  210. package/dist/types/Middleware.d.ts.map +1 -1
  211. package/package.json +5 -3
@@ -0,0 +1,104 @@
1
+ import { NodeLLM } from "../llm.js";
2
+ /**
3
+ * A reusable set of typed questions about your application data.
4
+ *
5
+ * Subclass it and declare `questions`; call `judge` with the input. Answers are
6
+ * typed from the declaration, so a choice's answer is a union of its options.
7
+ *
8
+ * @example
9
+ * class TicketTriage extends Judge {
10
+ * static questions = {
11
+ * urgent: probability("Does this need attention today?"),
12
+ * department: choice("Which team should handle this?", {
13
+ * billing: "Payments and refunds",
14
+ * technical: "Bugs and integrations",
15
+ * other: null
16
+ * }),
17
+ * frustration: score("How frustrated is the customer?", ["Calm", "Frustrated", "Angry"])
18
+ * };
19
+ * }
20
+ *
21
+ * const judgment = await TicketTriage.judge("Please refund the duplicate charge today.");
22
+ * judgment.urgent.probability; // number
23
+ * judgment.department.choice; // "billing" | "technical" | "other"
24
+ *
25
+ * Settings can depend on runtime data. Declare the inputs a judge needs and
26
+ * pass them to `judge`; `questions`, `model` and `providerOptions` may be
27
+ * functions of those inputs, resolved once per judgment.
28
+ *
29
+ * @example
30
+ * class TeamRouter extends Judge {
31
+ * static inputs = ["teams"] as const;
32
+ * static questions = ({ teams }: { teams: Team[] }) => ({
33
+ * team: choice("Which team should handle this?",
34
+ * Object.fromEntries(teams.map((t) => [t.slug, t.description])))
35
+ * });
36
+ * }
37
+ * await TeamRouter.judge(ticket.body, { inputs: { teams } });
38
+ */
39
+ export class Judge {
40
+ /** The judgment model. Defaults to the instance default, then `jev-latest`. */
41
+ static model;
42
+ /** Routes to a specific provider; normally inferred from the model. */
43
+ static provider;
44
+ /** Names of the inputs `judge` must be given. */
45
+ static inputs;
46
+ /** The questions, or a function of the declared inputs returning them. */
47
+ static questions;
48
+ /** Provider-specific request fields, or a function of the inputs. */
49
+ static providerOptions;
50
+ /**
51
+ * Judges the input and returns typed answers. The input is text, an object,
52
+ * or an array of JSON-compatible values - or a function of the inputs that
53
+ * returns one.
54
+ */
55
+ static async judge(input, options = {}) {
56
+ const inputs = (options.inputs ?? {});
57
+ checkInputs(this, inputs);
58
+ const resolve = (value) => typeof value === "function" ? value(inputs) : value;
59
+ const questions = resolve(this.questions);
60
+ if (!questions) {
61
+ throw new Error(`${this.name} declares no questions`);
62
+ }
63
+ const declaredOptions = resolve(this.providerOptions);
64
+ const providerOptions = declaredOptions || options.providerOptions
65
+ ? { ...declaredOptions, ...options.providerOptions }
66
+ : undefined;
67
+ const llm = options.llm ?? NodeLLM;
68
+ return llm.judge(resolve(input), {
69
+ questions: questions,
70
+ model: options.model ?? resolve(this.model),
71
+ provider: options.provider ?? this.provider,
72
+ providerOptions,
73
+ requestTimeout: options.requestTimeout
74
+ });
75
+ }
76
+ }
77
+ function checkInputs(judge, inputs) {
78
+ const declared = judge.inputs ?? [];
79
+ const given = Object.keys(inputs);
80
+ const missing = declared.filter((name) => !(name in inputs));
81
+ const unknown = given.filter((name) => !declared.includes(name));
82
+ if (missing.length > 0)
83
+ throw new Error(`${judge.name} is missing inputs: ${missing.join(", ")}`);
84
+ if (unknown.length > 0)
85
+ throw new Error(`${judge.name} does not declare inputs: ${unknown.join(", ")}`);
86
+ }
87
+ /**
88
+ * Defines a judge without a class, mirroring `defineAgent`.
89
+ *
90
+ * @example
91
+ * const Urgency = defineJudge({
92
+ * questions: { urgent: probability("Does this need attention today?") }
93
+ * });
94
+ * (await Urgency.judge("The site is down")).urgent.probability;
95
+ */
96
+ export function defineJudge(definition) {
97
+ return class extends Judge {
98
+ static questions = definition.questions;
99
+ static model = definition.model;
100
+ static provider = definition.provider;
101
+ static inputs = definition.inputs;
102
+ static providerOptions = definition.providerOptions;
103
+ };
104
+ }
@@ -0,0 +1,116 @@
1
+ import { JudgmentResponse, Usage } from "../providers/Provider.js";
2
+ import { ChoiceQuestion, Description, ProbabilityQuestion, NamedQuestion, QuestionDefinition, QuestionMap, ScoreQuestion } from "./Question.js";
3
+ /** The probability that a yes/no statement holds, between 0 and 1. */
4
+ export declare class ProbabilityAnswer {
5
+ readonly probability: number;
6
+ readonly type: "probability";
7
+ constructor(probability: number);
8
+ toJSON(): {
9
+ type: "probability";
10
+ probability: number;
11
+ };
12
+ }
13
+ /**
14
+ * The selected option, with a probability for every option. `confidence`
15
+ * describes how concentrated the distribution is; it is not a guarantee that
16
+ * the choice is correct.
17
+ */
18
+ export declare class ChoiceAnswer<K extends string = string> {
19
+ readonly choice: K;
20
+ readonly probabilities: Readonly<Record<K, number>>;
21
+ readonly confidence: number;
22
+ readonly type: "choice";
23
+ constructor(choice: K, probabilities: Readonly<Record<K, number>>, confidence: number);
24
+ toJSON(): {
25
+ type: "choice";
26
+ choice: K;
27
+ probabilities: Readonly<Record<K, number>>;
28
+ confidence: number;
29
+ };
30
+ }
31
+ /**
32
+ * A probability-weighted position on an ordered scale. Level 0 is the first
33
+ * level, so with three levels the score runs from 0 to 2 and can fall between
34
+ * them. `probabilities` is keyed by level index.
35
+ */
36
+ export declare class ScoreAnswer {
37
+ readonly score: number;
38
+ readonly levels: readonly Description[];
39
+ readonly probabilities: Readonly<Record<number, number>>;
40
+ readonly confidence: number;
41
+ readonly type: "score";
42
+ constructor(score: number, levels: readonly Description[], probabilities: Readonly<Record<number, number>>, confidence: number);
43
+ toJSON(): {
44
+ type: "score";
45
+ score: number;
46
+ levels: readonly Description[];
47
+ probabilities: Readonly<Record<number, number>>;
48
+ confidence: number;
49
+ };
50
+ }
51
+ export type Answer = ProbabilityAnswer | ChoiceAnswer | ScoreAnswer;
52
+ /** The answer type a question produces. */
53
+ export type AnswerFor<D extends QuestionDefinition> = D extends ProbabilityQuestion ? ProbabilityAnswer : D extends ChoiceQuestion<infer K> ? ChoiceAnswer<K> : D extends ScoreQuestion ? ScoreAnswer : never;
54
+ /** Answers keyed by question name, typed from the questions asked. */
55
+ export type AnswersOf<Q extends QuestionMap> = {
56
+ readonly [N in keyof Q]: AnswerFor<Q[N]>;
57
+ };
58
+ /**
59
+ * The typed answers to a set of questions, with the model that answered them
60
+ * and the tokens it used.
61
+ *
62
+ * Answers are available by name - `judgment.urgent.probability` - and through
63
+ * `judgment.answers`. A question named like one of Judgment's own members
64
+ * (`model`, `usage`, `get`, ...) is only reachable through `answers` or `get`.
65
+ */
66
+ export declare class Judgment<Q extends QuestionMap = QuestionMap> {
67
+ readonly answers: AnswersOf<Q>;
68
+ /** The model that answered, as reported by the provider. */
69
+ readonly model: string;
70
+ /** Token usage. `cost` is present only when the model has known pricing. */
71
+ readonly usage: Usage;
72
+ /** The provider's raw response. */
73
+ readonly raw?: unknown | undefined;
74
+ constructor(answers: AnswersOf<Q>,
75
+ /** The model that answered, as reported by the provider. */
76
+ model: string,
77
+ /** Token usage. `cost` is present only when the model has known pricing. */
78
+ usage: Usage,
79
+ /** The provider's raw response. */
80
+ raw?: unknown | undefined);
81
+ /** Returns the named answer, or undefined when there is none. */
82
+ get<N extends keyof Q & string>(name: N): AnswersOf<Q>[N] | undefined;
83
+ /** Returns the named answer, throwing when there is none. */
84
+ fetch<N extends keyof Q & string>(name: N): AnswersOf<Q>[N];
85
+ [Symbol.iterator](): IterableIterator<[keyof Q & string, Answer]>;
86
+ toJSON(): {
87
+ model: string;
88
+ answers: {
89
+ [k: string]: {
90
+ type: "probability";
91
+ probability: number;
92
+ } | {
93
+ type: "score";
94
+ score: number;
95
+ levels: readonly Description[];
96
+ probabilities: Readonly<Record<number, number>>;
97
+ confidence: number;
98
+ } | {
99
+ type: "choice";
100
+ choice: string;
101
+ probabilities: Readonly<Record<string, number>>;
102
+ confidence: number;
103
+ };
104
+ };
105
+ usage: Usage;
106
+ };
107
+ }
108
+ /** A Judgment whose answers can also be read directly by question name. */
109
+ export type JudgmentResult<Q extends QuestionMap = QuestionMap> = Judgment<Q> & Omit<AnswersOf<Q>, keyof Judgment<Q>>;
110
+ /**
111
+ * Wraps a provider's answers in typed, frozen answer objects. Every question
112
+ * must come back answered with the type it was asked as; anything else means
113
+ * the provider and the request disagree, and is reported rather than guessed.
114
+ */
115
+ export declare function buildJudgment<Q extends QuestionMap>(response: JudgmentResponse, questions: NamedQuestion[]): JudgmentResult<Q>;
116
+ //# sourceMappingURL=Judgment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Judgment.d.ts","sourceRoot":"","sources":["../../src/judge/Judgment.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,159 @@
1
+ /** The probability that a yes/no statement holds, between 0 and 1. */
2
+ export class ProbabilityAnswer {
3
+ probability;
4
+ type = "probability";
5
+ constructor(probability) {
6
+ this.probability = probability;
7
+ Object.freeze(this);
8
+ }
9
+ toJSON() {
10
+ return { type: this.type, probability: this.probability };
11
+ }
12
+ }
13
+ /**
14
+ * The selected option, with a probability for every option. `confidence`
15
+ * describes how concentrated the distribution is; it is not a guarantee that
16
+ * the choice is correct.
17
+ */
18
+ export class ChoiceAnswer {
19
+ choice;
20
+ probabilities;
21
+ confidence;
22
+ type = "choice";
23
+ constructor(choice, probabilities, confidence) {
24
+ this.choice = choice;
25
+ this.probabilities = probabilities;
26
+ this.confidence = confidence;
27
+ Object.freeze(this.probabilities);
28
+ Object.freeze(this);
29
+ }
30
+ toJSON() {
31
+ return {
32
+ type: this.type,
33
+ choice: this.choice,
34
+ probabilities: this.probabilities,
35
+ confidence: this.confidence
36
+ };
37
+ }
38
+ }
39
+ /**
40
+ * A probability-weighted position on an ordered scale. Level 0 is the first
41
+ * level, so with three levels the score runs from 0 to 2 and can fall between
42
+ * them. `probabilities` is keyed by level index.
43
+ */
44
+ export class ScoreAnswer {
45
+ score;
46
+ levels;
47
+ probabilities;
48
+ confidence;
49
+ type = "score";
50
+ constructor(score, levels, probabilities, confidence) {
51
+ this.score = score;
52
+ this.levels = levels;
53
+ this.probabilities = probabilities;
54
+ this.confidence = confidence;
55
+ Object.freeze(this.levels);
56
+ Object.freeze(this.probabilities);
57
+ Object.freeze(this);
58
+ }
59
+ toJSON() {
60
+ return {
61
+ type: this.type,
62
+ score: this.score,
63
+ levels: this.levels,
64
+ probabilities: this.probabilities,
65
+ confidence: this.confidence
66
+ };
67
+ }
68
+ }
69
+ /**
70
+ * The typed answers to a set of questions, with the model that answered them
71
+ * and the tokens it used.
72
+ *
73
+ * Answers are available by name - `judgment.urgent.probability` - and through
74
+ * `judgment.answers`. A question named like one of Judgment's own members
75
+ * (`model`, `usage`, `get`, ...) is only reachable through `answers` or `get`.
76
+ */
77
+ export class Judgment {
78
+ answers;
79
+ model;
80
+ usage;
81
+ raw;
82
+ constructor(answers,
83
+ /** The model that answered, as reported by the provider. */
84
+ model,
85
+ /** Token usage. `cost` is present only when the model has known pricing. */
86
+ usage,
87
+ /** The provider's raw response. */
88
+ raw) {
89
+ this.answers = answers;
90
+ this.model = model;
91
+ this.usage = usage;
92
+ this.raw = raw;
93
+ Object.freeze(this.answers);
94
+ for (const name of Object.keys(answers)) {
95
+ if (name in this)
96
+ continue;
97
+ Object.defineProperty(this, name, {
98
+ value: answers[name],
99
+ enumerable: false
100
+ });
101
+ }
102
+ Object.freeze(this);
103
+ }
104
+ /** Returns the named answer, or undefined when there is none. */
105
+ get(name) {
106
+ return Object.prototype.hasOwnProperty.call(this.answers, name)
107
+ ? this.answers[name]
108
+ : undefined;
109
+ }
110
+ /** Returns the named answer, throwing when there is none. */
111
+ fetch(name) {
112
+ const answer = this.get(name);
113
+ if (answer === undefined)
114
+ throw new Error(`No answer named '${name}' in this judgment`);
115
+ return answer;
116
+ }
117
+ *[Symbol.iterator]() {
118
+ for (const [name, answer] of Object.entries(this.answers)) {
119
+ yield [name, answer];
120
+ }
121
+ }
122
+ toJSON() {
123
+ return {
124
+ model: this.model,
125
+ answers: Object.fromEntries(Object.entries(this.answers).map(([name, answer]) => [name, answer.toJSON()])),
126
+ usage: this.usage
127
+ };
128
+ }
129
+ }
130
+ /**
131
+ * Wraps a provider's answers in typed, frozen answer objects. Every question
132
+ * must come back answered with the type it was asked as; anything else means
133
+ * the provider and the request disagree, and is reported rather than guessed.
134
+ */
135
+ export function buildJudgment(response, questions) {
136
+ const answers = {};
137
+ for (const question of questions) {
138
+ const data = response.answers[question.name];
139
+ if (!data)
140
+ throw new Error(`The provider returned no answer for '${question.name}'`);
141
+ if (data.type !== question.type) {
142
+ throw new Error(`The provider answered '${question.name}' as a ${data.type}, but it was asked as a ${question.type}`);
143
+ }
144
+ switch (data.type) {
145
+ case "probability":
146
+ answers[question.name] = new ProbabilityAnswer(data.probability);
147
+ break;
148
+ case "choice":
149
+ answers[question.name] = new ChoiceAnswer(data.choice, { ...data.probabilities }, data.confidence);
150
+ break;
151
+ case "score":
152
+ // Copy the levels: the answer is frozen, and freezing the caller's own
153
+ // question definition would be a surprising side effect.
154
+ answers[question.name] = new ScoreAnswer(data.score, structuredClone(question.levels), { ...data.probabilities }, data.confidence);
155
+ break;
156
+ }
157
+ }
158
+ return new Judgment(answers, response.model, response.usage, response.raw);
159
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Typed questions for judgments.
3
+ *
4
+ * A judgment asks a set of named questions about one input and returns a typed
5
+ * answer for each: the probability that a statement holds, a choice among named
6
+ * options, or a score on an ordered scale. Questions are plain data, so they
7
+ * can be declared on a Judge class, passed to NodeLLM.judge, or built at
8
+ * runtime from application data.
9
+ */
10
+ /** A JSON-compatible value, used for descriptions and structured input. */
11
+ export type JsonValue = string | number | boolean | null | JsonValue[] | {
12
+ [key: string]: JsonValue;
13
+ };
14
+ /**
15
+ * Describes a question, an option, or a level. Text is usual; an object or
16
+ * array carries structured instructions, and null means "no description".
17
+ */
18
+ export type Description = string | null | JsonValue[] | {
19
+ [key: string]: JsonValue;
20
+ };
21
+ /** The yes/no boundary of a probability question. Either side is optional. */
22
+ export interface ProbabilityCriteria {
23
+ yes?: Description;
24
+ no?: Description;
25
+ }
26
+ export interface ProbabilityQuestion {
27
+ type: "probability";
28
+ instructions?: Description;
29
+ criteria?: ProbabilityCriteria;
30
+ }
31
+ export interface ChoiceQuestion<K extends string = string> {
32
+ type: "choice";
33
+ instructions?: Description;
34
+ options: Record<K, Description>;
35
+ }
36
+ export interface ScoreQuestion {
37
+ type: "score";
38
+ instructions?: Description;
39
+ levels: Description[];
40
+ }
41
+ export type QuestionDefinition = ProbabilityQuestion | ChoiceQuestion | ScoreQuestion;
42
+ /** Questions keyed by the name your code reads the answer from. */
43
+ export type QuestionMap = Record<string, QuestionDefinition>;
44
+ /** A question after validation, carrying its name for the provider. */
45
+ export type NamedQuestion = QuestionDefinition & {
46
+ name: string;
47
+ };
48
+ /**
49
+ * Declares a yes/no question whose answer is the probability of yes. A value
50
+ * near 0.5 means yes and no are about equally likely, not "medium"; use a
51
+ * score to measure degree.
52
+ *
53
+ * @example
54
+ * probability("Does this need attention today?", {
55
+ * yes: "An explicit deadline today or an ongoing outage",
56
+ * no: "A general question with no time pressure"
57
+ * })
58
+ */
59
+ export declare function probability(instructions?: Description, criteria?: ProbabilityCriteria): ProbabilityQuestion;
60
+ /**
61
+ * Declares a question that selects one named option and returns a probability
62
+ * for every option. Include a catch-all such as `other` when none of the
63
+ * specific options may fit. Use null for an option that needs no description.
64
+ *
65
+ * @example
66
+ * choice("Which team should handle this?", {
67
+ * billing: "Payments and refunds",
68
+ * technical: "Bugs and integrations",
69
+ * other: null
70
+ * })
71
+ */
72
+ export declare function choice<const O extends Record<string, Description>>(instructions: Description | undefined, options: O): ChoiceQuestion<Extract<keyof O, string>>;
73
+ /**
74
+ * Declares a question scored against ordered levels. The first level is 0, the
75
+ * second 1, and so on; the answer is probability-weighted and can fall between
76
+ * levels. Describe each level concretely.
77
+ *
78
+ * @example
79
+ * score("How frustrated is the customer?", ["Calm", "Frustrated", "Angry"])
80
+ */
81
+ export declare function score(instructions: Description | undefined, levels: Description[]): ScoreQuestion;
82
+ /**
83
+ * Validates every question and returns them in declaration order with their
84
+ * names attached. Rejects the problems a provider would otherwise report as an
85
+ * opaque 400: unknown types, empty choices or scales, and probability criteria
86
+ * that describe anything other than yes and no.
87
+ */
88
+ export declare function normalizeQuestions(questions: QuestionMap): NamedQuestion[];
89
+ /** True when the value can be sent as judgment input. */
90
+ export declare function isJudgmentInput(value: unknown): boolean;
91
+ //# sourceMappingURL=Question.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Question.d.ts","sourceRoot":"","sources":["../../src/judge/Question.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,2EAA2E;AAC3E,MAAM,MAAM,SAAS,GACjB,MAAM,GACN,MAAM,GACN,OAAO,GACP,IAAI,GACJ,SAAS,EAAE,GACX;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC;AAEjC;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC;AAErF,8EAA8E;AAC9E,MAAM,WAAW,mBAAmB;IAClC,GAAG,CAAC,EAAE,WAAW,CAAC;IAClB,EAAE,CAAC,EAAE,WAAW,CAAC;CAClB;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,aAAa,CAAC;IACpB,YAAY,CAAC,EAAE,WAAW,CAAC;IAC3B,QAAQ,CAAC,EAAE,mBAAmB,CAAC;CAChC;AAED,MAAM,WAAW,cAAc,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM;IACvD,IAAI,EAAE,QAAQ,CAAC;IACf,YAAY,CAAC,EAAE,WAAW,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,CAAC,EAAE,WAAW,CAAC;IAC3B,MAAM,EAAE,WAAW,EAAE,CAAC;CACvB;AAED,MAAM,MAAM,kBAAkB,GAAG,mBAAmB,GAAG,cAAc,GAAG,aAAa,CAAC;AAEtF,mEAAmE;AACnE,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;AAE7D,uEAAuE;AACvE,MAAM,MAAM,aAAa,GAAG,kBAAkB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAElE;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CACzB,YAAY,CAAC,EAAE,WAAW,EAC1B,QAAQ,CAAC,EAAE,mBAAmB,GAC7B,mBAAmB,CAErB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,MAAM,CAAC,KAAK,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EAChE,YAAY,EAAE,WAAW,GAAG,SAAS,EACrC,OAAO,EAAE,CAAC,GACT,cAAc,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,CAE1C;AAED;;;;;;;GAOG;AACH,wBAAgB,KAAK,CAAC,YAAY,EAAE,WAAW,GAAG,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,aAAa,CAEjG;AAyCD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,WAAW,GAAG,aAAa,EAAE,CAmC1E;AAuDD,yDAAyD;AACzD,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAKvD"}
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Typed questions for judgments.
3
+ *
4
+ * A judgment asks a set of named questions about one input and returns a typed
5
+ * answer for each: the probability that a statement holds, a choice among named
6
+ * options, or a score on an ordered scale. Questions are plain data, so they
7
+ * can be declared on a Judge class, passed to NodeLLM.judge, or built at
8
+ * runtime from application data.
9
+ */
10
+ /**
11
+ * Declares a yes/no question whose answer is the probability of yes. A value
12
+ * near 0.5 means yes and no are about equally likely, not "medium"; use a
13
+ * score to measure degree.
14
+ *
15
+ * @example
16
+ * probability("Does this need attention today?", {
17
+ * yes: "An explicit deadline today or an ongoing outage",
18
+ * no: "A general question with no time pressure"
19
+ * })
20
+ */
21
+ export function probability(instructions, criteria) {
22
+ return { type: "probability", instructions, ...(criteria ? { criteria } : {}) };
23
+ }
24
+ /**
25
+ * Declares a question that selects one named option and returns a probability
26
+ * for every option. Include a catch-all such as `other` when none of the
27
+ * specific options may fit. Use null for an option that needs no description.
28
+ *
29
+ * @example
30
+ * choice("Which team should handle this?", {
31
+ * billing: "Payments and refunds",
32
+ * technical: "Bugs and integrations",
33
+ * other: null
34
+ * })
35
+ */
36
+ export function choice(instructions, options) {
37
+ return { type: "choice", instructions, options };
38
+ }
39
+ /**
40
+ * Declares a question scored against ordered levels. The first level is 0, the
41
+ * second 1, and so on; the answer is probability-weighted and can fall between
42
+ * levels. Describe each level concretely.
43
+ *
44
+ * @example
45
+ * score("How frustrated is the customer?", ["Calm", "Frustrated", "Angry"])
46
+ */
47
+ export function score(instructions, levels) {
48
+ return { type: "score", instructions, levels };
49
+ }
50
+ function isDescription(value) {
51
+ if (value === null || typeof value === "string")
52
+ return true;
53
+ if (Array.isArray(value))
54
+ return value.every(isJson);
55
+ if (typeof value === "object")
56
+ return isJson(value);
57
+ return false;
58
+ }
59
+ function isJson(value) {
60
+ if (value === null)
61
+ return true;
62
+ switch (typeof value) {
63
+ case "string":
64
+ case "boolean":
65
+ return true;
66
+ case "number":
67
+ return Number.isFinite(value);
68
+ case "object":
69
+ if (Array.isArray(value))
70
+ return value.every(isJson);
71
+ if (Object.getPrototypeOf(value) !== Object.prototype &&
72
+ Object.getPrototypeOf(value) !== null) {
73
+ return false;
74
+ }
75
+ return Object.values(value).every(isJson);
76
+ default:
77
+ return false;
78
+ }
79
+ }
80
+ function invalid(name, message) {
81
+ throw new Error(`Invalid judgment question '${name}': ${message}`);
82
+ }
83
+ function validateDescriptions(name, values) {
84
+ if (!values.every(isDescription)) {
85
+ invalid(name, "descriptions must be text, an object, an array, or null");
86
+ }
87
+ }
88
+ /**
89
+ * Validates every question and returns them in declaration order with their
90
+ * names attached. Rejects the problems a provider would otherwise report as an
91
+ * opaque 400: unknown types, empty choices or scales, and probability criteria
92
+ * that describe anything other than yes and no.
93
+ */
94
+ export function normalizeQuestions(questions) {
95
+ if (!questions || typeof questions !== "object" || Array.isArray(questions)) {
96
+ throw new Error("Judgment questions must be an object keyed by question name");
97
+ }
98
+ const entries = Object.entries(questions);
99
+ if (entries.length === 0) {
100
+ throw new Error("A judgment needs at least one question");
101
+ }
102
+ return entries.map(([name, question]) => {
103
+ if (name.length === 0)
104
+ invalid(name, "a question name cannot be empty");
105
+ if (!question || typeof question !== "object")
106
+ invalid(name, "the definition must be an object");
107
+ if (question.instructions !== undefined && !isDescription(question.instructions)) {
108
+ invalid(name, "instructions must be text, an object, an array, or null");
109
+ }
110
+ switch (question.type) {
111
+ case "probability":
112
+ validateProbability(name, question);
113
+ break;
114
+ case "choice":
115
+ validateChoice(name, question);
116
+ break;
117
+ case "score":
118
+ validateScore(name, question);
119
+ break;
120
+ default:
121
+ invalid(name, `unknown type '${String(question.type)}'`);
122
+ }
123
+ return { ...question, name };
124
+ });
125
+ }
126
+ function validateProbability(name, question) {
127
+ const extra = Object.keys(question).filter((k) => !["type", "instructions", "criteria"].includes(k));
128
+ if (extra.length > 0)
129
+ invalid(name, `unknown fields ${extra.join(", ")}`);
130
+ if (question.criteria === undefined)
131
+ return;
132
+ const criteria = question.criteria;
133
+ if (!criteria || typeof criteria !== "object" || Array.isArray(criteria)) {
134
+ invalid(name, "probability criteria must describe yes and no");
135
+ }
136
+ const keys = Object.keys(criteria);
137
+ const unknown = keys.filter((k) => !["yes", "no", "true", "false"].includes(k));
138
+ if (unknown.length > 0)
139
+ invalid(name, "probability criteria must describe yes and no");
140
+ // yes/true and no/false are the same outcome; describing one twice is ambiguous.
141
+ const positives = keys.filter((k) => k === "yes" || k === "true").length;
142
+ const negatives = keys.filter((k) => k === "no" || k === "false").length;
143
+ if (positives > 1 || negatives > 1)
144
+ invalid(name, "probability criteria contain duplicate outcomes");
145
+ validateDescriptions(name, Object.values(criteria));
146
+ }
147
+ function validateChoice(name, question) {
148
+ const extra = Object.keys(question).filter((k) => !["type", "instructions", "options"].includes(k));
149
+ if (extra.length > 0)
150
+ invalid(name, `unknown fields ${extra.join(", ")}`);
151
+ const options = question.options;
152
+ if (!options || typeof options !== "object" || Array.isArray(options)) {
153
+ invalid(name, "a choice needs a non-empty object of options");
154
+ }
155
+ const keys = Object.keys(options);
156
+ if (keys.length === 0)
157
+ invalid(name, "a choice needs a non-empty object of options");
158
+ if (keys.some((k) => k.length === 0))
159
+ invalid(name, "choice options must have non-empty names");
160
+ validateDescriptions(name, Object.values(options));
161
+ }
162
+ function validateScore(name, question) {
163
+ const extra = Object.keys(question).filter((k) => !["type", "instructions", "levels"].includes(k));
164
+ if (extra.length > 0)
165
+ invalid(name, `unknown fields ${extra.join(", ")}`);
166
+ if (!Array.isArray(question.levels) || question.levels.length === 0) {
167
+ invalid(name, "a score needs a non-empty array of levels");
168
+ }
169
+ validateDescriptions(name, question.levels);
170
+ }
171
+ /** True when the value can be sent as judgment input. */
172
+ export function isJudgmentInput(value) {
173
+ if (typeof value === "string")
174
+ return true;
175
+ if (Array.isArray(value))
176
+ return isJson(value);
177
+ if (value !== null && typeof value === "object")
178
+ return isJson(value);
179
+ return false;
180
+ }