@effect/ai 0.26.0 → 0.27.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.
- package/Chat/package.json +6 -0
- package/EmbeddingModel/package.json +6 -0
- package/IdGenerator/package.json +6 -0
- package/LanguageModel/package.json +6 -0
- package/Model/package.json +6 -0
- package/Prompt/package.json +6 -0
- package/Response/package.json +6 -0
- package/Telemetry/package.json +6 -0
- package/Tool/package.json +6 -0
- package/Toolkit/package.json +6 -0
- package/dist/cjs/AiError.js +575 -11
- package/dist/cjs/AiError.js.map +1 -1
- package/dist/cjs/Chat.js +302 -0
- package/dist/cjs/Chat.js.map +1 -0
- package/dist/cjs/EmbeddingModel.js +184 -0
- package/dist/cjs/EmbeddingModel.js.map +1 -0
- package/dist/cjs/IdGenerator.js +255 -0
- package/dist/cjs/IdGenerator.js.map +1 -0
- package/dist/cjs/LanguageModel.js +584 -0
- package/dist/cjs/LanguageModel.js.map +1 -0
- package/dist/cjs/McpServer.js +12 -4
- package/dist/cjs/McpServer.js.map +1 -1
- package/dist/cjs/Model.js +118 -0
- package/dist/cjs/Model.js.map +1 -0
- package/dist/cjs/Prompt.js +649 -0
- package/dist/cjs/Prompt.js.map +1 -0
- package/dist/cjs/Response.js +635 -0
- package/dist/cjs/Response.js.map +1 -0
- package/dist/cjs/Telemetry.js +176 -0
- package/dist/cjs/Telemetry.js.map +1 -0
- package/dist/cjs/Tokenizer.js +87 -8
- package/dist/cjs/Tokenizer.js.map +1 -1
- package/dist/cjs/Tool.js +556 -0
- package/dist/cjs/Tool.js.map +1 -0
- package/dist/cjs/Toolkit.js +279 -0
- package/dist/cjs/Toolkit.js.map +1 -0
- package/dist/cjs/index.js +21 -19
- package/dist/dts/AiError.d.ts +577 -9
- package/dist/dts/AiError.d.ts.map +1 -1
- package/dist/dts/Chat.d.ts +356 -0
- package/dist/dts/Chat.d.ts.map +1 -0
- package/dist/dts/EmbeddingModel.d.ts +153 -0
- package/dist/dts/EmbeddingModel.d.ts.map +1 -0
- package/dist/dts/IdGenerator.d.ts +272 -0
- package/dist/dts/IdGenerator.d.ts.map +1 -0
- package/dist/dts/LanguageModel.d.ts +458 -0
- package/dist/dts/LanguageModel.d.ts.map +1 -0
- package/dist/dts/McpSchema.d.ts +25 -25
- package/dist/dts/McpServer.d.ts +6 -4
- package/dist/dts/McpServer.d.ts.map +1 -1
- package/dist/dts/Model.d.ts +124 -0
- package/dist/dts/Model.d.ts.map +1 -0
- package/dist/dts/Prompt.d.ts +1119 -0
- package/dist/dts/Prompt.d.ts.map +1 -0
- package/dist/dts/Response.d.ts +1519 -0
- package/dist/dts/Response.d.ts.map +1 -0
- package/dist/dts/Telemetry.d.ts +520 -0
- package/dist/dts/Telemetry.d.ts.map +1 -0
- package/dist/dts/Tokenizer.d.ts +131 -13
- package/dist/dts/Tokenizer.d.ts.map +1 -1
- package/dist/dts/Tool.d.ts +876 -0
- package/dist/dts/Tool.d.ts.map +1 -0
- package/dist/dts/Toolkit.d.ts +310 -0
- package/dist/dts/Toolkit.d.ts.map +1 -0
- package/dist/dts/index.d.ts +498 -13
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/esm/AiError.js +570 -10
- package/dist/esm/AiError.js.map +1 -1
- package/dist/esm/Chat.js +291 -0
- package/dist/esm/Chat.js.map +1 -0
- package/dist/esm/EmbeddingModel.js +173 -0
- package/dist/esm/EmbeddingModel.js.map +1 -0
- package/dist/esm/IdGenerator.js +245 -0
- package/dist/esm/IdGenerator.js.map +1 -0
- package/dist/esm/LanguageModel.js +572 -0
- package/dist/esm/LanguageModel.js.map +1 -0
- package/dist/esm/McpServer.js +12 -4
- package/dist/esm/McpServer.js.map +1 -1
- package/dist/esm/Model.js +108 -0
- package/dist/esm/Model.js.map +1 -0
- package/dist/esm/Prompt.js +633 -0
- package/dist/esm/Prompt.js.map +1 -0
- package/dist/esm/Response.js +619 -0
- package/dist/esm/Response.js.map +1 -0
- package/dist/esm/Telemetry.js +166 -0
- package/dist/esm/Telemetry.js.map +1 -0
- package/dist/esm/Tokenizer.js +87 -8
- package/dist/esm/Tokenizer.js.map +1 -1
- package/dist/esm/Tool.js +534 -0
- package/dist/esm/Tool.js.map +1 -0
- package/dist/esm/Toolkit.js +269 -0
- package/dist/esm/Toolkit.js.map +1 -0
- package/dist/esm/index.js +498 -13
- package/dist/esm/index.js.map +1 -1
- package/package.json +76 -68
- package/src/AiError.ts +739 -9
- package/src/Chat.ts +546 -0
- package/src/EmbeddingModel.ts +311 -0
- package/src/IdGenerator.ts +320 -0
- package/src/LanguageModel.ts +1074 -0
- package/src/McpServer.ts +337 -194
- package/src/Model.ts +155 -0
- package/src/Prompt.ts +1616 -0
- package/src/Response.ts +2131 -0
- package/src/Telemetry.ts +655 -0
- package/src/Tokenizer.ts +145 -24
- package/src/Tool.ts +1267 -0
- package/src/Toolkit.ts +516 -0
- package/src/index.ts +499 -13
- package/AiChat/package.json +0 -6
- package/AiEmbeddingModel/package.json +0 -6
- package/AiInput/package.json +0 -6
- package/AiLanguageModel/package.json +0 -6
- package/AiModel/package.json +0 -6
- package/AiResponse/package.json +0 -6
- package/AiTelemetry/package.json +0 -6
- package/AiTool/package.json +0 -6
- package/AiToolkit/package.json +0 -6
- package/dist/cjs/AiChat.js +0 -122
- package/dist/cjs/AiChat.js.map +0 -1
- package/dist/cjs/AiEmbeddingModel.js +0 -109
- package/dist/cjs/AiEmbeddingModel.js.map +0 -1
- package/dist/cjs/AiInput.js +0 -458
- package/dist/cjs/AiInput.js.map +0 -1
- package/dist/cjs/AiLanguageModel.js +0 -351
- package/dist/cjs/AiLanguageModel.js.map +0 -1
- package/dist/cjs/AiModel.js +0 -37
- package/dist/cjs/AiModel.js.map +0 -1
- package/dist/cjs/AiResponse.js +0 -681
- package/dist/cjs/AiResponse.js.map +0 -1
- package/dist/cjs/AiTelemetry.js +0 -58
- package/dist/cjs/AiTelemetry.js.map +0 -1
- package/dist/cjs/AiTool.js +0 -150
- package/dist/cjs/AiTool.js.map +0 -1
- package/dist/cjs/AiToolkit.js +0 -157
- package/dist/cjs/AiToolkit.js.map +0 -1
- package/dist/cjs/internal/common.js +0 -21
- package/dist/cjs/internal/common.js.map +0 -1
- package/dist/dts/AiChat.d.ts +0 -101
- package/dist/dts/AiChat.d.ts.map +0 -1
- package/dist/dts/AiEmbeddingModel.d.ts +0 -65
- package/dist/dts/AiEmbeddingModel.d.ts.map +0 -1
- package/dist/dts/AiInput.d.ts +0 -590
- package/dist/dts/AiInput.d.ts.map +0 -1
- package/dist/dts/AiLanguageModel.d.ts +0 -302
- package/dist/dts/AiLanguageModel.d.ts.map +0 -1
- package/dist/dts/AiModel.d.ts +0 -25
- package/dist/dts/AiModel.d.ts.map +0 -1
- package/dist/dts/AiResponse.d.ts +0 -863
- package/dist/dts/AiResponse.d.ts.map +0 -1
- package/dist/dts/AiTelemetry.d.ts +0 -242
- package/dist/dts/AiTelemetry.d.ts.map +0 -1
- package/dist/dts/AiTool.d.ts +0 -334
- package/dist/dts/AiTool.d.ts.map +0 -1
- package/dist/dts/AiToolkit.d.ts +0 -96
- package/dist/dts/AiToolkit.d.ts.map +0 -1
- package/dist/dts/internal/common.d.ts +0 -2
- package/dist/dts/internal/common.d.ts.map +0 -1
- package/dist/esm/AiChat.js +0 -111
- package/dist/esm/AiChat.js.map +0 -1
- package/dist/esm/AiEmbeddingModel.js +0 -98
- package/dist/esm/AiEmbeddingModel.js.map +0 -1
- package/dist/esm/AiInput.js +0 -433
- package/dist/esm/AiInput.js.map +0 -1
- package/dist/esm/AiLanguageModel.js +0 -340
- package/dist/esm/AiLanguageModel.js.map +0 -1
- package/dist/esm/AiModel.js +0 -29
- package/dist/esm/AiModel.js.map +0 -1
- package/dist/esm/AiResponse.js +0 -657
- package/dist/esm/AiResponse.js.map +0 -1
- package/dist/esm/AiTelemetry.js +0 -48
- package/dist/esm/AiTelemetry.js.map +0 -1
- package/dist/esm/AiTool.js +0 -134
- package/dist/esm/AiTool.js.map +0 -1
- package/dist/esm/AiToolkit.js +0 -147
- package/dist/esm/AiToolkit.js.map +0 -1
- package/dist/esm/internal/common.js +0 -14
- package/dist/esm/internal/common.js.map +0 -1
- package/src/AiChat.ts +0 -251
- package/src/AiEmbeddingModel.ts +0 -169
- package/src/AiInput.ts +0 -602
- package/src/AiLanguageModel.ts +0 -685
- package/src/AiModel.ts +0 -53
- package/src/AiResponse.ts +0 -986
- package/src/AiTelemetry.ts +0 -333
- package/src/AiTool.ts +0 -579
- package/src/AiToolkit.ts +0 -265
- package/src/internal/common.ts +0 -12
|
@@ -0,0 +1,1074 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `LanguageModel` module provides AI text generation capabilities with tool
|
|
3
|
+
* calling support.
|
|
4
|
+
*
|
|
5
|
+
* This module offers a comprehensive interface for interacting with large
|
|
6
|
+
* language models, supporting both streaming and non-streaming text generation,
|
|
7
|
+
* structured output generation, and tool calling functionality. It provides a
|
|
8
|
+
* unified API that can be implemented by different AI providers while
|
|
9
|
+
* maintaining type safety and effect management.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { LanguageModel } from "@effect/ai"
|
|
14
|
+
* import { Effect } from "effect"
|
|
15
|
+
*
|
|
16
|
+
* // Basic text generation
|
|
17
|
+
* const program = Effect.gen(function* () {
|
|
18
|
+
* const response = yield* LanguageModel.generateText({
|
|
19
|
+
* prompt: "Explain quantum computing"
|
|
20
|
+
* })
|
|
21
|
+
*
|
|
22
|
+
* console.log(response.text)
|
|
23
|
+
*
|
|
24
|
+
* return response
|
|
25
|
+
* })
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* import { LanguageModel } from "@effect/ai"
|
|
31
|
+
* import { Effect, Schema } from "effect"
|
|
32
|
+
*
|
|
33
|
+
* // Structured output generation
|
|
34
|
+
* const ContactSchema = Schema.Struct({
|
|
35
|
+
* name: Schema.String,
|
|
36
|
+
* email: Schema.String
|
|
37
|
+
* })
|
|
38
|
+
*
|
|
39
|
+
* const extractContact = Effect.gen(function* () {
|
|
40
|
+
* const response = yield* LanguageModel.generateObject({
|
|
41
|
+
* prompt: "Extract contact: John Doe, john@example.com",
|
|
42
|
+
* schema: ContactSchema
|
|
43
|
+
* })
|
|
44
|
+
*
|
|
45
|
+
* return response.value
|
|
46
|
+
* })
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* @since 1.0.0
|
|
50
|
+
*/
|
|
51
|
+
import * as Chunk from "effect/Chunk"
|
|
52
|
+
import * as Context from "effect/Context"
|
|
53
|
+
import * as Effect from "effect/Effect"
|
|
54
|
+
import * as Option from "effect/Option"
|
|
55
|
+
import * as ParseResult from "effect/ParseResult"
|
|
56
|
+
import * as Predicate from "effect/Predicate"
|
|
57
|
+
import * as Schema from "effect/Schema"
|
|
58
|
+
import * as Stream from "effect/Stream"
|
|
59
|
+
import type { Span } from "effect/Tracer"
|
|
60
|
+
import type { Concurrency, Mutable, NoExcessProperties } from "effect/Types"
|
|
61
|
+
import * as AiError from "./AiError.js"
|
|
62
|
+
import { defaultIdGenerator, IdGenerator } from "./IdGenerator.js"
|
|
63
|
+
import * as Prompt from "./Prompt.js"
|
|
64
|
+
import * as Response from "./Response.js"
|
|
65
|
+
import type { SpanTransformer } from "./Telemetry.js"
|
|
66
|
+
import { CurrentSpanTransformer } from "./Telemetry.js"
|
|
67
|
+
import type * as Tool from "./Tool.js"
|
|
68
|
+
import * as Toolkit from "./Toolkit.js"
|
|
69
|
+
|
|
70
|
+
// =============================================================================
|
|
71
|
+
// Service Definition
|
|
72
|
+
// =============================================================================
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The `LanguageModel` service tag for dependency injection.
|
|
76
|
+
*
|
|
77
|
+
* This tag provides access to language model functionality throughout your
|
|
78
|
+
* application, enabling text generation, streaming, and structured output
|
|
79
|
+
* capabilities.
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* ```ts
|
|
83
|
+
* import { LanguageModel } from "@effect/ai"
|
|
84
|
+
* import { Effect } from "effect"
|
|
85
|
+
*
|
|
86
|
+
* const useLanguageModel = Effect.gen(function* () {
|
|
87
|
+
* const model = yield* LanguageModel
|
|
88
|
+
* const response = yield* model.generateText({
|
|
89
|
+
* prompt: "What is machine learning?"
|
|
90
|
+
* })
|
|
91
|
+
* return response.text
|
|
92
|
+
* })
|
|
93
|
+
* ```
|
|
94
|
+
*
|
|
95
|
+
* @since 1.0.0
|
|
96
|
+
* @category Context
|
|
97
|
+
*/
|
|
98
|
+
export class LanguageModel extends Context.Tag("@effect/ai/LanguageModel")<
|
|
99
|
+
LanguageModel,
|
|
100
|
+
Service
|
|
101
|
+
>() {}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The service interface for language model operations.
|
|
105
|
+
*
|
|
106
|
+
* Defines the contract that all language model implementations must fulfill,
|
|
107
|
+
* providing text generation, structured output, and streaming capabilities.
|
|
108
|
+
*
|
|
109
|
+
* @since 1.0.0
|
|
110
|
+
* @category Models
|
|
111
|
+
*/
|
|
112
|
+
export interface Service {
|
|
113
|
+
/**
|
|
114
|
+
* Generate text using the language model.
|
|
115
|
+
*/
|
|
116
|
+
readonly generateText: <
|
|
117
|
+
Options extends NoExcessProperties<GenerateTextOptions<any>, Options>,
|
|
118
|
+
Tools extends Record<string, Tool.Any> = {}
|
|
119
|
+
>(options: Options & GenerateTextOptions<Tools>) => Effect.Effect<
|
|
120
|
+
GenerateTextResponse<Tools>,
|
|
121
|
+
ExtractError<Options>,
|
|
122
|
+
ExtractContext<Options>
|
|
123
|
+
>
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Generate a structured object from a schema using the language model.
|
|
127
|
+
*/
|
|
128
|
+
readonly generateObject: <
|
|
129
|
+
A,
|
|
130
|
+
I extends Record<string, unknown>,
|
|
131
|
+
R,
|
|
132
|
+
Options extends NoExcessProperties<GenerateObjectOptions<any, A, I, R>, Options>,
|
|
133
|
+
Tools extends Record<string, Tool.Any> = {}
|
|
134
|
+
>(options: Options & GenerateObjectOptions<Tools, A, I, R>) => Effect.Effect<
|
|
135
|
+
GenerateObjectResponse<Tools, A>,
|
|
136
|
+
ExtractError<Options>,
|
|
137
|
+
R | ExtractContext<Options>
|
|
138
|
+
>
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Generate text using the language model with streaming output.
|
|
142
|
+
*/
|
|
143
|
+
readonly streamText: <
|
|
144
|
+
Options extends NoExcessProperties<GenerateTextOptions<any>, Options>,
|
|
145
|
+
Tools extends Record<string, Tool.Any> = {}
|
|
146
|
+
>(options: Options & GenerateTextOptions<Tools>) => Stream.Stream<
|
|
147
|
+
Response.StreamPart<Tools>,
|
|
148
|
+
ExtractError<Options>,
|
|
149
|
+
ExtractContext<Options>
|
|
150
|
+
>
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Configuration options for text generation.
|
|
155
|
+
*
|
|
156
|
+
* @since 1.0.0
|
|
157
|
+
* @category Models
|
|
158
|
+
*/
|
|
159
|
+
export interface GenerateTextOptions<Tools extends Record<string, Tool.Any>> {
|
|
160
|
+
/**
|
|
161
|
+
* The prompt input to use to generate text.
|
|
162
|
+
*/
|
|
163
|
+
readonly prompt: Prompt.RawInput
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* A toolkit containing both the tools and the tool call handler to use to
|
|
167
|
+
* augment text generation.
|
|
168
|
+
*/
|
|
169
|
+
readonly toolkit?: Toolkit.WithHandler<Tools> | Effect.Effect<Toolkit.WithHandler<Tools>, any, any> | undefined
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* The tool choice mode for the language model.
|
|
173
|
+
* - `auto` (default): The model can decide whether or not to call tools, as
|
|
174
|
+
* well as which tools to call.
|
|
175
|
+
* - `required`: The model **must** call a tool but can decide which tool will
|
|
176
|
+
* be called.
|
|
177
|
+
* - `none`: The model **must not** call a tool.
|
|
178
|
+
* - `{ tool: <tool_name> }`: The model must call the specified tool.
|
|
179
|
+
* - `{ mode?: "auto" (default) | "required", "oneOf": [<tool-names>] }`: The
|
|
180
|
+
* model is restricted to the subset of tools specified by `oneOf`. When
|
|
181
|
+
* `mode` is `"auto"` or omitted, the model can decide whether or not a tool
|
|
182
|
+
* from the allowed subset of tools can be called. When `mode` is
|
|
183
|
+
* `"required"`, the model **must** call one tool from the allowed subset of
|
|
184
|
+
* tools.
|
|
185
|
+
*/
|
|
186
|
+
readonly toolChoice?:
|
|
187
|
+
| ToolChoice<{ [Name in keyof Tools]: Tools[Name]["name"] }[keyof Tools]>
|
|
188
|
+
| undefined
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* The concurrency level for resolving tool calls.
|
|
192
|
+
*/
|
|
193
|
+
readonly concurrency?: Concurrency | undefined
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* When set to `true`, tool calls requested by the large language model
|
|
197
|
+
* will not be auto-resolved by the framework.
|
|
198
|
+
*
|
|
199
|
+
* This option is useful when:
|
|
200
|
+
* 1. The user wants to include tool call definitions from an `AiToolkit`
|
|
201
|
+
* in requests to the large language model so that the model has the
|
|
202
|
+
* capability to call tools
|
|
203
|
+
* 2. The user wants to control the execution of tool call resolvers
|
|
204
|
+
* instead of having the framework handle tool call resolution
|
|
205
|
+
*/
|
|
206
|
+
readonly disableToolCallResolution?: boolean | undefined
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Configuration options for structured object generation.
|
|
211
|
+
*
|
|
212
|
+
* @since 1.0.0
|
|
213
|
+
* @category Models
|
|
214
|
+
*/
|
|
215
|
+
export interface GenerateObjectOptions<Tools extends Record<string, Tool.Any>, A, I extends Record<string, unknown>, R>
|
|
216
|
+
extends GenerateTextOptions<Tools>
|
|
217
|
+
{
|
|
218
|
+
/**
|
|
219
|
+
* The name of the structured output that should be generated. Used by some
|
|
220
|
+
* large language model providers to provide additional guidance to the model.
|
|
221
|
+
*/
|
|
222
|
+
readonly objectName?: string | undefined
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* The schema to be used to specify the structure of the object to generate.
|
|
226
|
+
*/
|
|
227
|
+
readonly schema: Schema.Schema<A, I, R>
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* The tool choice mode for the language model.
|
|
232
|
+
* - `auto` (default): The model can decide whether or not to call tools, as
|
|
233
|
+
* well as which tools to call.
|
|
234
|
+
* - `required`: The model **must** call a tool but can decide which tool will
|
|
235
|
+
* be called.
|
|
236
|
+
* - `none`: The model **must not** call a tool.
|
|
237
|
+
* - `{ tool: <tool_name> }`: The model must call the specified tool.
|
|
238
|
+
* - `{ mode?: "auto" (default) | "required", "oneOf": [<tool-names>] }`: The
|
|
239
|
+
* model is restricted to the subset of tools specified by `oneOf`. When
|
|
240
|
+
* `mode` is `"auto"` or omitted, the model can decide whether or not a tool
|
|
241
|
+
* from the allowed subset of tools can be called. When `mode` is
|
|
242
|
+
* `"required"`, the model **must** call one tool from the allowed subset of
|
|
243
|
+
* tools.
|
|
244
|
+
*
|
|
245
|
+
* @since 1.0.0
|
|
246
|
+
* @category Models
|
|
247
|
+
*/
|
|
248
|
+
export type ToolChoice<Tools extends string> = "auto" | "none" | "required" | {
|
|
249
|
+
readonly tool: Tools
|
|
250
|
+
} | {
|
|
251
|
+
readonly mode?: "auto" | "required"
|
|
252
|
+
readonly oneOf: ReadonlyArray<Tools>
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Response class for text generation operations.
|
|
257
|
+
*
|
|
258
|
+
* Contains the generated content and provides convenient accessors for
|
|
259
|
+
* extracting different types of response parts like text, tool calls, and usage
|
|
260
|
+
* information.
|
|
261
|
+
*
|
|
262
|
+
* @example
|
|
263
|
+
* ```ts
|
|
264
|
+
* import { LanguageModel } from "@effect/ai"
|
|
265
|
+
* import { Effect } from "effect"
|
|
266
|
+
*
|
|
267
|
+
* const program = Effect.gen(function* () {
|
|
268
|
+
* const response = yield* LanguageModel.generateText({
|
|
269
|
+
* prompt: "Explain photosynthesis"
|
|
270
|
+
* })
|
|
271
|
+
*
|
|
272
|
+
* console.log(response.text) // Generated text content
|
|
273
|
+
* console.log(response.finishReason) // "stop", "length", etc.
|
|
274
|
+
* console.log(response.usage) // Usage information
|
|
275
|
+
*
|
|
276
|
+
* return response
|
|
277
|
+
* })
|
|
278
|
+
* ```
|
|
279
|
+
*
|
|
280
|
+
* @since 1.0.0
|
|
281
|
+
* @category Models
|
|
282
|
+
*/
|
|
283
|
+
export class GenerateTextResponse<Tools extends Record<string, Tool.Any>> {
|
|
284
|
+
readonly content: Array<Response.Part<Tools>>
|
|
285
|
+
|
|
286
|
+
constructor(content: Array<Response.Part<Tools>>) {
|
|
287
|
+
this.content = content
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Extracts and concatenates all text parts from the response.
|
|
292
|
+
*/
|
|
293
|
+
get text(): string {
|
|
294
|
+
const text: Array<string> = []
|
|
295
|
+
for (const part of this.content) {
|
|
296
|
+
if (part.type === "text") {
|
|
297
|
+
text.push(part.text)
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return text.join("")
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Returns all reasoning parts from the response.
|
|
305
|
+
*/
|
|
306
|
+
get reasoning(): Array<Response.ReasoningPart> {
|
|
307
|
+
return this.content.filter((part) => part.type === "reasoning")
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Extracts and concatenates all reasoning text, or undefined if none exists.
|
|
312
|
+
*/
|
|
313
|
+
get reasoningText(): string | undefined {
|
|
314
|
+
const text: Array<string> = []
|
|
315
|
+
for (const part of this.content) {
|
|
316
|
+
if (part.type === "reasoning") {
|
|
317
|
+
text.push(part.text)
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return text.length === 0 ? undefined : text.join("")
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Returns all tool call parts from the response.
|
|
325
|
+
*/
|
|
326
|
+
get toolCalls(): Array<Response.ToolCallParts<Tools>> {
|
|
327
|
+
return this.content.filter((part) => part.type === "tool-call")
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Returns all tool result parts from the response.
|
|
332
|
+
*/
|
|
333
|
+
get toolResults(): Array<Response.ToolResultParts<Tools>> {
|
|
334
|
+
return this.content.filter((part) => part.type === "tool-result")
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* The reason why text generation finished.
|
|
339
|
+
*/
|
|
340
|
+
get finishReason(): Response.FinishReason {
|
|
341
|
+
const finishPart = this.content.find((part) => part.type === "finish")
|
|
342
|
+
return Predicate.isUndefined(finishPart) ? "unknown" : finishPart.reason
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Token usage statistics for the generation request.
|
|
347
|
+
*/
|
|
348
|
+
get usage(): Response.Usage {
|
|
349
|
+
const finishPart = this.content.find((part) => part.type === "finish")
|
|
350
|
+
if (Predicate.isUndefined(finishPart)) {
|
|
351
|
+
return new Response.Usage({
|
|
352
|
+
inputTokens: undefined,
|
|
353
|
+
outputTokens: undefined,
|
|
354
|
+
totalTokens: undefined,
|
|
355
|
+
reasoningTokens: undefined,
|
|
356
|
+
cachedInputTokens: undefined
|
|
357
|
+
})
|
|
358
|
+
}
|
|
359
|
+
return finishPart.usage
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Response class for structured object generation operations.
|
|
365
|
+
*
|
|
366
|
+
* @example
|
|
367
|
+
* ```ts
|
|
368
|
+
* import { LanguageModel } from "@effect/ai"
|
|
369
|
+
* import { Effect, Schema } from "effect"
|
|
370
|
+
*
|
|
371
|
+
* const UserSchema = Schema.Struct({
|
|
372
|
+
* name: Schema.String,
|
|
373
|
+
* email: Schema.String
|
|
374
|
+
* })
|
|
375
|
+
*
|
|
376
|
+
* const program = Effect.gen(function* () {
|
|
377
|
+
* const response = yield* LanguageModel.generateObject({
|
|
378
|
+
* prompt: "Create user: John Doe, john@example.com",
|
|
379
|
+
* schema: UserSchema
|
|
380
|
+
* })
|
|
381
|
+
*
|
|
382
|
+
* console.log(response.value) // { name: "John Doe", email: "john@example.com" }
|
|
383
|
+
* console.log(response.text) // Raw generated text
|
|
384
|
+
*
|
|
385
|
+
* return response.value
|
|
386
|
+
* })
|
|
387
|
+
* ```
|
|
388
|
+
*
|
|
389
|
+
* @since 1.0.0
|
|
390
|
+
* @category Models
|
|
391
|
+
*/
|
|
392
|
+
export class GenerateObjectResponse<Tools extends Record<string, Tool.Any>, A> extends GenerateTextResponse<Tools> {
|
|
393
|
+
/**
|
|
394
|
+
* The parsed structured object that conforms to the provided schema.
|
|
395
|
+
*/
|
|
396
|
+
readonly value: A
|
|
397
|
+
|
|
398
|
+
constructor(value: A, content: Array<Response.Part<Tools>>) {
|
|
399
|
+
super(content)
|
|
400
|
+
this.value = value
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// =============================================================================
|
|
405
|
+
// Utility Types
|
|
406
|
+
// =============================================================================
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Utility type that extracts the error type from LanguageModel options.
|
|
410
|
+
*
|
|
411
|
+
* Automatically infers the possible error types based on toolkit configuration
|
|
412
|
+
* and tool call resolution settings.
|
|
413
|
+
*
|
|
414
|
+
* @since 1.0.0
|
|
415
|
+
* @category Utility Types
|
|
416
|
+
*/
|
|
417
|
+
export type ExtractError<Options> = Options extends {
|
|
418
|
+
readonly disableToolCallResolution: true
|
|
419
|
+
} ? Options extends {
|
|
420
|
+
readonly toolkit: Effect.Effect<Toolkit.WithHandler<infer _Tools>, infer _E, infer _R>
|
|
421
|
+
} ? AiError.AiError | _E :
|
|
422
|
+
AiError.AiError :
|
|
423
|
+
Options extends {
|
|
424
|
+
readonly toolkit: Toolkit.WithHandler<infer _Tools>
|
|
425
|
+
} ? AiError.AiError | Tool.Failure<_Tools[keyof _Tools]>
|
|
426
|
+
: Options extends {
|
|
427
|
+
readonly toolkit: Effect.Effect<Toolkit.WithHandler<infer _Tools>, infer _E, infer _R>
|
|
428
|
+
} ? AiError.AiError | Tool.Failure<_Tools[keyof _Tools]> | _E :
|
|
429
|
+
AiError.AiError
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Utility type that extracts the context requirements from LanguageModel options.
|
|
433
|
+
*
|
|
434
|
+
* Automatically infers the required services based on the toolkit configuration.
|
|
435
|
+
*
|
|
436
|
+
* @since 1.0.0
|
|
437
|
+
* @category Utility Types
|
|
438
|
+
*/
|
|
439
|
+
export type ExtractContext<Options> = Options extends {
|
|
440
|
+
readonly toolkit: Toolkit.WithHandler<infer _Tools>
|
|
441
|
+
} ? Tool.Requirements<_Tools[keyof _Tools]>
|
|
442
|
+
: Options extends {
|
|
443
|
+
readonly toolkit: Effect.Effect<Toolkit.WithHandler<infer _Tools>, infer _E, infer _R>
|
|
444
|
+
} ? Tool.Requirements<_Tools[keyof _Tools]> | _R
|
|
445
|
+
: never
|
|
446
|
+
|
|
447
|
+
// =============================================================================
|
|
448
|
+
// Service Constructor
|
|
449
|
+
// =============================================================================
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Configuration options passed along to language model provider
|
|
453
|
+
* implementations.
|
|
454
|
+
*
|
|
455
|
+
* This interface defines the normalized options that are passed to the
|
|
456
|
+
* underlying provider implementation, regardless of the specific provider being
|
|
457
|
+
* used.
|
|
458
|
+
*
|
|
459
|
+
* @since 1.0.0
|
|
460
|
+
* @category Models
|
|
461
|
+
*/
|
|
462
|
+
export interface ProviderOptions {
|
|
463
|
+
/**
|
|
464
|
+
* The prompt messages to use to generate text.
|
|
465
|
+
*/
|
|
466
|
+
readonly prompt: Prompt.Prompt
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* The tools that the large language model will have available to provide
|
|
470
|
+
* additional information which can be incorporated into its text generation.
|
|
471
|
+
*/
|
|
472
|
+
readonly tools: ReadonlyArray<Tool.Any>
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* The format which the response should be provided in.
|
|
476
|
+
*
|
|
477
|
+
* If `"text"` is specified, the large language model response will be
|
|
478
|
+
* returned as text.
|
|
479
|
+
*
|
|
480
|
+
* If `"json"` is specified, the large language model respose will be provided
|
|
481
|
+
* as an JSON object that conforms to the shape of the specified schema.
|
|
482
|
+
*
|
|
483
|
+
* Defaults to `{ type: "text" }`.
|
|
484
|
+
*/
|
|
485
|
+
readonly responseFormat:
|
|
486
|
+
| {
|
|
487
|
+
readonly type: "text"
|
|
488
|
+
}
|
|
489
|
+
| {
|
|
490
|
+
readonly type: "json"
|
|
491
|
+
readonly objectName: string
|
|
492
|
+
readonly schema: Schema.Schema.Any
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* The tool choice mode for the language model.
|
|
497
|
+
* - `auto` (default): The model can decide whether or not to call tools, as
|
|
498
|
+
* well as which tools to call.
|
|
499
|
+
* - `required`: The model **must** call a tool but can decide which tool will
|
|
500
|
+
* be called.
|
|
501
|
+
* - `none`: The model **must not** call a tool.
|
|
502
|
+
* - `{ tool: <tool_name> }`: The model must call the specified tool.
|
|
503
|
+
* - `{ mode?: "auto" (default) | "required", "oneOf": [<tool-names>] }`: The
|
|
504
|
+
* model is restricted to the subset of tools specified by `oneOf`. When
|
|
505
|
+
* `mode` is `"auto"` or omitted, the model can decide whether or not a tool
|
|
506
|
+
* from the allowed subset of tools can be called. When `mode` is
|
|
507
|
+
* `"required"`, the model **must** call one tool from the allowed subset of
|
|
508
|
+
* tools.
|
|
509
|
+
*/
|
|
510
|
+
readonly toolChoice: ToolChoice<any>
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* The span to use to trace interactions with the large language model.
|
|
514
|
+
*/
|
|
515
|
+
readonly span: Span
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Parameters required to construct a LanguageModel service.
|
|
520
|
+
*
|
|
521
|
+
* @since 1.0.0
|
|
522
|
+
* @category Models
|
|
523
|
+
*/
|
|
524
|
+
export interface ConstructorParams {
|
|
525
|
+
/**
|
|
526
|
+
* A method which requests text generation from the large language model
|
|
527
|
+
* provider.
|
|
528
|
+
*
|
|
529
|
+
* The final result is returned when the large language model provider
|
|
530
|
+
* finishes text generation.
|
|
531
|
+
*/
|
|
532
|
+
readonly generateText: (options: ProviderOptions) => Effect.Effect<
|
|
533
|
+
Array<Response.PartEncoded>,
|
|
534
|
+
AiError.AiError,
|
|
535
|
+
IdGenerator
|
|
536
|
+
>
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* A method which requests text generation from the large language model
|
|
540
|
+
* provider.
|
|
541
|
+
*
|
|
542
|
+
* Intermediate results are streamed from the large language model provider.
|
|
543
|
+
*/
|
|
544
|
+
readonly streamText: (options: ProviderOptions) => Stream.Stream<
|
|
545
|
+
Response.StreamPartEncoded,
|
|
546
|
+
AiError.AiError,
|
|
547
|
+
IdGenerator
|
|
548
|
+
>
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Creates a LanguageModel service from provider-specific implementations.
|
|
553
|
+
*
|
|
554
|
+
* This constructor takes provider-specific implementations for text generation
|
|
555
|
+
* and streaming text generation and returns a LanguageModel service.
|
|
556
|
+
*
|
|
557
|
+
* @since 1.0.0
|
|
558
|
+
* @category Constructors
|
|
559
|
+
*/
|
|
560
|
+
export const make: (params: ConstructorParams) => Effect.Effect<Service> = Effect.fnUntraced(
|
|
561
|
+
function*(params) {
|
|
562
|
+
const parentSpanTransformer = yield* Effect.serviceOption(CurrentSpanTransformer)
|
|
563
|
+
const getSpanTransformer = Effect.serviceOption(CurrentSpanTransformer).pipe(
|
|
564
|
+
Effect.map(Option.orElse(() => parentSpanTransformer))
|
|
565
|
+
)
|
|
566
|
+
|
|
567
|
+
const idGenerator = yield* Effect.serviceOption(IdGenerator).pipe(
|
|
568
|
+
Effect.map(Option.getOrElse(() => defaultIdGenerator))
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
const generateText = <
|
|
572
|
+
Options extends NoExcessProperties<GenerateTextOptions<any>, Options>,
|
|
573
|
+
Tools extends Record<string, Tool.Any> = {}
|
|
574
|
+
>(options: Options & GenerateTextOptions<Tools>): Effect.Effect<
|
|
575
|
+
GenerateTextResponse<Tools>,
|
|
576
|
+
ExtractError<Options>,
|
|
577
|
+
ExtractContext<Options>
|
|
578
|
+
> =>
|
|
579
|
+
Effect.useSpan(
|
|
580
|
+
"LanguageModel.generateText",
|
|
581
|
+
{
|
|
582
|
+
captureStackTrace: false,
|
|
583
|
+
attributes: {
|
|
584
|
+
concurrency: options.concurrency,
|
|
585
|
+
toolChoice: options.toolChoice
|
|
586
|
+
}
|
|
587
|
+
},
|
|
588
|
+
Effect.fnUntraced(
|
|
589
|
+
function*(span) {
|
|
590
|
+
const spanTransformer = yield* getSpanTransformer
|
|
591
|
+
|
|
592
|
+
const providerOptions: Mutable<ProviderOptions> = {
|
|
593
|
+
prompt: Prompt.make(options.prompt),
|
|
594
|
+
tools: [],
|
|
595
|
+
toolChoice: "none",
|
|
596
|
+
responseFormat: { type: "text" },
|
|
597
|
+
span
|
|
598
|
+
}
|
|
599
|
+
const content = yield* generateContent(options, providerOptions)
|
|
600
|
+
|
|
601
|
+
applySpanTransformer(spanTransformer, content as any, providerOptions)
|
|
602
|
+
|
|
603
|
+
return new GenerateTextResponse(content)
|
|
604
|
+
},
|
|
605
|
+
Effect.catchTag("ParseError", (error) =>
|
|
606
|
+
AiError.MalformedOutput.fromParseError({
|
|
607
|
+
module: "LanguageModel",
|
|
608
|
+
method: "generateText",
|
|
609
|
+
error
|
|
610
|
+
})),
|
|
611
|
+
(effect, span) => Effect.withParentSpan(effect, span),
|
|
612
|
+
Effect.provideService(IdGenerator, idGenerator)
|
|
613
|
+
)
|
|
614
|
+
) as any
|
|
615
|
+
|
|
616
|
+
const generateObject = <
|
|
617
|
+
A,
|
|
618
|
+
I extends Record<string, unknown>,
|
|
619
|
+
R,
|
|
620
|
+
Options extends NoExcessProperties<GenerateObjectOptions<any, A, I, R>, Options>,
|
|
621
|
+
Tools extends Record<string, Tool.Any> = {}
|
|
622
|
+
>(options: Options & GenerateObjectOptions<Tools, A, I, R>): Effect.Effect<
|
|
623
|
+
GenerateObjectResponse<Tools, A>,
|
|
624
|
+
ExtractError<Options>,
|
|
625
|
+
R | ExtractContext<Options>
|
|
626
|
+
> => {
|
|
627
|
+
const schema: Schema.Schema<A, I, R> = options.schema
|
|
628
|
+
const objectName = getObjectName(options.objectName, schema)
|
|
629
|
+
return Effect.useSpan(
|
|
630
|
+
"LanguageModel.generateObject",
|
|
631
|
+
{
|
|
632
|
+
captureStackTrace: false,
|
|
633
|
+
attributes: {
|
|
634
|
+
objectName,
|
|
635
|
+
concurrency: options.concurrency,
|
|
636
|
+
toolChoice: options.toolChoice
|
|
637
|
+
}
|
|
638
|
+
},
|
|
639
|
+
Effect.fnUntraced(
|
|
640
|
+
function*(span) {
|
|
641
|
+
const spanTransformer = yield* getSpanTransformer
|
|
642
|
+
|
|
643
|
+
const providerOptions: Mutable<ProviderOptions> = {
|
|
644
|
+
prompt: Prompt.make(options.prompt),
|
|
645
|
+
tools: [],
|
|
646
|
+
toolChoice: "none",
|
|
647
|
+
responseFormat: { type: "json", objectName, schema },
|
|
648
|
+
span
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
const content = yield* generateContent(options, providerOptions)
|
|
652
|
+
|
|
653
|
+
applySpanTransformer(spanTransformer, content as any, providerOptions)
|
|
654
|
+
|
|
655
|
+
const value = yield* resolveStructuredOutput(content as any, schema)
|
|
656
|
+
|
|
657
|
+
return new GenerateObjectResponse(value, content)
|
|
658
|
+
},
|
|
659
|
+
Effect.catchTag("ParseError", (error) =>
|
|
660
|
+
AiError.MalformedOutput.fromParseError({
|
|
661
|
+
module: "LanguageModel",
|
|
662
|
+
method: "generateText",
|
|
663
|
+
error
|
|
664
|
+
})),
|
|
665
|
+
(effect, span) => Effect.withParentSpan(effect, span),
|
|
666
|
+
Effect.provideService(IdGenerator, idGenerator)
|
|
667
|
+
)
|
|
668
|
+
) as any
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
const streamText: <
|
|
672
|
+
Options extends NoExcessProperties<GenerateTextOptions<any>, Options>,
|
|
673
|
+
Tools extends Record<string, Tool.Any> = {}
|
|
674
|
+
>(options: Options & GenerateTextOptions<Tools>) => Stream.Stream<
|
|
675
|
+
Response.StreamPart<Tools>,
|
|
676
|
+
ExtractError<Options>,
|
|
677
|
+
ExtractContext<Options>
|
|
678
|
+
> = Effect.fnUntraced(
|
|
679
|
+
function*<
|
|
680
|
+
Tools extends Record<string, Tool.Any>,
|
|
681
|
+
Options extends NoExcessProperties<GenerateTextOptions<Tools>, Options>
|
|
682
|
+
>(options: Options & GenerateTextOptions<Tools>) {
|
|
683
|
+
const span = yield* Effect.makeSpanScoped("LanguageModel.streamText", {
|
|
684
|
+
captureStackTrace: false,
|
|
685
|
+
attributes: { concurrency: options.concurrency, toolChoice: options.toolChoice }
|
|
686
|
+
})
|
|
687
|
+
|
|
688
|
+
const providerOptions: Mutable<ProviderOptions> = {
|
|
689
|
+
prompt: Prompt.make(options.prompt),
|
|
690
|
+
tools: [],
|
|
691
|
+
toolChoice: "none",
|
|
692
|
+
responseFormat: { type: "text" },
|
|
693
|
+
span
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// Resolve the content stream for the request
|
|
697
|
+
const stream = yield* streamContent(options, providerOptions)
|
|
698
|
+
|
|
699
|
+
// Return the stream immediately if there is no span transformer
|
|
700
|
+
const spanTransformer = yield* getSpanTransformer
|
|
701
|
+
if (Option.isNone(spanTransformer)) {
|
|
702
|
+
return stream
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// Otherwise aggregate generated content and apply the span transformer
|
|
706
|
+
// when the stream is finished
|
|
707
|
+
let content: Array<Response.StreamPart<Tools>> = []
|
|
708
|
+
return stream.pipe(
|
|
709
|
+
Stream.mapChunks((chunk) => {
|
|
710
|
+
content = [...content, ...chunk]
|
|
711
|
+
return chunk
|
|
712
|
+
}),
|
|
713
|
+
Stream.ensuring(Effect.sync(() => {
|
|
714
|
+
spanTransformer.value({ ...providerOptions, response: content as any })
|
|
715
|
+
}))
|
|
716
|
+
)
|
|
717
|
+
},
|
|
718
|
+
Stream.unwrapScoped,
|
|
719
|
+
Stream.mapError((error) =>
|
|
720
|
+
ParseResult.isParseError(error)
|
|
721
|
+
? AiError.MalformedOutput.fromParseError({
|
|
722
|
+
module: "LanguageModel",
|
|
723
|
+
method: "streamText",
|
|
724
|
+
error
|
|
725
|
+
})
|
|
726
|
+
: error
|
|
727
|
+
),
|
|
728
|
+
Stream.provideService(IdGenerator, idGenerator)
|
|
729
|
+
) as any
|
|
730
|
+
|
|
731
|
+
const generateContent: <
|
|
732
|
+
Options extends NoExcessProperties<GenerateTextOptions<any>, Options>,
|
|
733
|
+
Tools extends Record<string, Tool.Any> = {}
|
|
734
|
+
>(options: Options & GenerateTextOptions<Tools>, providerOptions: Mutable<ProviderOptions>) => Effect.Effect<
|
|
735
|
+
Array<Response.Part<Tools>>,
|
|
736
|
+
AiError.AiError | ParseResult.ParseError,
|
|
737
|
+
IdGenerator
|
|
738
|
+
> = Effect.fnUntraced(
|
|
739
|
+
function*<
|
|
740
|
+
Tools extends Record<string, Tool.Any>,
|
|
741
|
+
Options extends NoExcessProperties<GenerateTextOptions<Tools>, Options>
|
|
742
|
+
>(options: Options & GenerateTextOptions<Tools>, providerOptions: Mutable<ProviderOptions>) {
|
|
743
|
+
const toolChoice = options.toolChoice ?? "auto"
|
|
744
|
+
|
|
745
|
+
// If there is no toolkit, the generated content can be returned immediately
|
|
746
|
+
if (Predicate.isUndefined(options.toolkit)) {
|
|
747
|
+
const ResponseSchema = Schema.mutable(Schema.Array(Response.Part(Toolkit.empty)))
|
|
748
|
+
const rawContent = yield* params.generateText(providerOptions)
|
|
749
|
+
const content = yield* Schema.decodeUnknown(ResponseSchema)(rawContent)
|
|
750
|
+
return content as Array<Response.Part<Tools>>
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// If there is a toolkit resolve and apply it to the provider options
|
|
754
|
+
const toolkit = yield* resolveToolkit<Tools, any, any>(options.toolkit)
|
|
755
|
+
|
|
756
|
+
// If the resolved toolkit is empty, return the generated content immediately
|
|
757
|
+
if (Object.values(toolkit.tools).length === 0) {
|
|
758
|
+
const ResponseSchema = Schema.mutable(Schema.Array(Response.Part(Toolkit.empty)))
|
|
759
|
+
const rawContent = yield* params.generateText(providerOptions)
|
|
760
|
+
const content = yield* Schema.decodeUnknown(ResponseSchema)(rawContent)
|
|
761
|
+
return content as Array<Response.Part<Tools>>
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
const tools = typeof toolChoice === "object" && "oneOf" in toolChoice
|
|
765
|
+
? Object.values(toolkit.tools).filter((tool) => toolChoice.oneOf.includes(tool.name))
|
|
766
|
+
: Object.values(toolkit.tools)
|
|
767
|
+
providerOptions.tools = tools
|
|
768
|
+
providerOptions.toolChoice = toolChoice
|
|
769
|
+
|
|
770
|
+
// If tool call resolution is disabled, return the response without
|
|
771
|
+
// resolving the tool calls that were generated
|
|
772
|
+
if (options.disableToolCallResolution === true) {
|
|
773
|
+
const ResponseSchema = Schema.mutable(Schema.Array(Response.Part(Toolkit.empty)))
|
|
774
|
+
const rawContent = yield* params.generateText(providerOptions)
|
|
775
|
+
const content = yield* Schema.decodeUnknown(ResponseSchema)(rawContent)
|
|
776
|
+
return content as Array<Response.Part<Tools>>
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
const rawContent = yield* params.generateText(providerOptions)
|
|
780
|
+
|
|
781
|
+
// Resolve the generated tool calls
|
|
782
|
+
const toolResults = yield* resolveToolCalls(rawContent, toolkit, options.concurrency)
|
|
783
|
+
const ResponseSchema = Schema.mutable(Schema.Array(Response.Part(toolkit)))
|
|
784
|
+
const content = yield* Schema.decodeUnknown(ResponseSchema)(rawContent)
|
|
785
|
+
|
|
786
|
+
// Return the content merged with the tool call results
|
|
787
|
+
return [...content, ...toolResults] as Array<Response.Part<Tools>>
|
|
788
|
+
}
|
|
789
|
+
)
|
|
790
|
+
|
|
791
|
+
const streamContent: <
|
|
792
|
+
Options extends NoExcessProperties<GenerateTextOptions<any>, Options>,
|
|
793
|
+
Tools extends Record<string, Tool.Any> = {}
|
|
794
|
+
>(options: Options & GenerateTextOptions<Tools>, providerOptions: Mutable<ProviderOptions>) => Effect.Effect<
|
|
795
|
+
Stream.Stream<Response.StreamPart<Tools>, AiError.AiError | ParseResult.ParseError, IdGenerator>,
|
|
796
|
+
Options extends { readonly toolkit: Effect.Effect<Toolkit.WithHandler<Tools>, infer _E, infer _R> } ? _E : never,
|
|
797
|
+
Options extends { readonly toolkit: Effect.Effect<Toolkit.WithHandler<Tools>, infer _E, infer _R> } ? _R : never
|
|
798
|
+
> = Effect.fnUntraced(
|
|
799
|
+
function*<
|
|
800
|
+
Tools extends Record<string, Tool.Any>,
|
|
801
|
+
Options extends NoExcessProperties<GenerateTextOptions<Tools>, Options>
|
|
802
|
+
>(options: Options & GenerateTextOptions<Tools>, providerOptions: Mutable<ProviderOptions>) {
|
|
803
|
+
const toolChoice = options.toolChoice ?? "auto"
|
|
804
|
+
|
|
805
|
+
// If there is no toolkit, return immediately
|
|
806
|
+
if (Predicate.isUndefined(options.toolkit)) {
|
|
807
|
+
const schema = Schema.ChunkFromSelf(Response.StreamPart(Toolkit.empty))
|
|
808
|
+
const decode = Schema.decode(schema)
|
|
809
|
+
return params.streamText(providerOptions).pipe(
|
|
810
|
+
Stream.mapChunksEffect(decode)
|
|
811
|
+
) as Stream.Stream<Response.StreamPart<Tools>, AiError.AiError | ParseResult.ParseError, IdGenerator>
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
// If there is a toolkit resolve and apply it to the provider options
|
|
815
|
+
const toolkit = Effect.isEffect(options.toolkit) ? yield* options.toolkit : options.toolkit
|
|
816
|
+
|
|
817
|
+
// If the toolkit is empty, return immediately
|
|
818
|
+
if (Object.values(toolkit.tools).length === 0) {
|
|
819
|
+
const schema = Schema.ChunkFromSelf(Response.StreamPart(Toolkit.empty))
|
|
820
|
+
const decode = Schema.decode(schema)
|
|
821
|
+
return params.streamText(providerOptions).pipe(
|
|
822
|
+
Stream.mapChunksEffect(decode)
|
|
823
|
+
) as Stream.Stream<Response.StreamPart<Tools>, AiError.AiError | ParseResult.ParseError, IdGenerator>
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
const tools = typeof toolChoice === "object" && "oneOf" in toolChoice
|
|
827
|
+
? Object.values(toolkit.tools).filter((tool) => toolChoice.oneOf.includes(tool.name))
|
|
828
|
+
: Object.values(toolkit.tools)
|
|
829
|
+
providerOptions.tools = tools
|
|
830
|
+
providerOptions.toolChoice = toolChoice
|
|
831
|
+
|
|
832
|
+
// If tool call resolution is disabled, return the response without
|
|
833
|
+
// resolving the tool calls that were generated
|
|
834
|
+
if (options.disableToolCallResolution === true) {
|
|
835
|
+
const schema = Schema.ChunkFromSelf(Response.StreamPart(toolkit))
|
|
836
|
+
const decode = Schema.decode(schema)
|
|
837
|
+
return params.streamText(providerOptions).pipe(
|
|
838
|
+
Stream.mapChunksEffect(decode)
|
|
839
|
+
) as Stream.Stream<Response.StreamPart<Tools>, AiError.AiError | ParseResult.ParseError, IdGenerator>
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
const ResponseSchema = Schema.Chunk(Response.StreamPart(toolkit))
|
|
843
|
+
const decode = Schema.decode(ResponseSchema)
|
|
844
|
+
return params.streamText(providerOptions).pipe(
|
|
845
|
+
Stream.mapChunksEffect(Effect.fnUntraced(function*(chunk) {
|
|
846
|
+
const rawContent = Chunk.toArray(chunk)
|
|
847
|
+
const toolResults = yield* resolveToolCalls(rawContent, toolkit, options.concurrency)
|
|
848
|
+
const content = yield* decode(rawContent)
|
|
849
|
+
return Chunk.unsafeFromArray([...content, ...toolResults])
|
|
850
|
+
}))
|
|
851
|
+
) as Stream.Stream<Response.StreamPart<Tools>, AiError.AiError | ParseResult.ParseError, IdGenerator>
|
|
852
|
+
}
|
|
853
|
+
)
|
|
854
|
+
|
|
855
|
+
return {
|
|
856
|
+
generateText,
|
|
857
|
+
generateObject,
|
|
858
|
+
streamText
|
|
859
|
+
} as const
|
|
860
|
+
}
|
|
861
|
+
)
|
|
862
|
+
|
|
863
|
+
// =============================================================================
|
|
864
|
+
// Accessors
|
|
865
|
+
// =============================================================================
|
|
866
|
+
|
|
867
|
+
/**
|
|
868
|
+
* Generate text using a language model.
|
|
869
|
+
*
|
|
870
|
+
* @example
|
|
871
|
+
* ```ts
|
|
872
|
+
* import { LanguageModel } from "@effect/ai"
|
|
873
|
+
* import { Effect } from "effect"
|
|
874
|
+
*
|
|
875
|
+
* const program = Effect.gen(function* () {
|
|
876
|
+
* const response = yield* LanguageModel.generateText({
|
|
877
|
+
* prompt: "Write a haiku about programming",
|
|
878
|
+
* toolChoice: "none"
|
|
879
|
+
* })
|
|
880
|
+
*
|
|
881
|
+
* console.log(response.text)
|
|
882
|
+
* console.log(response.usage.totalTokens)
|
|
883
|
+
*
|
|
884
|
+
* return response
|
|
885
|
+
* })
|
|
886
|
+
* ```
|
|
887
|
+
*
|
|
888
|
+
* @since 1.0.0
|
|
889
|
+
* @category Functions
|
|
890
|
+
*/
|
|
891
|
+
export const generateText: <
|
|
892
|
+
Options extends NoExcessProperties<GenerateTextOptions<any>, Options>,
|
|
893
|
+
Tools extends Record<string, Tool.Any> = {}
|
|
894
|
+
>(options: Options & GenerateTextOptions<Tools>) => Effect.Effect<
|
|
895
|
+
GenerateTextResponse<Tools>,
|
|
896
|
+
ExtractError<Options>,
|
|
897
|
+
LanguageModel | ExtractContext<Options>
|
|
898
|
+
> = Effect.serviceFunctionEffect(LanguageModel, (model) => model.generateText)
|
|
899
|
+
|
|
900
|
+
/**
|
|
901
|
+
* Generate a structured object from a schema using a language model.
|
|
902
|
+
*
|
|
903
|
+
* @example
|
|
904
|
+
* ```ts
|
|
905
|
+
* import { LanguageModel } from "@effect/ai"
|
|
906
|
+
* import { Effect, Schema } from "effect"
|
|
907
|
+
*
|
|
908
|
+
* const EventSchema = Schema.Struct({
|
|
909
|
+
* title: Schema.String,
|
|
910
|
+
* date: Schema.String,
|
|
911
|
+
* location: Schema.String
|
|
912
|
+
* })
|
|
913
|
+
*
|
|
914
|
+
* const program = Effect.gen(function* () {
|
|
915
|
+
* const response = yield* LanguageModel.generateObject({
|
|
916
|
+
* prompt: "Extract event info: Tech Conference on March 15th in San Francisco",
|
|
917
|
+
* schema: EventSchema,
|
|
918
|
+
* objectName: "event"
|
|
919
|
+
* })
|
|
920
|
+
*
|
|
921
|
+
* console.log(response.value)
|
|
922
|
+
* // { title: "Tech Conference", date: "March 15th", location: "San Francisco" }
|
|
923
|
+
*
|
|
924
|
+
* return response.value
|
|
925
|
+
* })
|
|
926
|
+
* ```
|
|
927
|
+
*
|
|
928
|
+
* @since 1.0.0
|
|
929
|
+
* @category Functions
|
|
930
|
+
*/
|
|
931
|
+
export const generateObject: <
|
|
932
|
+
A,
|
|
933
|
+
I extends Record<string, unknown>,
|
|
934
|
+
R,
|
|
935
|
+
Options extends NoExcessProperties<GenerateObjectOptions<any, A, I, R>, Options>,
|
|
936
|
+
Tools extends Record<string, Tool.Any> = {}
|
|
937
|
+
>(options: Options & GenerateObjectOptions<Tools, A, I, R>) => Effect.Effect<
|
|
938
|
+
GenerateObjectResponse<Tools, A>,
|
|
939
|
+
ExtractError<Options>,
|
|
940
|
+
LanguageModel | R | ExtractContext<Options>
|
|
941
|
+
> = Effect.serviceFunctionEffect(LanguageModel, (model) => model.generateObject)
|
|
942
|
+
|
|
943
|
+
/**
|
|
944
|
+
* Generate text using a language model with streaming output.
|
|
945
|
+
*
|
|
946
|
+
* Returns a stream of response parts that are emitted as soon as they are
|
|
947
|
+
* available from the model, enabling real-time text generation experiences.
|
|
948
|
+
*
|
|
949
|
+
* @example
|
|
950
|
+
* ```ts
|
|
951
|
+
* import { LanguageModel } from "@effect/ai"
|
|
952
|
+
* import { Effect, Stream, Console } from "effect"
|
|
953
|
+
*
|
|
954
|
+
* const program = LanguageModel.streamText({
|
|
955
|
+
* prompt: "Write a story about a space explorer"
|
|
956
|
+
* }).pipe(Stream.runForEach((part) => {
|
|
957
|
+
* if (part.type === "text-delta") {
|
|
958
|
+
* return Console.log(part.delta)
|
|
959
|
+
* }
|
|
960
|
+
* return Effect.void
|
|
961
|
+
* }))
|
|
962
|
+
* ```
|
|
963
|
+
*
|
|
964
|
+
* @since 1.0.0
|
|
965
|
+
* @category Functions
|
|
966
|
+
*/
|
|
967
|
+
export const streamText = <
|
|
968
|
+
Options extends NoExcessProperties<GenerateTextOptions<any>, Options>,
|
|
969
|
+
Tools extends Record<string, Tool.Any> = {}
|
|
970
|
+
>(options: Options & GenerateTextOptions<Tools>): Stream.Stream<
|
|
971
|
+
Response.StreamPart<Tools>,
|
|
972
|
+
ExtractError<Options>,
|
|
973
|
+
LanguageModel | ExtractContext<Options>
|
|
974
|
+
> => Stream.unwrap(LanguageModel.pipe(Effect.map((model) => model.streamText(options))))
|
|
975
|
+
|
|
976
|
+
// =============================================================================
|
|
977
|
+
// Tool Call Resolution
|
|
978
|
+
// =============================================================================
|
|
979
|
+
|
|
980
|
+
const resolveToolCalls = <Tools extends Record<string, Tool.Any>>(
|
|
981
|
+
content: ReadonlyArray<Response.AllPartsEncoded>,
|
|
982
|
+
toolkit: Toolkit.WithHandler<Tools>,
|
|
983
|
+
concurrency: Concurrency | undefined
|
|
984
|
+
): Effect.Effect<
|
|
985
|
+
ReadonlyArray<Response.ToolResultPart<Tool.Name<Tools[keyof Tools]>, Tool.Success<Tools[keyof Tools]>>>,
|
|
986
|
+
Tool.Failure<Tools[keyof Tools]>,
|
|
987
|
+
Tool.Requirements<Tools[keyof Tools]>
|
|
988
|
+
> => {
|
|
989
|
+
const toolNames: Array<string> = []
|
|
990
|
+
const toolCalls: Array<Response.ToolCallPartEncoded> = []
|
|
991
|
+
|
|
992
|
+
for (const part of content) {
|
|
993
|
+
if (part.type === "tool-call") {
|
|
994
|
+
toolNames.push(part.name)
|
|
995
|
+
if (part.providerExecuted === true) {
|
|
996
|
+
continue
|
|
997
|
+
}
|
|
998
|
+
toolCalls.push(part)
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
return Effect.forEach(toolCalls, (toolCall) => {
|
|
1003
|
+
return toolkit.handle(toolCall.name, toolCall.params as any).pipe(
|
|
1004
|
+
Effect.map(({ encodedResult, result }) =>
|
|
1005
|
+
Response.makePart("tool-result", {
|
|
1006
|
+
id: toolCall.id,
|
|
1007
|
+
name: toolCall.name,
|
|
1008
|
+
result,
|
|
1009
|
+
encodedResult,
|
|
1010
|
+
providerName: toolCall.providerName,
|
|
1011
|
+
providerExecuted: false
|
|
1012
|
+
})
|
|
1013
|
+
)
|
|
1014
|
+
)
|
|
1015
|
+
}, { concurrency })
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
// =============================================================================
|
|
1019
|
+
// Utilities
|
|
1020
|
+
// =============================================================================
|
|
1021
|
+
|
|
1022
|
+
const resolveToolkit = <Tools extends Record<string, Tool.Any>, E, R>(
|
|
1023
|
+
toolkit: Toolkit.WithHandler<Tools> | Effect.Effect<Toolkit.WithHandler<Tools>, E, R>
|
|
1024
|
+
): Effect.Effect<Toolkit.WithHandler<Tools>, E, R> => Effect.isEffect(toolkit) ? toolkit : Effect.succeed(toolkit)
|
|
1025
|
+
|
|
1026
|
+
const getObjectName = <A, I extends Record<string, unknown>, R>(
|
|
1027
|
+
objectName: string | undefined,
|
|
1028
|
+
schema: Schema.Schema<A, I, R>
|
|
1029
|
+
): string =>
|
|
1030
|
+
Predicate.isNotUndefined(objectName)
|
|
1031
|
+
? objectName
|
|
1032
|
+
: "_tag" in schema
|
|
1033
|
+
? schema._tag as string
|
|
1034
|
+
: "identifier" in schema
|
|
1035
|
+
? schema.identifier as string
|
|
1036
|
+
: "generateObject"
|
|
1037
|
+
|
|
1038
|
+
const resolveStructuredOutput = Effect.fnUntraced(
|
|
1039
|
+
function*<A, I, R>(response: ReadonlyArray<Response.AllParts<any>>, ResultSchema: Schema.Schema<A, I, R>) {
|
|
1040
|
+
const text: Array<string> = []
|
|
1041
|
+
for (const part of response) {
|
|
1042
|
+
if (part.type === "text") {
|
|
1043
|
+
text.push(part.text)
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
if (text.length === 0) {
|
|
1048
|
+
return yield* new AiError.MalformedOutput({
|
|
1049
|
+
module: "LanguageModel",
|
|
1050
|
+
method: "generateObject",
|
|
1051
|
+
description: "No object was generated by the large language model"
|
|
1052
|
+
})
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
const decode = Schema.decode(Schema.parseJson(ResultSchema))
|
|
1056
|
+
return yield* Effect.mapError(decode(text.join("")), (cause) =>
|
|
1057
|
+
new AiError.MalformedOutput({
|
|
1058
|
+
module: "LanguageModel",
|
|
1059
|
+
method: "generateObject",
|
|
1060
|
+
description: "Generated object failed to conform to provided schema",
|
|
1061
|
+
cause
|
|
1062
|
+
}))
|
|
1063
|
+
}
|
|
1064
|
+
)
|
|
1065
|
+
|
|
1066
|
+
const applySpanTransformer = (
|
|
1067
|
+
transformer: Option.Option<SpanTransformer>,
|
|
1068
|
+
response: ReadonlyArray<Response.AllParts<any>>,
|
|
1069
|
+
options: ProviderOptions
|
|
1070
|
+
): void => {
|
|
1071
|
+
if (Option.isSome(transformer)) {
|
|
1072
|
+
transformer.value({ ...options, response: response as any })
|
|
1073
|
+
}
|
|
1074
|
+
}
|