@effect/ai 0.1.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/AiChat/package.json +6 -0
- package/AiError/package.json +6 -0
- package/AiInput/package.json +6 -0
- package/AiResponse/package.json +6 -0
- package/AiRole/package.json +6 -0
- package/AiToolkit/package.json +6 -0
- package/Completions/package.json +6 -0
- package/LICENSE +21 -0
- package/README.md +1 -0
- package/Tokenizer/package.json +6 -0
- package/dist/cjs/AiChat.js +151 -0
- package/dist/cjs/AiChat.js.map +1 -0
- package/dist/cjs/AiError.js +41 -0
- package/dist/cjs/AiError.js.map +1 -0
- package/dist/cjs/AiInput.js +349 -0
- package/dist/cjs/AiInput.js.map +1 -0
- package/dist/cjs/AiResponse.js +295 -0
- package/dist/cjs/AiResponse.js.map +1 -0
- package/dist/cjs/AiRole.js +106 -0
- package/dist/cjs/AiRole.js.map +1 -0
- package/dist/cjs/AiToolkit.js +132 -0
- package/dist/cjs/AiToolkit.js.map +1 -0
- package/dist/cjs/Completions.js +217 -0
- package/dist/cjs/Completions.js.map +1 -0
- package/dist/cjs/Tokenizer.js +59 -0
- package/dist/cjs/Tokenizer.js.map +1 -0
- package/dist/cjs/index.js +25 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/dts/AiChat.d.ts +73 -0
- package/dist/dts/AiChat.d.ts.map +1 -0
- package/dist/dts/AiError.d.ts +38 -0
- package/dist/dts/AiError.d.ts.map +1 -0
- package/dist/dts/AiInput.d.ts +283 -0
- package/dist/dts/AiInput.d.ts.map +1 -0
- package/dist/dts/AiResponse.d.ts +235 -0
- package/dist/dts/AiResponse.d.ts.map +1 -0
- package/dist/dts/AiRole.d.ts +111 -0
- package/dist/dts/AiRole.d.ts.map +1 -0
- package/dist/dts/AiToolkit.d.ts +158 -0
- package/dist/dts/AiToolkit.d.ts.map +1 -0
- package/dist/dts/Completions.d.ts +104 -0
- package/dist/dts/Completions.d.ts.map +1 -0
- package/dist/dts/Tokenizer.d.ts +34 -0
- package/dist/dts/Tokenizer.d.ts.map +1 -0
- package/dist/dts/index.d.ts +33 -0
- package/dist/dts/index.d.ts.map +1 -0
- package/dist/esm/AiChat.js +139 -0
- package/dist/esm/AiChat.js.map +1 -0
- package/dist/esm/AiError.js +31 -0
- package/dist/esm/AiError.js.map +1 -0
- package/dist/esm/AiInput.js +332 -0
- package/dist/esm/AiInput.js.map +1 -0
- package/dist/esm/AiResponse.js +281 -0
- package/dist/esm/AiResponse.js.map +1 -0
- package/dist/esm/AiRole.js +93 -0
- package/dist/esm/AiRole.js.map +1 -0
- package/dist/esm/AiToolkit.js +123 -0
- package/dist/esm/AiToolkit.js.map +1 -0
- package/dist/esm/Completions.js +206 -0
- package/dist/esm/Completions.js.map +1 -0
- package/dist/esm/Tokenizer.js +48 -0
- package/dist/esm/Tokenizer.js.map +1 -0
- package/dist/esm/index.js +33 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/package.json +4 -0
- package/package.json +100 -0
- package/src/AiChat.ts +274 -0
- package/src/AiError.ts +38 -0
- package/src/AiInput.ts +456 -0
- package/src/AiResponse.ts +343 -0
- package/src/AiRole.ts +122 -0
- package/src/AiToolkit.ts +314 -0
- package/src/Completions.ts +354 -0
- package/src/Tokenizer.ts +78 -0
- package/src/index.ts +39 -0
package/src/AiToolkit.ts
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @since 1.0.0
|
|
3
|
+
*/
|
|
4
|
+
import type * as Schema from "@effect/schema/Schema"
|
|
5
|
+
import type * as Serializable from "@effect/schema/Serializable"
|
|
6
|
+
import * as Context from "effect/Context"
|
|
7
|
+
import * as Effect from "effect/Effect"
|
|
8
|
+
import * as Effectable from "effect/Effectable"
|
|
9
|
+
import { identity } from "effect/Function"
|
|
10
|
+
import * as HashMap from "effect/HashMap"
|
|
11
|
+
import * as Inspectable from "effect/Inspectable"
|
|
12
|
+
import * as Layer from "effect/Layer"
|
|
13
|
+
import { pipeArguments } from "effect/Pipeable"
|
|
14
|
+
import type { Scope } from "effect/Scope"
|
|
15
|
+
import type * as Types from "effect/Types"
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @since 1.0.0
|
|
19
|
+
* @category type ids
|
|
20
|
+
*/
|
|
21
|
+
export const TypeId: unique symbol = Symbol.for("@effect/ai/AiToolkit")
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @since 1.0.0
|
|
25
|
+
* @category type ids
|
|
26
|
+
*/
|
|
27
|
+
export type TypeId = typeof TypeId
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @since 1.0.0
|
|
31
|
+
* @category models
|
|
32
|
+
*/
|
|
33
|
+
export interface AiToolkit<in out Tools extends Tool.AnySchema>
|
|
34
|
+
extends Effect.Effect<Handlers<Tools>, never, Tool.Services<Tools> | Registry>, Inspectable.Inspectable
|
|
35
|
+
{
|
|
36
|
+
readonly [TypeId]: TypeId
|
|
37
|
+
readonly tools: HashMap.HashMap<string, Tools>
|
|
38
|
+
readonly add: <S extends Tool.AnySchema>(tool: S) => AiToolkit<Tools | S>
|
|
39
|
+
readonly addAll: <ToAdd extends ReadonlyArray<Tool.AnySchema>>(
|
|
40
|
+
...tools: ToAdd
|
|
41
|
+
) => AiToolkit<Tools | ToAdd[number]>
|
|
42
|
+
readonly concat: <T extends Tool.AnySchema>(that: AiToolkit<T>) => AiToolkit<Tools | T>
|
|
43
|
+
readonly implement: <R, EX = never, RX = never>(
|
|
44
|
+
f: (
|
|
45
|
+
handlers: Handlers<Tools>
|
|
46
|
+
) => Handlers<never, R> | Effect.Effect<Handlers<never, R>, EX, RX>
|
|
47
|
+
) => Layer.Layer<Tool.ServiceFromTag<Tools["_tag"]> | Registry, EX, R | RX>
|
|
48
|
+
readonly implementScoped: <R, EX = never, RX = never>(
|
|
49
|
+
f: (
|
|
50
|
+
handlers: Handlers<Tools>
|
|
51
|
+
) => Handlers<never, R> | Effect.Effect<Handlers<never, R>, EX, RX>
|
|
52
|
+
) => Layer.Layer<Tool.ServiceFromTag<Tools["_tag"]> | Registry, EX, Exclude<R | RX, Scope>>
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @since 1.0.0
|
|
57
|
+
* @category models
|
|
58
|
+
*/
|
|
59
|
+
export declare namespace AiToolkit {
|
|
60
|
+
/**
|
|
61
|
+
* @since 1.0.0
|
|
62
|
+
* @category models
|
|
63
|
+
*/
|
|
64
|
+
export type Tools<A> = A extends AiToolkit<infer Tools> ? Tools : never
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @since 1.0.0
|
|
68
|
+
* @category models
|
|
69
|
+
*/
|
|
70
|
+
export type SuccessSchema<A> = A extends AiToolkit<infer Tools> ? Tools["success"] : never
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @since 1.0.0
|
|
75
|
+
* @category tool
|
|
76
|
+
*/
|
|
77
|
+
export declare namespace Tool {
|
|
78
|
+
/**
|
|
79
|
+
* @since 1.0.0
|
|
80
|
+
* @category tool
|
|
81
|
+
*/
|
|
82
|
+
export interface AnySchema {
|
|
83
|
+
readonly [Schema.TypeId]: any
|
|
84
|
+
readonly _tag: string
|
|
85
|
+
readonly Type: Serializable.SerializableWithResult.All
|
|
86
|
+
readonly success: Schema.Schema.Any
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @since 1.0.0
|
|
91
|
+
* @category tool
|
|
92
|
+
*/
|
|
93
|
+
export type Success<Tool extends AnySchema> = Serializable.WithResult.Success<Tool["Type"]>
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* @since 1.0.0
|
|
97
|
+
* @category tool
|
|
98
|
+
*/
|
|
99
|
+
export type Failure<Tool extends AnySchema> = Serializable.WithResult.Failure<Tool["Type"]>
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* @since 1.0.0
|
|
103
|
+
* @category tool
|
|
104
|
+
*/
|
|
105
|
+
export type Context<Tool extends AnySchema> = Serializable.WithResult.Context<Tool["Type"]>
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* @since 1.0.0
|
|
109
|
+
* @category tool
|
|
110
|
+
*/
|
|
111
|
+
export type Handler<Tool extends AnySchema, R> = (
|
|
112
|
+
params: Tool["Type"]
|
|
113
|
+
) => Effect.Effect<Success<Tool>, Failure<Tool>, R>
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @since 1.0.0
|
|
117
|
+
* @category tool
|
|
118
|
+
*/
|
|
119
|
+
export type HandlerAny = (params: any) => Effect.Effect<any, any, any>
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* @since 1.0.0
|
|
123
|
+
* @category tool
|
|
124
|
+
*/
|
|
125
|
+
export interface Service<Tag extends string> {
|
|
126
|
+
readonly _: unique symbol
|
|
127
|
+
readonly name: Types.Invariant<Tag>
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* @since 1.0.0
|
|
132
|
+
* @category tool
|
|
133
|
+
*/
|
|
134
|
+
export type ServiceFromTag<Tag extends string> = Tag extends infer T ? T extends string ? Service<T> : never : never
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* @since 1.0.0
|
|
138
|
+
* @category tool
|
|
139
|
+
*/
|
|
140
|
+
export type Services<Tools extends AnySchema> = ServiceFromTag<Tools["_tag"]>
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* @since 1.0.0
|
|
145
|
+
* @category registry
|
|
146
|
+
*/
|
|
147
|
+
export class Registry extends Context.Tag("@effect/ai/AiToolkit/Registry")<
|
|
148
|
+
Registry,
|
|
149
|
+
Map<Tool.AnySchema, Tool.HandlerAny>
|
|
150
|
+
>() {
|
|
151
|
+
static readonly Live: Layer.Layer<Registry> = Layer.sync(Registry, () => new Map())
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
class AiToolkitImpl<Tools extends Tool.AnySchema>
|
|
155
|
+
extends Effectable.Class<Handlers<Tools>, never, Tool.Services<Tools> | Registry>
|
|
156
|
+
implements AiToolkit<Tools>
|
|
157
|
+
{
|
|
158
|
+
readonly [TypeId]: TypeId
|
|
159
|
+
constructor(readonly tools: HashMap.HashMap<string, Tools>) {
|
|
160
|
+
super()
|
|
161
|
+
this[TypeId] = TypeId
|
|
162
|
+
}
|
|
163
|
+
toJSON(): unknown {
|
|
164
|
+
return {
|
|
165
|
+
_id: "@effect/ai/AiToolkit",
|
|
166
|
+
tools: [...HashMap.values(this.tools)].map((tool) => tool._tag)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
toString(): string {
|
|
170
|
+
return Inspectable.format(this)
|
|
171
|
+
}
|
|
172
|
+
[Inspectable.NodeInspectSymbol](): string {
|
|
173
|
+
return Inspectable.format(this)
|
|
174
|
+
}
|
|
175
|
+
pipe() {
|
|
176
|
+
return pipeArguments(this, arguments)
|
|
177
|
+
}
|
|
178
|
+
add<S extends Tool.AnySchema>(tool: S): AiToolkit<Tools | S> {
|
|
179
|
+
return new AiToolkitImpl(HashMap.set(this.tools, tool._tag, tool as any)) as any
|
|
180
|
+
}
|
|
181
|
+
addAll<ToAdd extends ReadonlyArray<Tool.AnySchema>>(...tools: ToAdd): AiToolkit<Tools | ToAdd[number]> {
|
|
182
|
+
let map = this.tools
|
|
183
|
+
for (const tool of tools) {
|
|
184
|
+
map = HashMap.set(map, tool._tag, tool as any)
|
|
185
|
+
}
|
|
186
|
+
return new AiToolkitImpl(map as any)
|
|
187
|
+
}
|
|
188
|
+
concat<T extends Tool.AnySchema>(that: AiToolkit<T>): AiToolkit<Tools | T> {
|
|
189
|
+
return new AiToolkitImpl(HashMap.union(this.tools, that.tools))
|
|
190
|
+
}
|
|
191
|
+
implement<R, EX = never, RX = never>(
|
|
192
|
+
f: (
|
|
193
|
+
handlers: Handlers<Tools>
|
|
194
|
+
) => Handlers<never, R> | Effect.Effect<Handlers<never, R>, EX, RX>
|
|
195
|
+
): Layer.Layer<Tool.ServiceFromTag<Tools["_tag"]> | Registry, EX, R | RX> {
|
|
196
|
+
return registerHandlers(this as any, f as any).pipe(Layer.effectDiscard, Layer.provideMerge(Registry.Live))
|
|
197
|
+
}
|
|
198
|
+
implementScoped<R, EX = never, RX = never>(
|
|
199
|
+
f: (
|
|
200
|
+
handlers: Handlers<Tools>
|
|
201
|
+
) => Handlers<never, R> | Effect.Effect<Handlers<never, R>, EX, RX>
|
|
202
|
+
): Layer.Layer<Tool.ServiceFromTag<Tools["_tag"]> | Registry, EX, Exclude<R | RX, Scope>> {
|
|
203
|
+
return registerHandlers(this as any, f as any).pipe(Layer.scopedDiscard, Layer.provideMerge(Registry.Live))
|
|
204
|
+
}
|
|
205
|
+
commit(): Effect.Effect<Handlers<Tools>, never, Tool.ServiceFromTag<Tools["_tag"]> | Registry> {
|
|
206
|
+
return Effect.map(Registry, (map) => {
|
|
207
|
+
let handlers = HashMap.empty<string, Tool.HandlerAny>()
|
|
208
|
+
for (const [tag, tool] of this.tools) {
|
|
209
|
+
handlers = HashMap.set(handlers, tag, map.get(tool)!)
|
|
210
|
+
}
|
|
211
|
+
return new HandlersImpl(this as any, handlers)
|
|
212
|
+
}) as any
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const registerHandlers = (
|
|
217
|
+
toolkit: AiToolkit<any>,
|
|
218
|
+
f: (handlers: Handlers<any, any>) => Handlers<any, any> | Effect.Effect<Handlers<any, any>>
|
|
219
|
+
) =>
|
|
220
|
+
Effect.context<any>().pipe(
|
|
221
|
+
Effect.bindTo("context"),
|
|
222
|
+
Effect.bind("handlers", () => {
|
|
223
|
+
const handlers = f(HandlersImpl.fromToolkit(toolkit))
|
|
224
|
+
return Effect.isEffect(handlers) ? handlers : Effect.succeed(handlers)
|
|
225
|
+
}),
|
|
226
|
+
Effect.tap(({ context, handlers }) => {
|
|
227
|
+
const registry = Context.unsafeGet(context, Registry)
|
|
228
|
+
for (const [tag, handler] of handlers.handlers) {
|
|
229
|
+
const tool = HashMap.unsafeGet(handlers.toolkit.tools, tag)
|
|
230
|
+
registry.set(tool, function(params: any) {
|
|
231
|
+
return Effect.withSpan(
|
|
232
|
+
Effect.mapInputContext(handler(params), (input) => Context.merge(input, context)),
|
|
233
|
+
"AiToolkit.handler",
|
|
234
|
+
{
|
|
235
|
+
captureStackTrace: false,
|
|
236
|
+
attributes: {
|
|
237
|
+
tool: tag,
|
|
238
|
+
parameters: params
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
)
|
|
242
|
+
})
|
|
243
|
+
}
|
|
244
|
+
})
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* @since 1.0.0
|
|
249
|
+
* @category constructors
|
|
250
|
+
*/
|
|
251
|
+
export const empty: AiToolkit<never> = new AiToolkitImpl(HashMap.empty())
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* @since 1.0.0
|
|
255
|
+
* @category handlers
|
|
256
|
+
*/
|
|
257
|
+
export const HandlersTypeId: unique symbol = Symbol.for("@effect/ai/AiToolkit/Handlers")
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* @since 1.0.0
|
|
261
|
+
* @category handlers
|
|
262
|
+
*/
|
|
263
|
+
export type HandlersTypeId = typeof HandlersTypeId
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* @since 1.0.0
|
|
267
|
+
* @category handlers
|
|
268
|
+
*/
|
|
269
|
+
export interface Handlers<in out Tools extends Tool.AnySchema, R = never> {
|
|
270
|
+
readonly [HandlersTypeId]: Handlers.Variance<Tools>
|
|
271
|
+
readonly toolkit: AiToolkit<Tools>
|
|
272
|
+
readonly handlers: HashMap.HashMap<string, Tool.Handler<any, R>>
|
|
273
|
+
readonly handle: <Tag extends Types.Tags<Tools>, RH>(
|
|
274
|
+
tag: Tag,
|
|
275
|
+
f: Tool.Handler<Types.ExtractTag<Tools, Tag>, RH>
|
|
276
|
+
) => Handlers<Types.ExcludeTag<Tools, Tag>, R | RH | Tool.Context<Types.ExtractTag<Tools, Tag>>>
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* @since 1.0.0
|
|
281
|
+
* @category handlers
|
|
282
|
+
*/
|
|
283
|
+
export declare namespace Handlers {
|
|
284
|
+
/**
|
|
285
|
+
* @since 1.0.0
|
|
286
|
+
* @category handlers
|
|
287
|
+
*/
|
|
288
|
+
export interface Variance<Tools extends Tool.AnySchema> {
|
|
289
|
+
readonly _Tools: Types.Invariant<Tools>
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const handlersVariance = {
|
|
294
|
+
_Tools: identity
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
class HandlersImpl<Tools extends Tool.AnySchema, R = never> implements Handlers<Tools, R> {
|
|
298
|
+
readonly [HandlersTypeId]: Handlers.Variance<Tools>
|
|
299
|
+
constructor(
|
|
300
|
+
readonly toolkit: AiToolkit<Tools>,
|
|
301
|
+
readonly handlers: HashMap.HashMap<string, Tool.Handler<any, R>>
|
|
302
|
+
) {
|
|
303
|
+
this[HandlersTypeId] = handlersVariance
|
|
304
|
+
}
|
|
305
|
+
static fromToolkit<Tools extends Tool.AnySchema>(toolkit: AiToolkit<Tools>): Handlers<Tools> {
|
|
306
|
+
return new HandlersImpl(toolkit, HashMap.empty())
|
|
307
|
+
}
|
|
308
|
+
handle<Tag extends Types.Tags<Tools>, RH>(
|
|
309
|
+
tag: Tag,
|
|
310
|
+
f: Tool.Handler<Types.ExtractTag<Tools, Tag>, RH>
|
|
311
|
+
): Handlers<Types.ExcludeTag<Tools, Tag>, R | RH | Tool.Context<Types.ExtractTag<Tools, Tag>>> {
|
|
312
|
+
return new HandlersImpl(this.toolkit as any, HashMap.set(this.handlers, tag, f as any))
|
|
313
|
+
}
|
|
314
|
+
}
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @since 1.0.0
|
|
3
|
+
*/
|
|
4
|
+
import * as JsonSchema from "@effect/platform/OpenApiJsonSchema"
|
|
5
|
+
import * as AST from "@effect/schema/AST"
|
|
6
|
+
import * as Schema from "@effect/schema/Schema"
|
|
7
|
+
import * as Chunk from "effect/Chunk"
|
|
8
|
+
import * as Context from "effect/Context"
|
|
9
|
+
import * as Effect from "effect/Effect"
|
|
10
|
+
import * as HashMap from "effect/HashMap"
|
|
11
|
+
import * as Option from "effect/Option"
|
|
12
|
+
import * as Stream from "effect/Stream"
|
|
13
|
+
import type { Concurrency } from "effect/Types"
|
|
14
|
+
import { AiError } from "./AiError.js"
|
|
15
|
+
import type { Message } from "./AiInput.js"
|
|
16
|
+
import * as AiInput from "./AiInput.js"
|
|
17
|
+
import type { AiResponse, ToolCallId, ToolCallPart } from "./AiResponse.js"
|
|
18
|
+
import { WithResolved } from "./AiResponse.js"
|
|
19
|
+
import type * as AiToolkit from "./AiToolkit.js"
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @since 1.0.0
|
|
23
|
+
* @category tags
|
|
24
|
+
*/
|
|
25
|
+
export class Completions extends Context.Tag("@effect/ai/Completions")<
|
|
26
|
+
Completions,
|
|
27
|
+
Completions.Service
|
|
28
|
+
>() {}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @since 1.0.0
|
|
32
|
+
* @category models
|
|
33
|
+
*/
|
|
34
|
+
export declare namespace Completions {
|
|
35
|
+
/**
|
|
36
|
+
* @since 1.0.0
|
|
37
|
+
* @models
|
|
38
|
+
*/
|
|
39
|
+
export interface StructuredSchema<A, I, R> extends Schema.Schema<A, I, R> {
|
|
40
|
+
readonly _tag?: string
|
|
41
|
+
readonly identifier: string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @since 1.0.0
|
|
46
|
+
* @models
|
|
47
|
+
*/
|
|
48
|
+
export interface Service {
|
|
49
|
+
readonly create: (input: AiInput.Input) => Effect.Effect<AiResponse, AiError>
|
|
50
|
+
readonly stream: (input: AiInput.Input) => Stream.Stream<AiResponse, AiError>
|
|
51
|
+
readonly structured: <A, I, R>(
|
|
52
|
+
options: {
|
|
53
|
+
readonly input: AiInput.Input
|
|
54
|
+
readonly schema: StructuredSchema<A, I, R>
|
|
55
|
+
}
|
|
56
|
+
) => Effect.Effect<WithResolved<A>, AiError, R>
|
|
57
|
+
readonly toolkit: <Tools extends AiToolkit.Tool.AnySchema>(
|
|
58
|
+
options: {
|
|
59
|
+
readonly input: AiInput.Input
|
|
60
|
+
readonly tools: AiToolkit.Handlers<Tools>
|
|
61
|
+
readonly required?: Tools["_tag"] | boolean | undefined
|
|
62
|
+
readonly concurrency?: Concurrency | undefined
|
|
63
|
+
}
|
|
64
|
+
) => Effect.Effect<
|
|
65
|
+
WithResolved<AiToolkit.Tool.Success<Tools>>,
|
|
66
|
+
AiError | AiToolkit.Tool.Failure<Tools>,
|
|
67
|
+
AiToolkit.Tool.Context<Tools>
|
|
68
|
+
>
|
|
69
|
+
readonly toolkitStream: <Tools extends AiToolkit.Tool.AnySchema>(
|
|
70
|
+
options: {
|
|
71
|
+
readonly input: AiInput.Input
|
|
72
|
+
readonly tools: AiToolkit.Handlers<Tools>
|
|
73
|
+
readonly required?: Tools["_tag"] | boolean | undefined
|
|
74
|
+
readonly concurrency?: Concurrency | undefined
|
|
75
|
+
}
|
|
76
|
+
) => Stream.Stream<
|
|
77
|
+
WithResolved<AiToolkit.Tool.Success<Tools>>,
|
|
78
|
+
AiError | AiToolkit.Tool.Failure<Tools>,
|
|
79
|
+
AiToolkit.Tool.Context<Tools>
|
|
80
|
+
>
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const constEmptyMap = new Map<never, never>()
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @since 1.0.0
|
|
88
|
+
* @category models
|
|
89
|
+
*/
|
|
90
|
+
export interface CompletionOptions {
|
|
91
|
+
readonly system: Option.Option<string>
|
|
92
|
+
readonly input: Chunk.NonEmptyChunk<Message>
|
|
93
|
+
readonly tools: Array<{
|
|
94
|
+
readonly name: string
|
|
95
|
+
readonly description: string
|
|
96
|
+
readonly parameters: JsonSchema.JsonSchema
|
|
97
|
+
}>
|
|
98
|
+
readonly required: boolean | string
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* @since 1.0.0
|
|
103
|
+
* @category constructors
|
|
104
|
+
*/
|
|
105
|
+
export const make = (options: {
|
|
106
|
+
readonly create: (options: {
|
|
107
|
+
readonly system: Option.Option<string>
|
|
108
|
+
readonly input: Chunk.NonEmptyChunk<Message>
|
|
109
|
+
readonly tools: Array<{
|
|
110
|
+
readonly name: string
|
|
111
|
+
readonly description: string
|
|
112
|
+
readonly parameters: JsonSchema.JsonSchema
|
|
113
|
+
}>
|
|
114
|
+
readonly required: boolean | string
|
|
115
|
+
}) => Effect.Effect<AiResponse, AiError>
|
|
116
|
+
readonly stream: (options: {
|
|
117
|
+
readonly system: Option.Option<string>
|
|
118
|
+
readonly input: Chunk.NonEmptyChunk<Message>
|
|
119
|
+
readonly tools: Array<{
|
|
120
|
+
readonly name: string
|
|
121
|
+
readonly description: string
|
|
122
|
+
readonly parameters: JsonSchema.JsonSchema
|
|
123
|
+
}>
|
|
124
|
+
readonly required: boolean | string
|
|
125
|
+
}) => Stream.Stream<AiResponse, AiError>
|
|
126
|
+
}): Effect.Effect<Completions.Service> =>
|
|
127
|
+
Effect.map(Effect.serviceOption(AiInput.SystemInstruction), (parentSystem) => {
|
|
128
|
+
return Completions.of({
|
|
129
|
+
create(input) {
|
|
130
|
+
return Effect.serviceOption(AiInput.SystemInstruction).pipe(
|
|
131
|
+
Effect.flatMap((system) =>
|
|
132
|
+
options.create({
|
|
133
|
+
input: AiInput.make(input) as Chunk.NonEmptyChunk<Message>,
|
|
134
|
+
system: Option.orElse(system, () => parentSystem),
|
|
135
|
+
tools: [],
|
|
136
|
+
required: false
|
|
137
|
+
})
|
|
138
|
+
),
|
|
139
|
+
Effect.withSpan("Completions.create", { captureStackTrace: false })
|
|
140
|
+
)
|
|
141
|
+
},
|
|
142
|
+
stream(input_) {
|
|
143
|
+
const input = AiInput.make(input_)
|
|
144
|
+
return Effect.serviceOption(AiInput.SystemInstruction).pipe(
|
|
145
|
+
Effect.map((system) =>
|
|
146
|
+
options.stream({
|
|
147
|
+
input: input as Chunk.NonEmptyChunk<Message>,
|
|
148
|
+
system: Option.orElse(system, () => parentSystem),
|
|
149
|
+
tools: [],
|
|
150
|
+
required: false
|
|
151
|
+
})
|
|
152
|
+
),
|
|
153
|
+
Stream.unwrap,
|
|
154
|
+
Stream.withSpan("Completions.stream", { captureStackTrace: false })
|
|
155
|
+
)
|
|
156
|
+
},
|
|
157
|
+
structured(opts) {
|
|
158
|
+
const input = AiInput.make(opts.input)
|
|
159
|
+
const schema = opts.schema
|
|
160
|
+
const decode = Schema.decodeUnknown(schema)
|
|
161
|
+
const toolId = schema._tag ?? schema.identifier
|
|
162
|
+
return Effect.serviceOption(AiInput.SystemInstruction).pipe(
|
|
163
|
+
Effect.flatMap((system) =>
|
|
164
|
+
options.create({
|
|
165
|
+
input: input as Chunk.NonEmptyChunk<Message>,
|
|
166
|
+
system: Option.orElse(system, () => parentSystem),
|
|
167
|
+
tools: [convertTool(schema)],
|
|
168
|
+
required: true
|
|
169
|
+
})
|
|
170
|
+
),
|
|
171
|
+
Effect.flatMap((response) =>
|
|
172
|
+
Chunk.findFirst(
|
|
173
|
+
response.parts,
|
|
174
|
+
(part): part is ToolCallPart => part._tag === "ToolCall" && part.name === toolId
|
|
175
|
+
).pipe(
|
|
176
|
+
Option.match({
|
|
177
|
+
onNone: () =>
|
|
178
|
+
Effect.fail(
|
|
179
|
+
new AiError({
|
|
180
|
+
module: "Completions",
|
|
181
|
+
method: "structured",
|
|
182
|
+
description: `Tool call '${toolId}' not found in response`
|
|
183
|
+
})
|
|
184
|
+
),
|
|
185
|
+
onSome: (toolCall) =>
|
|
186
|
+
Effect.matchEffect(decode(toolCall.params), {
|
|
187
|
+
onFailure: (cause) =>
|
|
188
|
+
new AiError({
|
|
189
|
+
module: "Completions",
|
|
190
|
+
method: "structured",
|
|
191
|
+
description: `Failed to decode tool call '${toolId}' parameters`,
|
|
192
|
+
cause
|
|
193
|
+
}),
|
|
194
|
+
onSuccess: (resolved) =>
|
|
195
|
+
Effect.succeed(
|
|
196
|
+
new WithResolved({
|
|
197
|
+
response,
|
|
198
|
+
resolved: new Map([[toolCall.id, resolved]]),
|
|
199
|
+
encoded: new Map([[toolCall.id, toolCall.params]])
|
|
200
|
+
})
|
|
201
|
+
)
|
|
202
|
+
})
|
|
203
|
+
}),
|
|
204
|
+
Effect.withSpan("Completions.structured", {
|
|
205
|
+
attributes: { tool: toolId },
|
|
206
|
+
captureStackTrace: false
|
|
207
|
+
})
|
|
208
|
+
)
|
|
209
|
+
)
|
|
210
|
+
)
|
|
211
|
+
},
|
|
212
|
+
toolkit({ concurrency, input: inputInput, required = false, tools }) {
|
|
213
|
+
const input = AiInput.make(inputInput)
|
|
214
|
+
const toolArr: Array<{ name: string; description: string; parameters: JsonSchema.JsonSchema }> = []
|
|
215
|
+
for (const [, tool] of tools.toolkit.tools) {
|
|
216
|
+
toolArr.push(convertTool(tool as any))
|
|
217
|
+
}
|
|
218
|
+
return Effect.serviceOption(AiInput.SystemInstruction).pipe(
|
|
219
|
+
Effect.flatMap((system) =>
|
|
220
|
+
options.create({
|
|
221
|
+
input: input as Chunk.NonEmptyChunk<Message>,
|
|
222
|
+
system: Option.orElse(system, () => parentSystem),
|
|
223
|
+
tools: toolArr,
|
|
224
|
+
required: required as any
|
|
225
|
+
})
|
|
226
|
+
),
|
|
227
|
+
Effect.flatMap((response) => resolveParts({ response, tools, concurrency, method: "toolkit" })),
|
|
228
|
+
Effect.withSpan("Completions.toolkit", {
|
|
229
|
+
captureStackTrace: false,
|
|
230
|
+
attributes: {
|
|
231
|
+
concurrency,
|
|
232
|
+
required
|
|
233
|
+
}
|
|
234
|
+
})
|
|
235
|
+
) as any
|
|
236
|
+
},
|
|
237
|
+
toolkitStream({ concurrency, input, required = false, tools }) {
|
|
238
|
+
const toolArr: Array<{ name: string; description: string; parameters: JsonSchema.JsonSchema }> = []
|
|
239
|
+
for (const [, tool] of tools.toolkit.tools) {
|
|
240
|
+
toolArr.push(convertTool(tool as any))
|
|
241
|
+
}
|
|
242
|
+
return Effect.serviceOption(AiInput.SystemInstruction).pipe(
|
|
243
|
+
Effect.map((system) =>
|
|
244
|
+
options.stream({
|
|
245
|
+
input: AiInput.make(input) as Chunk.NonEmptyChunk<Message>,
|
|
246
|
+
system: Option.orElse(system, () => parentSystem),
|
|
247
|
+
tools: toolArr,
|
|
248
|
+
required: required as any
|
|
249
|
+
})
|
|
250
|
+
),
|
|
251
|
+
Stream.unwrap,
|
|
252
|
+
Stream.mapEffect(
|
|
253
|
+
(chunk) => resolveParts({ response: chunk, tools, concurrency, method: "toolkitStream" }),
|
|
254
|
+
{ concurrency: "unbounded" }
|
|
255
|
+
),
|
|
256
|
+
Stream.withSpan("Completions.toolkitStream", {
|
|
257
|
+
captureStackTrace: false,
|
|
258
|
+
attributes: {
|
|
259
|
+
concurrency,
|
|
260
|
+
required
|
|
261
|
+
}
|
|
262
|
+
})
|
|
263
|
+
) as any
|
|
264
|
+
}
|
|
265
|
+
})
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
const convertTool = <A, I, R>(tool: Completions.StructuredSchema<A, I, R>) => ({
|
|
269
|
+
name: tool._tag ?? tool.identifier,
|
|
270
|
+
description: getDescription(tool.ast),
|
|
271
|
+
parameters: JsonSchema.make(tool)
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
const getDescription = (ast: AST.AST): string => {
|
|
275
|
+
const annotations = ast._tag === "Transformation" ?
|
|
276
|
+
{
|
|
277
|
+
...ast.to.annotations,
|
|
278
|
+
...ast.annotations
|
|
279
|
+
} :
|
|
280
|
+
ast.annotations
|
|
281
|
+
return AST.DescriptionAnnotationId in annotations ? annotations[AST.DescriptionAnnotationId] as string : ""
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const resolveParts = (
|
|
285
|
+
options: {
|
|
286
|
+
readonly response: AiResponse
|
|
287
|
+
readonly tools: AiToolkit.Handlers<any>
|
|
288
|
+
readonly concurrency: Concurrency | undefined
|
|
289
|
+
readonly method: string
|
|
290
|
+
}
|
|
291
|
+
) => {
|
|
292
|
+
const toolNames: Array<string> = []
|
|
293
|
+
const toolParts = Chunk.filter(
|
|
294
|
+
options.response.parts,
|
|
295
|
+
(part): part is ToolCallPart => {
|
|
296
|
+
if (part._tag === "ToolCall") {
|
|
297
|
+
toolNames.push(part.name)
|
|
298
|
+
return true
|
|
299
|
+
}
|
|
300
|
+
return false
|
|
301
|
+
}
|
|
302
|
+
)
|
|
303
|
+
if (Chunk.isEmpty(toolParts)) {
|
|
304
|
+
return Effect.succeed(
|
|
305
|
+
new WithResolved({
|
|
306
|
+
response: options.response,
|
|
307
|
+
resolved: constEmptyMap,
|
|
308
|
+
encoded: constEmptyMap
|
|
309
|
+
})
|
|
310
|
+
)
|
|
311
|
+
}
|
|
312
|
+
const resolved = new Map<ToolCallId, AiToolkit.Tool.Success<any>>()
|
|
313
|
+
const encoded = new Map<ToolCallId, unknown>()
|
|
314
|
+
return Effect.annotateCurrentSpan("toolCalls", toolNames).pipe(
|
|
315
|
+
Effect.zipRight(Effect.forEach(
|
|
316
|
+
toolParts,
|
|
317
|
+
(part) => {
|
|
318
|
+
const tool = HashMap.unsafeGet(options.tools.toolkit.tools, part.name)
|
|
319
|
+
const handler = HashMap.unsafeGet(options.tools.handlers, part.name)
|
|
320
|
+
const decodeParams = Schema.decodeUnknown(tool as any)
|
|
321
|
+
const encodeSuccess = Schema.encode(tool.success)
|
|
322
|
+
return decodeParams(part.params).pipe(
|
|
323
|
+
Effect.mapError((cause) =>
|
|
324
|
+
new AiError({
|
|
325
|
+
module: "Completions",
|
|
326
|
+
method: options.method,
|
|
327
|
+
description: `Failed to decode tool call '${part.name}' parameters`,
|
|
328
|
+
cause
|
|
329
|
+
})
|
|
330
|
+
),
|
|
331
|
+
Effect.flatMap(handler),
|
|
332
|
+
Effect.tap((value) => {
|
|
333
|
+
return encodeSuccess(value).pipe(
|
|
334
|
+
Effect.mapError((cause) =>
|
|
335
|
+
new AiError({
|
|
336
|
+
module: "Completions",
|
|
337
|
+
method: options.method,
|
|
338
|
+
description: `Failed to encode tool call '${part.name}' result`,
|
|
339
|
+
cause
|
|
340
|
+
})
|
|
341
|
+
),
|
|
342
|
+
Effect.map((encodedValue) => {
|
|
343
|
+
resolved.set(part.id, value)
|
|
344
|
+
encoded.set(part.id, encodedValue)
|
|
345
|
+
})
|
|
346
|
+
)
|
|
347
|
+
})
|
|
348
|
+
)
|
|
349
|
+
},
|
|
350
|
+
{ concurrency: options.concurrency, discard: true }
|
|
351
|
+
)),
|
|
352
|
+
Effect.as(new WithResolved({ response: options.response, resolved, encoded }))
|
|
353
|
+
)
|
|
354
|
+
}
|