@effect/ai
Advanced tools
Comparing version 0.8.1 to 0.8.2
@@ -32,108 +32,95 @@ "use strict"; | ||
exports.AiChat = AiChat; | ||
const fromInput = input => Ref.make(AiInput.make(input)).pipe(Effect.bindTo("historyRef"), Effect.bind("completions", () => _Completions.Completions), Effect.map(({ | ||
completions, | ||
historyRef | ||
}) => new AiChatImpl(historyRef, completions))); | ||
exports.fromInput = fromInput; | ||
class AiChatImpl { | ||
historyRef; | ||
completions; | ||
semaphore = /*#__PURE__*/Effect.unsafeMakeSemaphore(1); | ||
constructor(historyRef, completions) { | ||
this.historyRef = historyRef; | ||
this.completions = completions; | ||
} | ||
get history() { | ||
return Ref.get(this.historyRef); | ||
} | ||
get export() { | ||
return Ref.get(this.historyRef).pipe(Effect.flatMap(Schema.encode(AiInput.Schema)), Effect.orDie); | ||
} | ||
get exportJson() { | ||
return Ref.get(this.historyRef).pipe(Effect.flatMap(Schema.encode(AiInput.SchemaJson)), Effect.orDie); | ||
} | ||
send(input) { | ||
const newParts = AiInput.make(input); | ||
return Ref.get(this.historyRef).pipe(Effect.flatMap(parts => { | ||
const allParts = Chunk.appendAll(parts, newParts); | ||
return this.completions.create(allParts).pipe(Effect.tap(response => { | ||
const responseParts = AiInput.make(response); | ||
return Ref.set(this.historyRef, Chunk.appendAll(allParts, responseParts)); | ||
const fromInput = exports.fromInput = /*#__PURE__*/Effect.fnUntraced(function* (input) { | ||
const completions = yield* _Completions.Completions; | ||
const history = yield* Ref.make(AiInput.make(input)); | ||
const semaphore = yield* Effect.makeSemaphore(1); | ||
return AiChat.of({ | ||
history: Ref.get(history), | ||
export: Ref.get(history).pipe(Effect.flatMap(Schema.encode(AiInput.Schema)), Effect.orDie), | ||
exportJson: Ref.get(history).pipe(Effect.flatMap(Schema.encode(AiInput.SchemaJson)), Effect.orDie), | ||
send(input) { | ||
const newParts = AiInput.make(input); | ||
return Ref.get(history).pipe(Effect.flatMap(parts => { | ||
const allParts = Chunk.appendAll(parts, newParts); | ||
return completions.create(allParts).pipe(Effect.tap(response => { | ||
const responseParts = AiInput.make(response); | ||
return Ref.set(history, Chunk.appendAll(allParts, responseParts)); | ||
})); | ||
}), semaphore.withPermits(1), Effect.withSpan("AiChat.send", { | ||
attributes: { | ||
input | ||
}, | ||
captureStackTrace: false | ||
})); | ||
}), this.semaphore.withPermits(1), Effect.withSpan("AiChat.send", { | ||
attributes: { | ||
input | ||
}, | ||
captureStackTrace: false | ||
})); | ||
} | ||
stream(input) { | ||
return Stream.suspend(() => { | ||
let combined = _AiResponse.AiResponse.empty; | ||
return Stream.fromChannel(Channel.acquireUseRelease(this.semaphore.take(1).pipe(Effect.zipRight(Ref.get(this.historyRef)), Effect.map(Chunk.appendAll(AiInput.make(input)))), parts => this.completions.stream(parts).pipe(Stream.map(chunk => { | ||
combined = combined.concat(chunk); | ||
return chunk; | ||
}), Stream.toChannel), parts => Effect.zipRight(Ref.set(this.historyRef, Chunk.appendAll(parts, AiInput.make(combined))), this.semaphore.release(1)))); | ||
}).pipe(Stream.withSpan("AiChat.stream", { | ||
attributes: { | ||
input | ||
}, | ||
captureStackTrace: false | ||
})); | ||
} | ||
structured(schema, input) { | ||
const newParts = AiInput.make(input); | ||
return Ref.get(this.historyRef).pipe(Effect.flatMap(parts => { | ||
const allParts = Chunk.appendAll(parts, newParts); | ||
return this.completions.structured({ | ||
input: allParts, | ||
schema | ||
}).pipe(Effect.flatMap(response => { | ||
const responseParts = AiInput.make(response); | ||
return Effect.as(Ref.set(this.historyRef, Chunk.appendAll(allParts, responseParts)), response.unsafeValue); | ||
}, | ||
stream(input) { | ||
return Stream.suspend(() => { | ||
let combined = _AiResponse.AiResponse.empty; | ||
return Stream.fromChannel(Channel.acquireUseRelease(semaphore.take(1).pipe(Effect.zipRight(Ref.get(history)), Effect.map(Chunk.appendAll(AiInput.make(input)))), parts => completions.stream(parts).pipe(Stream.map(chunk => { | ||
combined = combined.concat(chunk); | ||
return chunk; | ||
}), Stream.toChannel), parts => Effect.zipRight(Ref.set(history, Chunk.appendAll(parts, AiInput.make(combined))), semaphore.release(1)))); | ||
}).pipe(Stream.withSpan("AiChat.stream", { | ||
attributes: { | ||
input | ||
}, | ||
captureStackTrace: false | ||
})); | ||
}), this.semaphore.withPermits(1), Effect.withSpan("AiChat.structured", { | ||
attributes: { | ||
input, | ||
schema: schema._tag ?? schema.identifier | ||
}, | ||
captureStackTrace: false | ||
})); | ||
} | ||
toolkit(options) { | ||
const newParts = AiInput.make(options.input); | ||
return Ref.get(this.historyRef).pipe(Effect.flatMap(parts => { | ||
const allParts = Chunk.appendAll(parts, newParts); | ||
return this.completions.toolkit({ | ||
...options, | ||
input: allParts | ||
}).pipe(Effect.tap(response => { | ||
const responseParts = AiInput.make(response); | ||
return Ref.set(this.historyRef, Chunk.appendAll(allParts, responseParts)); | ||
}, | ||
structured(options) { | ||
const newParts = AiInput.make(options.input); | ||
return Ref.get(history).pipe(Effect.flatMap(parts => { | ||
const allParts = Chunk.appendAll(parts, newParts); | ||
return completions.structured({ | ||
...options, | ||
input: allParts | ||
}).pipe(Effect.flatMap(response => { | ||
const responseParts = AiInput.make(response); | ||
return Effect.as(Ref.set(history, Chunk.appendAll(allParts, responseParts)), response.unsafeValue); | ||
})); | ||
}), semaphore.withPermits(1), Effect.withSpan("AiChat.structured", { | ||
attributes: { | ||
input: options.input, | ||
schema: "toolCallId" in options ? options.toolCallId : "_tag" in options.schema ? options.schema._tag : options.schema.identifier | ||
}, | ||
captureStackTrace: false | ||
})); | ||
}), this.semaphore.withPermits(1), Effect.withSpan("AiChat.toolkit", { | ||
attributes: { | ||
input: options.input | ||
}, | ||
captureStackTrace: false | ||
})); | ||
} | ||
toolkitStream(options) { | ||
return Stream.suspend(() => { | ||
let combined = _AiResponse.WithResolved.empty; | ||
return Stream.fromChannel(Channel.acquireUseRelease(this.semaphore.take(1).pipe(Effect.zipRight(Ref.get(this.historyRef)), Effect.map(Chunk.appendAll(AiInput.make(options.input)))), parts => this.completions.toolkitStream({ | ||
...options, | ||
input: parts | ||
}).pipe(Stream.map(chunk => { | ||
combined = combined.concat(chunk); | ||
return chunk; | ||
}), Stream.toChannel), parts => Effect.zipRight(Ref.set(this.historyRef, Chunk.appendAll(parts, AiInput.make(combined))), this.semaphore.release(1)))); | ||
}).pipe(Stream.withSpan("AiChat.toolkitStream", { | ||
attributes: { | ||
input: options.input | ||
}, | ||
captureStackTrace: false | ||
})); | ||
} | ||
} | ||
}, | ||
toolkit(options) { | ||
const newParts = AiInput.make(options.input); | ||
return Ref.get(history).pipe(Effect.flatMap(parts => { | ||
const allParts = Chunk.appendAll(parts, newParts); | ||
return completions.toolkit({ | ||
...options, | ||
input: allParts | ||
}).pipe(Effect.tap(response => { | ||
const responseParts = AiInput.make(response); | ||
return Ref.set(history, Chunk.appendAll(allParts, responseParts)); | ||
})); | ||
}), semaphore.withPermits(1), Effect.withSpan("AiChat.toolkit", { | ||
attributes: { | ||
input: options.input | ||
}, | ||
captureStackTrace: false | ||
})); | ||
}, | ||
toolkitStream(options) { | ||
return Stream.suspend(() => { | ||
let combined = _AiResponse.WithResolved.empty; | ||
return Stream.fromChannel(Channel.acquireUseRelease(semaphore.take(1).pipe(Effect.zipRight(Ref.get(history)), Effect.map(Chunk.appendAll(AiInput.make(options.input)))), parts => completions.toolkitStream({ | ||
...options, | ||
input: parts | ||
}).pipe(Stream.map(chunk => { | ||
combined = combined.concat(chunk); | ||
return chunk; | ||
}), Stream.toChannel), parts => Effect.zipRight(Ref.set(history, Chunk.appendAll(parts, AiInput.make(combined))), semaphore.release(1)))); | ||
}).pipe(Stream.withSpan("AiChat.toolkitStream", { | ||
attributes: { | ||
input: options.input | ||
}, | ||
captureStackTrace: false | ||
})); | ||
} | ||
}); | ||
}); | ||
/** | ||
@@ -140,0 +127,0 @@ * @since 1.0.0 |
@@ -39,32 +39,39 @@ "use strict"; | ||
create(input) { | ||
return Effect.serviceOption(AiInput.SystemInstruction).pipe(Effect.flatMap(system => options.create({ | ||
return Effect.useSpan("Completions.create", { | ||
captureStackTrace: false | ||
}, span => Effect.serviceOption(AiInput.SystemInstruction).pipe(Effect.flatMap(system => options.create({ | ||
input: AiInput.make(input), | ||
system: Option.orElse(system, () => parentSystem), | ||
tools: [], | ||
required: false | ||
})), Effect.withSpan("Completions.create", { | ||
captureStackTrace: false | ||
})); | ||
required: false, | ||
span | ||
})))); | ||
}, | ||
stream(input_) { | ||
const input = AiInput.make(input_); | ||
return Effect.serviceOption(AiInput.SystemInstruction).pipe(Effect.map(system => options.stream({ | ||
return Effect.makeSpanScoped("Completions.stream", { | ||
captureStackTrace: false | ||
}).pipe(Effect.zip(Effect.serviceOption(AiInput.SystemInstruction)), Effect.map(([span, system]) => options.stream({ | ||
input: input, | ||
system: Option.orElse(system, () => parentSystem), | ||
tools: [], | ||
required: false | ||
})), Stream.unwrap, Stream.withSpan("Completions.stream", { | ||
captureStackTrace: false | ||
})); | ||
required: false, | ||
span | ||
})), Stream.unwrapScoped); | ||
}, | ||
structured(opts) { | ||
const input = AiInput.make(opts.input); | ||
const schema = opts.schema; | ||
const decode = Schema.decodeUnknown(schema); | ||
const toolId = schema._tag ?? schema.identifier; | ||
return Effect.serviceOption(AiInput.SystemInstruction).pipe(Effect.flatMap(system => options.create({ | ||
const decode = Schema.decodeUnknown(opts.schema); | ||
const toolId = "toolCallId" in opts ? opts.toolCallId : "_tag" in opts.schema ? opts.schema._tag : opts.schema.identifier; | ||
return Effect.useSpan("Completions.structured", { | ||
attributes: { | ||
toolId | ||
}, | ||
captureStackTrace: false | ||
}, span => Effect.serviceOption(AiInput.SystemInstruction).pipe(Effect.flatMap(system => options.create({ | ||
input: input, | ||
system: Option.orElse(system, () => parentSystem), | ||
tools: [convertTool(schema, true)], | ||
required: true | ||
tools: [convertTool(toolId, opts.schema, true)], | ||
required: true, | ||
span | ||
})), Effect.flatMap(response => Chunk.findFirst(response.parts, part => part._tag === "ToolCall" && part.name === toolId).pipe(Option.match({ | ||
@@ -89,8 +96,3 @@ onNone: () => Effect.fail(new _AiError.AiError({ | ||
}) | ||
}), Effect.withSpan("Completions.structured", { | ||
attributes: { | ||
tool: toolId | ||
}, | ||
captureStackTrace: false | ||
})))); | ||
}))))); | ||
}, | ||
@@ -106,9 +108,16 @@ toolkit({ | ||
for (const [, tool] of tools.toolkit.tools) { | ||
toolArr.push(convertTool(tool)); | ||
toolArr.push(convertTool(tool._tag, tool)); | ||
} | ||
return Effect.serviceOption(AiInput.SystemInstruction).pipe(Effect.flatMap(system => options.create({ | ||
return Effect.useSpan("Completions.toolkit", { | ||
attributes: { | ||
concurrency, | ||
required | ||
}, | ||
captureStackTrace: false | ||
}, span => Effect.serviceOption(AiInput.SystemInstruction).pipe(Effect.flatMap(system => options.create({ | ||
input: input, | ||
system: Option.orElse(system, () => parentSystem), | ||
tools: toolArr, | ||
required: required | ||
required: required, | ||
span | ||
})), Effect.flatMap(response => resolveParts({ | ||
@@ -119,9 +128,3 @@ response, | ||
method: "toolkit" | ||
})), Effect.withSpan("Completions.toolkit", { | ||
captureStackTrace: false, | ||
attributes: { | ||
concurrency, | ||
required | ||
} | ||
})); | ||
})))); | ||
}, | ||
@@ -136,10 +139,17 @@ toolkitStream({ | ||
for (const [, tool] of tools.toolkit.tools) { | ||
toolArr.push(convertTool(tool)); | ||
toolArr.push(convertTool(tool._tag, tool)); | ||
} | ||
return Effect.serviceOption(AiInput.SystemInstruction).pipe(Effect.map(system => options.stream({ | ||
return Effect.makeSpanScoped("Completions.stream", { | ||
captureStackTrace: false, | ||
attributes: { | ||
required, | ||
concurrency | ||
} | ||
}).pipe(Effect.zip(Effect.serviceOption(AiInput.SystemInstruction)), Effect.map(([span, system]) => options.stream({ | ||
input: AiInput.make(input), | ||
system: Option.orElse(system, () => parentSystem), | ||
tools: toolArr, | ||
required: required | ||
})), Stream.unwrap, Stream.mapEffect(chunk => resolveParts({ | ||
required: required, | ||
span | ||
})), Stream.unwrapScoped, Stream.mapEffect(chunk => resolveParts({ | ||
response: chunk, | ||
@@ -151,8 +161,2 @@ tools, | ||
concurrency: "unbounded" | ||
}), Stream.withSpan("Completions.toolkitStream", { | ||
captureStackTrace: false, | ||
attributes: { | ||
concurrency, | ||
required | ||
} | ||
})); | ||
@@ -163,6 +167,6 @@ } | ||
exports.make = make; | ||
const convertTool = (tool, structured = false) => ({ | ||
name: tool._tag ?? tool.identifier, | ||
description: getDescription(tool.ast), | ||
parameters: makeJsonSchema(tool.ast), | ||
const convertTool = (name, schema, structured = false) => ({ | ||
name, | ||
description: getDescription(schema.ast), | ||
parameters: makeJsonSchema(schema.ast), | ||
structured | ||
@@ -169,0 +173,0 @@ }); |
@@ -6,3 +6,3 @@ "use strict"; | ||
}); | ||
exports.Tokenizer = exports.Completions = exports.AiToolkit = exports.AiRole = exports.AiResponse = exports.AiInput = exports.AiError = exports.AiChat = void 0; | ||
exports.Tokenizer = exports.Embeddings = exports.Completions = exports.AiToolkit = exports.AiTelemetry = exports.AiRole = exports.AiResponse = exports.AiInput = exports.AiError = exports.AiChat = void 0; | ||
var _AiChat = _interopRequireWildcard(require("./AiChat.js")); | ||
@@ -18,2 +18,4 @@ exports.AiChat = _AiChat; | ||
exports.AiRole = _AiRole; | ||
var _AiTelemetry = _interopRequireWildcard(require("./AiTelemetry.js")); | ||
exports.AiTelemetry = _AiTelemetry; | ||
var _AiToolkit = _interopRequireWildcard(require("./AiToolkit.js")); | ||
@@ -23,2 +25,4 @@ exports.AiToolkit = _AiToolkit; | ||
exports.Completions = _Completions; | ||
var _Embeddings = _interopRequireWildcard(require("./Embeddings.js")); | ||
exports.Embeddings = _Embeddings; | ||
var _Tokenizer = _interopRequireWildcard(require("./Tokenizer.js")); | ||
@@ -25,0 +29,0 @@ exports.Tokenizer = _Tokenizer; |
import * as Effect from "effect/Effect"; | ||
import type { ParseError } from "effect/ParseResult"; | ||
import * as Schema from "effect/Schema"; | ||
import * as Stream from "effect/Stream"; | ||
@@ -34,3 +35,13 @@ import type { Concurrency } from "effect/Types"; | ||
readonly stream: (input: AiInput.Input) => Stream.Stream<AiResponse, AiError>; | ||
readonly structured: <A, I, R>(tool: Completions.StructuredSchema<A, I, R>, input: AiInput.Input) => Effect.Effect<A, AiError, R>; | ||
readonly structured: { | ||
<A, I, R>(options: { | ||
readonly input: AiInput.Input; | ||
readonly schema: Completions.StructuredSchema<A, I, R>; | ||
}): Effect.Effect<A, AiError, R>; | ||
<A, I, R>(options: { | ||
readonly input: AiInput.Input; | ||
readonly schema: Schema.Schema<A, I, R>; | ||
readonly toolCallId: string; | ||
}): Effect.Effect<A, AiError, R>; | ||
}; | ||
readonly toolkit: <Tools extends AiToolkit.Tool.AnySchema>(options: { | ||
@@ -37,0 +48,0 @@ readonly input: AiInput.Input; |
@@ -11,2 +11,3 @@ /** | ||
import * as Stream from "effect/Stream"; | ||
import type { Span } from "effect/Tracer"; | ||
import type { Concurrency } from "effect/Types"; | ||
@@ -33,6 +34,17 @@ import { AiError } from "./AiError.js"; | ||
* @since 1.0.0 | ||
* @models | ||
* @category models | ||
*/ | ||
interface StructuredSchema<A, I, R> extends Schema.Schema<A, I, R> { | ||
readonly _tag?: string; | ||
type StructuredSchema<A, I, R> = TaggedSchema<A, I, R> | IdentifiedSchema<A, I, R>; | ||
/** | ||
* @since 1.0.0 | ||
* @category models | ||
*/ | ||
interface TaggedSchema<A, I, R> extends Schema.Schema<A, I, R> { | ||
readonly _tag: string; | ||
} | ||
/** | ||
* @since 1.0.0 | ||
* @category models | ||
*/ | ||
interface IdentifiedSchema<A, I, R> extends Schema.Schema<A, I, R> { | ||
readonly identifier: string; | ||
@@ -42,3 +54,3 @@ } | ||
* @since 1.0.0 | ||
* @models | ||
* @category models | ||
*/ | ||
@@ -48,6 +60,13 @@ interface Service { | ||
readonly stream: (input: AiInput.Input) => Stream.Stream<AiResponse, AiError>; | ||
readonly structured: <A, I, R>(options: { | ||
readonly input: AiInput.Input; | ||
readonly schema: StructuredSchema<A, I, R>; | ||
}) => Effect.Effect<WithResolved<A>, AiError, R>; | ||
readonly structured: { | ||
<A, I, R>(options: { | ||
readonly input: AiInput.Input; | ||
readonly schema: StructuredSchema<A, I, R>; | ||
}): Effect.Effect<WithResolved<A>, AiError, R>; | ||
<A, I, R>(options: { | ||
readonly input: AiInput.Input; | ||
readonly schema: Schema.Schema<A, I, R>; | ||
readonly toolCallId: string; | ||
}): Effect.Effect<WithResolved<A>, AiError, R>; | ||
}; | ||
readonly toolkit: <Tools extends AiToolkit.Tool.AnySchema>(options: { | ||
@@ -97,2 +116,3 @@ readonly input: AiInput.Input; | ||
readonly required: boolean | string; | ||
readonly span: Span; | ||
}) => Effect.Effect<AiResponse, AiError>; | ||
@@ -109,2 +129,3 @@ readonly stream: (options: { | ||
readonly required: boolean | string; | ||
readonly span: Span; | ||
}) => Stream.Stream<AiResponse, AiError>; | ||
@@ -111,0 +132,0 @@ }) => Effect.Effect<Completions.Service>; |
@@ -24,2 +24,6 @@ /** | ||
*/ | ||
export * as AiTelemetry from "./AiTelemetry.js"; | ||
/** | ||
* @since 1.0.0 | ||
*/ | ||
export * as AiToolkit from "./AiToolkit.js"; | ||
@@ -33,3 +37,7 @@ /** | ||
*/ | ||
export * as Embeddings from "./Embeddings.js"; | ||
/** | ||
* @since 1.0.0 | ||
*/ | ||
export * as Tokenizer from "./Tokenizer.js"; | ||
//# sourceMappingURL=index.d.ts.map |
@@ -22,107 +22,95 @@ /** | ||
*/ | ||
export const fromInput = input => Ref.make(AiInput.make(input)).pipe(Effect.bindTo("historyRef"), Effect.bind("completions", () => Completions), Effect.map(({ | ||
completions, | ||
historyRef | ||
}) => new AiChatImpl(historyRef, completions))); | ||
class AiChatImpl { | ||
historyRef; | ||
completions; | ||
semaphore = /*#__PURE__*/Effect.unsafeMakeSemaphore(1); | ||
constructor(historyRef, completions) { | ||
this.historyRef = historyRef; | ||
this.completions = completions; | ||
} | ||
get history() { | ||
return Ref.get(this.historyRef); | ||
} | ||
get export() { | ||
return Ref.get(this.historyRef).pipe(Effect.flatMap(Schema.encode(AiInput.Schema)), Effect.orDie); | ||
} | ||
get exportJson() { | ||
return Ref.get(this.historyRef).pipe(Effect.flatMap(Schema.encode(AiInput.SchemaJson)), Effect.orDie); | ||
} | ||
send(input) { | ||
const newParts = AiInput.make(input); | ||
return Ref.get(this.historyRef).pipe(Effect.flatMap(parts => { | ||
const allParts = Chunk.appendAll(parts, newParts); | ||
return this.completions.create(allParts).pipe(Effect.tap(response => { | ||
const responseParts = AiInput.make(response); | ||
return Ref.set(this.historyRef, Chunk.appendAll(allParts, responseParts)); | ||
export const fromInput = /*#__PURE__*/Effect.fnUntraced(function* (input) { | ||
const completions = yield* Completions; | ||
const history = yield* Ref.make(AiInput.make(input)); | ||
const semaphore = yield* Effect.makeSemaphore(1); | ||
return AiChat.of({ | ||
history: Ref.get(history), | ||
export: Ref.get(history).pipe(Effect.flatMap(Schema.encode(AiInput.Schema)), Effect.orDie), | ||
exportJson: Ref.get(history).pipe(Effect.flatMap(Schema.encode(AiInput.SchemaJson)), Effect.orDie), | ||
send(input) { | ||
const newParts = AiInput.make(input); | ||
return Ref.get(history).pipe(Effect.flatMap(parts => { | ||
const allParts = Chunk.appendAll(parts, newParts); | ||
return completions.create(allParts).pipe(Effect.tap(response => { | ||
const responseParts = AiInput.make(response); | ||
return Ref.set(history, Chunk.appendAll(allParts, responseParts)); | ||
})); | ||
}), semaphore.withPermits(1), Effect.withSpan("AiChat.send", { | ||
attributes: { | ||
input | ||
}, | ||
captureStackTrace: false | ||
})); | ||
}), this.semaphore.withPermits(1), Effect.withSpan("AiChat.send", { | ||
attributes: { | ||
input | ||
}, | ||
captureStackTrace: false | ||
})); | ||
} | ||
stream(input) { | ||
return Stream.suspend(() => { | ||
let combined = AiResponse.empty; | ||
return Stream.fromChannel(Channel.acquireUseRelease(this.semaphore.take(1).pipe(Effect.zipRight(Ref.get(this.historyRef)), Effect.map(Chunk.appendAll(AiInput.make(input)))), parts => this.completions.stream(parts).pipe(Stream.map(chunk => { | ||
combined = combined.concat(chunk); | ||
return chunk; | ||
}), Stream.toChannel), parts => Effect.zipRight(Ref.set(this.historyRef, Chunk.appendAll(parts, AiInput.make(combined))), this.semaphore.release(1)))); | ||
}).pipe(Stream.withSpan("AiChat.stream", { | ||
attributes: { | ||
input | ||
}, | ||
captureStackTrace: false | ||
})); | ||
} | ||
structured(schema, input) { | ||
const newParts = AiInput.make(input); | ||
return Ref.get(this.historyRef).pipe(Effect.flatMap(parts => { | ||
const allParts = Chunk.appendAll(parts, newParts); | ||
return this.completions.structured({ | ||
input: allParts, | ||
schema | ||
}).pipe(Effect.flatMap(response => { | ||
const responseParts = AiInput.make(response); | ||
return Effect.as(Ref.set(this.historyRef, Chunk.appendAll(allParts, responseParts)), response.unsafeValue); | ||
}, | ||
stream(input) { | ||
return Stream.suspend(() => { | ||
let combined = AiResponse.empty; | ||
return Stream.fromChannel(Channel.acquireUseRelease(semaphore.take(1).pipe(Effect.zipRight(Ref.get(history)), Effect.map(Chunk.appendAll(AiInput.make(input)))), parts => completions.stream(parts).pipe(Stream.map(chunk => { | ||
combined = combined.concat(chunk); | ||
return chunk; | ||
}), Stream.toChannel), parts => Effect.zipRight(Ref.set(history, Chunk.appendAll(parts, AiInput.make(combined))), semaphore.release(1)))); | ||
}).pipe(Stream.withSpan("AiChat.stream", { | ||
attributes: { | ||
input | ||
}, | ||
captureStackTrace: false | ||
})); | ||
}), this.semaphore.withPermits(1), Effect.withSpan("AiChat.structured", { | ||
attributes: { | ||
input, | ||
schema: schema._tag ?? schema.identifier | ||
}, | ||
captureStackTrace: false | ||
})); | ||
} | ||
toolkit(options) { | ||
const newParts = AiInput.make(options.input); | ||
return Ref.get(this.historyRef).pipe(Effect.flatMap(parts => { | ||
const allParts = Chunk.appendAll(parts, newParts); | ||
return this.completions.toolkit({ | ||
...options, | ||
input: allParts | ||
}).pipe(Effect.tap(response => { | ||
const responseParts = AiInput.make(response); | ||
return Ref.set(this.historyRef, Chunk.appendAll(allParts, responseParts)); | ||
}, | ||
structured(options) { | ||
const newParts = AiInput.make(options.input); | ||
return Ref.get(history).pipe(Effect.flatMap(parts => { | ||
const allParts = Chunk.appendAll(parts, newParts); | ||
return completions.structured({ | ||
...options, | ||
input: allParts | ||
}).pipe(Effect.flatMap(response => { | ||
const responseParts = AiInput.make(response); | ||
return Effect.as(Ref.set(history, Chunk.appendAll(allParts, responseParts)), response.unsafeValue); | ||
})); | ||
}), semaphore.withPermits(1), Effect.withSpan("AiChat.structured", { | ||
attributes: { | ||
input: options.input, | ||
schema: "toolCallId" in options ? options.toolCallId : "_tag" in options.schema ? options.schema._tag : options.schema.identifier | ||
}, | ||
captureStackTrace: false | ||
})); | ||
}), this.semaphore.withPermits(1), Effect.withSpan("AiChat.toolkit", { | ||
attributes: { | ||
input: options.input | ||
}, | ||
captureStackTrace: false | ||
})); | ||
} | ||
toolkitStream(options) { | ||
return Stream.suspend(() => { | ||
let combined = WithResolved.empty; | ||
return Stream.fromChannel(Channel.acquireUseRelease(this.semaphore.take(1).pipe(Effect.zipRight(Ref.get(this.historyRef)), Effect.map(Chunk.appendAll(AiInput.make(options.input)))), parts => this.completions.toolkitStream({ | ||
...options, | ||
input: parts | ||
}).pipe(Stream.map(chunk => { | ||
combined = combined.concat(chunk); | ||
return chunk; | ||
}), Stream.toChannel), parts => Effect.zipRight(Ref.set(this.historyRef, Chunk.appendAll(parts, AiInput.make(combined))), this.semaphore.release(1)))); | ||
}).pipe(Stream.withSpan("AiChat.toolkitStream", { | ||
attributes: { | ||
input: options.input | ||
}, | ||
captureStackTrace: false | ||
})); | ||
} | ||
} | ||
}, | ||
toolkit(options) { | ||
const newParts = AiInput.make(options.input); | ||
return Ref.get(history).pipe(Effect.flatMap(parts => { | ||
const allParts = Chunk.appendAll(parts, newParts); | ||
return completions.toolkit({ | ||
...options, | ||
input: allParts | ||
}).pipe(Effect.tap(response => { | ||
const responseParts = AiInput.make(response); | ||
return Ref.set(history, Chunk.appendAll(allParts, responseParts)); | ||
})); | ||
}), semaphore.withPermits(1), Effect.withSpan("AiChat.toolkit", { | ||
attributes: { | ||
input: options.input | ||
}, | ||
captureStackTrace: false | ||
})); | ||
}, | ||
toolkitStream(options) { | ||
return Stream.suspend(() => { | ||
let combined = WithResolved.empty; | ||
return Stream.fromChannel(Channel.acquireUseRelease(semaphore.take(1).pipe(Effect.zipRight(Ref.get(history)), Effect.map(Chunk.appendAll(AiInput.make(options.input)))), parts => completions.toolkitStream({ | ||
...options, | ||
input: parts | ||
}).pipe(Stream.map(chunk => { | ||
combined = combined.concat(chunk); | ||
return chunk; | ||
}), Stream.toChannel), parts => Effect.zipRight(Ref.set(history, Chunk.appendAll(parts, AiInput.make(combined))), semaphore.release(1)))); | ||
}).pipe(Stream.withSpan("AiChat.toolkitStream", { | ||
attributes: { | ||
input: options.input | ||
}, | ||
captureStackTrace: false | ||
})); | ||
} | ||
}); | ||
}); | ||
/** | ||
@@ -129,0 +117,0 @@ * @since 1.0.0 |
@@ -29,32 +29,39 @@ /** | ||
create(input) { | ||
return Effect.serviceOption(AiInput.SystemInstruction).pipe(Effect.flatMap(system => options.create({ | ||
return Effect.useSpan("Completions.create", { | ||
captureStackTrace: false | ||
}, span => Effect.serviceOption(AiInput.SystemInstruction).pipe(Effect.flatMap(system => options.create({ | ||
input: AiInput.make(input), | ||
system: Option.orElse(system, () => parentSystem), | ||
tools: [], | ||
required: false | ||
})), Effect.withSpan("Completions.create", { | ||
captureStackTrace: false | ||
})); | ||
required: false, | ||
span | ||
})))); | ||
}, | ||
stream(input_) { | ||
const input = AiInput.make(input_); | ||
return Effect.serviceOption(AiInput.SystemInstruction).pipe(Effect.map(system => options.stream({ | ||
return Effect.makeSpanScoped("Completions.stream", { | ||
captureStackTrace: false | ||
}).pipe(Effect.zip(Effect.serviceOption(AiInput.SystemInstruction)), Effect.map(([span, system]) => options.stream({ | ||
input: input, | ||
system: Option.orElse(system, () => parentSystem), | ||
tools: [], | ||
required: false | ||
})), Stream.unwrap, Stream.withSpan("Completions.stream", { | ||
captureStackTrace: false | ||
})); | ||
required: false, | ||
span | ||
})), Stream.unwrapScoped); | ||
}, | ||
structured(opts) { | ||
const input = AiInput.make(opts.input); | ||
const schema = opts.schema; | ||
const decode = Schema.decodeUnknown(schema); | ||
const toolId = schema._tag ?? schema.identifier; | ||
return Effect.serviceOption(AiInput.SystemInstruction).pipe(Effect.flatMap(system => options.create({ | ||
const decode = Schema.decodeUnknown(opts.schema); | ||
const toolId = "toolCallId" in opts ? opts.toolCallId : "_tag" in opts.schema ? opts.schema._tag : opts.schema.identifier; | ||
return Effect.useSpan("Completions.structured", { | ||
attributes: { | ||
toolId | ||
}, | ||
captureStackTrace: false | ||
}, span => Effect.serviceOption(AiInput.SystemInstruction).pipe(Effect.flatMap(system => options.create({ | ||
input: input, | ||
system: Option.orElse(system, () => parentSystem), | ||
tools: [convertTool(schema, true)], | ||
required: true | ||
tools: [convertTool(toolId, opts.schema, true)], | ||
required: true, | ||
span | ||
})), Effect.flatMap(response => Chunk.findFirst(response.parts, part => part._tag === "ToolCall" && part.name === toolId).pipe(Option.match({ | ||
@@ -79,8 +86,3 @@ onNone: () => Effect.fail(new AiError({ | ||
}) | ||
}), Effect.withSpan("Completions.structured", { | ||
attributes: { | ||
tool: toolId | ||
}, | ||
captureStackTrace: false | ||
})))); | ||
}))))); | ||
}, | ||
@@ -96,9 +98,16 @@ toolkit({ | ||
for (const [, tool] of tools.toolkit.tools) { | ||
toolArr.push(convertTool(tool)); | ||
toolArr.push(convertTool(tool._tag, tool)); | ||
} | ||
return Effect.serviceOption(AiInput.SystemInstruction).pipe(Effect.flatMap(system => options.create({ | ||
return Effect.useSpan("Completions.toolkit", { | ||
attributes: { | ||
concurrency, | ||
required | ||
}, | ||
captureStackTrace: false | ||
}, span => Effect.serviceOption(AiInput.SystemInstruction).pipe(Effect.flatMap(system => options.create({ | ||
input: input, | ||
system: Option.orElse(system, () => parentSystem), | ||
tools: toolArr, | ||
required: required | ||
required: required, | ||
span | ||
})), Effect.flatMap(response => resolveParts({ | ||
@@ -109,9 +118,3 @@ response, | ||
method: "toolkit" | ||
})), Effect.withSpan("Completions.toolkit", { | ||
captureStackTrace: false, | ||
attributes: { | ||
concurrency, | ||
required | ||
} | ||
})); | ||
})))); | ||
}, | ||
@@ -126,10 +129,17 @@ toolkitStream({ | ||
for (const [, tool] of tools.toolkit.tools) { | ||
toolArr.push(convertTool(tool)); | ||
toolArr.push(convertTool(tool._tag, tool)); | ||
} | ||
return Effect.serviceOption(AiInput.SystemInstruction).pipe(Effect.map(system => options.stream({ | ||
return Effect.makeSpanScoped("Completions.stream", { | ||
captureStackTrace: false, | ||
attributes: { | ||
required, | ||
concurrency | ||
} | ||
}).pipe(Effect.zip(Effect.serviceOption(AiInput.SystemInstruction)), Effect.map(([span, system]) => options.stream({ | ||
input: AiInput.make(input), | ||
system: Option.orElse(system, () => parentSystem), | ||
tools: toolArr, | ||
required: required | ||
})), Stream.unwrap, Stream.mapEffect(chunk => resolveParts({ | ||
required: required, | ||
span | ||
})), Stream.unwrapScoped, Stream.mapEffect(chunk => resolveParts({ | ||
response: chunk, | ||
@@ -141,8 +151,2 @@ tools, | ||
concurrency: "unbounded" | ||
}), Stream.withSpan("Completions.toolkitStream", { | ||
captureStackTrace: false, | ||
attributes: { | ||
concurrency, | ||
required | ||
} | ||
})); | ||
@@ -152,6 +156,6 @@ } | ||
}); | ||
const convertTool = (tool, structured = false) => ({ | ||
name: tool._tag ?? tool.identifier, | ||
description: getDescription(tool.ast), | ||
parameters: makeJsonSchema(tool.ast), | ||
const convertTool = (name, schema, structured = false) => ({ | ||
name, | ||
description: getDescription(schema.ast), | ||
parameters: makeJsonSchema(schema.ast), | ||
structured | ||
@@ -158,0 +162,0 @@ }); |
@@ -24,2 +24,6 @@ /** | ||
*/ | ||
export * as AiTelemetry from "./AiTelemetry.js"; | ||
/** | ||
* @since 1.0.0 | ||
*/ | ||
export * as AiToolkit from "./AiToolkit.js"; | ||
@@ -33,3 +37,7 @@ /** | ||
*/ | ||
export * as Embeddings from "./Embeddings.js"; | ||
/** | ||
* @since 1.0.0 | ||
*/ | ||
export * as Tokenizer from "./Tokenizer.js"; | ||
//# sourceMappingURL=index.js.map |
{ | ||
"name": "@effect/ai", | ||
"version": "0.8.1", | ||
"version": "0.8.2", | ||
"description": "Effect modules for working with AI apis", | ||
@@ -14,4 +14,5 @@ "license": "MIT", | ||
"peerDependencies": { | ||
"@effect/platform": "^0.75.1", | ||
"effect": "^3.12.7" | ||
"@effect/experimental": "^0.39.2", | ||
"@effect/platform": "^0.75.2", | ||
"effect": "^3.12.8" | ||
}, | ||
@@ -56,2 +57,7 @@ "publishConfig": { | ||
}, | ||
"./AiTelemetry": { | ||
"types": "./dist/dts/AiTelemetry.d.ts", | ||
"import": "./dist/esm/AiTelemetry.js", | ||
"default": "./dist/cjs/AiTelemetry.js" | ||
}, | ||
"./AiToolkit": { | ||
@@ -67,2 +73,7 @@ "types": "./dist/dts/AiToolkit.d.ts", | ||
}, | ||
"./Embeddings": { | ||
"types": "./dist/dts/Embeddings.d.ts", | ||
"import": "./dist/esm/Embeddings.js", | ||
"default": "./dist/cjs/Embeddings.js" | ||
}, | ||
"./Tokenizer": { | ||
@@ -91,2 +102,5 @@ "types": "./dist/dts/Tokenizer.d.ts", | ||
], | ||
"AiTelemetry": [ | ||
"./dist/dts/AiTelemetry.d.ts" | ||
], | ||
"AiToolkit": [ | ||
@@ -98,2 +112,5 @@ "./dist/dts/AiToolkit.d.ts" | ||
], | ||
"Embeddings": [ | ||
"./dist/dts/Embeddings.d.ts" | ||
], | ||
"Tokenizer": [ | ||
@@ -100,0 +117,0 @@ "./dist/dts/Tokenizer.d.ts" |
@@ -42,6 +42,13 @@ /** | ||
readonly stream: (input: AiInput.Input) => Stream.Stream<AiResponse, AiError> | ||
readonly structured: <A, I, R>( | ||
tool: Completions.StructuredSchema<A, I, R>, | ||
input: AiInput.Input | ||
) => Effect.Effect<A, AiError, R> | ||
readonly structured: { | ||
<A, I, R>(options: { | ||
readonly input: AiInput.Input | ||
readonly schema: Completions.StructuredSchema<A, I, R> | ||
}): Effect.Effect<A, AiError, R> | ||
<A, I, R>(options: { | ||
readonly input: AiInput.Input | ||
readonly schema: Schema.Schema<A, I, R> | ||
readonly toolCallId: string | ||
}): Effect.Effect<A, AiError, R> | ||
} | ||
readonly toolkit: <Tools extends AiToolkit.Tool.AnySchema>( | ||
@@ -78,177 +85,161 @@ options: { | ||
*/ | ||
export const fromInput = (input: AiInput.Input): Effect.Effect<AiChat.Service, never, Completions> => | ||
Ref.make(AiInput.make(input)).pipe( | ||
Effect.bindTo("historyRef"), | ||
Effect.bind("completions", () => Completions), | ||
Effect.map(({ completions, historyRef }) => new AiChatImpl(historyRef, completions)) | ||
) | ||
export const fromInput = Effect.fnUntraced( | ||
function*(input: AiInput.Input) { | ||
const completions = yield* Completions | ||
const history = yield* Ref.make(AiInput.make(input)) | ||
const semaphore = yield* Effect.makeSemaphore(1) | ||
class AiChatImpl implements AiChat.Service { | ||
readonly semaphore = Effect.unsafeMakeSemaphore(1) | ||
constructor( | ||
readonly historyRef: Ref.Ref<AiInput.AiInput>, | ||
readonly completions: Completions.Service | ||
) {} | ||
get history() { | ||
return Ref.get(this.historyRef) | ||
} | ||
get export() { | ||
return Ref.get(this.historyRef).pipe( | ||
Effect.flatMap(Schema.encode(AiInput.Schema)), | ||
Effect.orDie | ||
) | ||
} | ||
get exportJson() { | ||
return Ref.get(this.historyRef).pipe( | ||
Effect.flatMap(Schema.encode(AiInput.SchemaJson)), | ||
Effect.orDie | ||
) | ||
} | ||
send(input: AiInput.Input) { | ||
const newParts = AiInput.make(input) | ||
return Ref.get(this.historyRef).pipe( | ||
Effect.flatMap((parts) => { | ||
const allParts = Chunk.appendAll(parts, newParts) | ||
return this.completions.create(allParts).pipe( | ||
Effect.tap((response) => { | ||
const responseParts = AiInput.make(response) | ||
return Ref.set(this.historyRef, Chunk.appendAll(allParts, responseParts)) | ||
return AiChat.of({ | ||
history: Ref.get(history), | ||
export: Ref.get(history).pipe( | ||
Effect.flatMap(Schema.encode(AiInput.Schema)), | ||
Effect.orDie | ||
), | ||
exportJson: Ref.get(history).pipe( | ||
Effect.flatMap(Schema.encode(AiInput.SchemaJson)), | ||
Effect.orDie | ||
), | ||
send(input) { | ||
const newParts = AiInput.make(input) | ||
return Ref.get(history).pipe( | ||
Effect.flatMap((parts) => { | ||
const allParts = Chunk.appendAll(parts, newParts) | ||
return completions.create(allParts).pipe( | ||
Effect.tap((response) => { | ||
const responseParts = AiInput.make(response) | ||
return Ref.set(history, Chunk.appendAll(allParts, responseParts)) | ||
}) | ||
) | ||
}), | ||
semaphore.withPermits(1), | ||
Effect.withSpan("AiChat.send", { | ||
attributes: { input }, | ||
captureStackTrace: false | ||
}) | ||
) | ||
}), | ||
this.semaphore.withPermits(1), | ||
Effect.withSpan("AiChat.send", { attributes: { input }, captureStackTrace: false }) | ||
) | ||
} | ||
stream(input: AiInput.Input) { | ||
return Stream.suspend(() => { | ||
let combined = AiResponse.empty | ||
return Stream.fromChannel(Channel.acquireUseRelease( | ||
this.semaphore.take(1).pipe( | ||
Effect.zipRight(Ref.get(this.historyRef)), | ||
Effect.map(Chunk.appendAll(AiInput.make(input))) | ||
), | ||
(parts) => | ||
this.completions.stream(parts).pipe( | ||
Stream.map((chunk) => { | ||
combined = combined.concat(chunk) | ||
return chunk | ||
}), | ||
Stream.toChannel | ||
), | ||
(parts) => | ||
Effect.zipRight( | ||
Ref.set(this.historyRef, Chunk.appendAll(parts, AiInput.make(combined))), | ||
this.semaphore.release(1) | ||
) | ||
)) | ||
}).pipe(Stream.withSpan("AiChat.stream", { attributes: { input }, captureStackTrace: false })) | ||
} | ||
structured<A, I, R>( | ||
schema: Completions.StructuredSchema<A, I, R>, | ||
input: AiInput.Input | ||
): Effect.Effect<A, AiError, R> { | ||
const newParts = AiInput.make(input) | ||
return Ref.get(this.historyRef).pipe( | ||
Effect.flatMap((parts) => { | ||
const allParts = Chunk.appendAll(parts, newParts) | ||
return this.completions.structured({ | ||
input: allParts, | ||
schema | ||
}).pipe( | ||
Effect.flatMap((response) => { | ||
const responseParts = AiInput.make(response) | ||
return Effect.as( | ||
Ref.set(this.historyRef, Chunk.appendAll(allParts, responseParts)), | ||
response.unsafeValue | ||
}, | ||
stream(input) { | ||
return Stream.suspend(() => { | ||
let combined = AiResponse.empty | ||
return Stream.fromChannel(Channel.acquireUseRelease( | ||
semaphore.take(1).pipe( | ||
Effect.zipRight(Ref.get(history)), | ||
Effect.map(Chunk.appendAll(AiInput.make(input))) | ||
), | ||
(parts) => | ||
completions.stream(parts).pipe( | ||
Stream.map((chunk) => { | ||
combined = combined.concat(chunk) | ||
return chunk | ||
}), | ||
Stream.toChannel | ||
), | ||
(parts) => | ||
Effect.zipRight( | ||
Ref.set(history, Chunk.appendAll(parts, AiInput.make(combined))), | ||
semaphore.release(1) | ||
) | ||
)) | ||
}).pipe(Stream.withSpan("AiChat.stream", { | ||
attributes: { input }, | ||
captureStackTrace: false | ||
})) | ||
}, | ||
structured(options) { | ||
const newParts = AiInput.make(options.input) | ||
return Ref.get(history).pipe( | ||
Effect.flatMap((parts) => { | ||
const allParts = Chunk.appendAll(parts, newParts) | ||
return completions.structured({ | ||
...options, | ||
input: allParts | ||
} as any).pipe( | ||
Effect.flatMap((response) => { | ||
const responseParts = AiInput.make(response) | ||
return Effect.as( | ||
Ref.set(history, Chunk.appendAll(allParts, responseParts)), | ||
response.unsafeValue | ||
) | ||
}) | ||
) | ||
}), | ||
semaphore.withPermits(1), | ||
Effect.withSpan("AiChat.structured", { | ||
attributes: { | ||
input: options.input, | ||
schema: "toolCallId" in options | ||
? options.toolCallId | ||
: "_tag" in options.schema | ||
? options.schema._tag | ||
: options.schema.identifier | ||
}, | ||
captureStackTrace: false | ||
}) | ||
) | ||
}), | ||
this.semaphore.withPermits(1), | ||
Effect.withSpan("AiChat.structured", { | ||
attributes: { input, schema: schema._tag ?? schema.identifier }, | ||
captureStackTrace: false | ||
}) | ||
) | ||
} | ||
toolkit<Tools extends AiToolkit.Tool.AnySchema>( | ||
options: { | ||
readonly input: AiInput.Input | ||
readonly tools: AiToolkit.Handlers<Tools> | ||
readonly required?: Tools["_tag"] | boolean | undefined | ||
readonly concurrency?: Concurrency | undefined | ||
} | ||
): Effect.Effect< | ||
WithResolved<AiToolkit.Tool.Success<Tools>>, | ||
AiError | AiToolkit.Tool.Failure<Tools>, | ||
AiToolkit.Tool.Context<Tools> | ||
> { | ||
const newParts = AiInput.make(options.input) | ||
return Ref.get(this.historyRef).pipe( | ||
Effect.flatMap((parts) => { | ||
const allParts = Chunk.appendAll(parts, newParts) | ||
return this.completions.toolkit({ | ||
...options, | ||
input: allParts | ||
}).pipe( | ||
Effect.tap((response) => { | ||
const responseParts = AiInput.make(response) | ||
return Ref.set(this.historyRef, Chunk.appendAll(allParts, responseParts)) | ||
}, | ||
toolkit(options) { | ||
const newParts = AiInput.make(options.input) | ||
return Ref.get(history).pipe( | ||
Effect.flatMap((parts) => { | ||
const allParts = Chunk.appendAll(parts, newParts) | ||
return completions.toolkit({ | ||
...options, | ||
input: allParts | ||
}).pipe( | ||
Effect.tap((response) => { | ||
const responseParts = AiInput.make(response) | ||
return Ref.set(history, Chunk.appendAll(allParts, responseParts)) | ||
}) | ||
) | ||
}), | ||
semaphore.withPermits(1), | ||
Effect.withSpan("AiChat.toolkit", { | ||
attributes: { input: options.input }, | ||
captureStackTrace: false | ||
}) | ||
) | ||
}), | ||
this.semaphore.withPermits(1), | ||
Effect.withSpan("AiChat.toolkit", { attributes: { input: options.input }, captureStackTrace: false }) | ||
) | ||
}, | ||
toolkitStream<Tools extends AiToolkit.Tool.AnySchema>(options: { | ||
readonly input: AiInput.Input | ||
readonly tools: AiToolkit.Handlers<Tools> | ||
readonly required?: Tools["_tag"] | boolean | undefined | ||
readonly concurrency?: Concurrency | undefined | ||
}): Stream.Stream< | ||
WithResolved<AiToolkit.Tool.Success<Tools>>, | ||
AiError | AiToolkit.Tool.Failure<Tools>, | ||
AiToolkit.Tool.Context<Tools> | ||
> { | ||
return Stream.suspend(() => { | ||
let combined = WithResolved.empty as WithResolved<AiToolkit.Tool.Success<Tools>> | ||
return Stream.fromChannel(Channel.acquireUseRelease( | ||
semaphore.take(1).pipe( | ||
Effect.zipRight(Ref.get(history)), | ||
Effect.map(Chunk.appendAll(AiInput.make(options.input))) | ||
), | ||
(parts) => | ||
completions.toolkitStream({ | ||
...options, | ||
input: parts | ||
}).pipe( | ||
Stream.map((chunk) => { | ||
combined = combined.concat(chunk) | ||
return chunk | ||
}), | ||
Stream.toChannel | ||
), | ||
(parts) => | ||
Effect.zipRight( | ||
Ref.set(history, Chunk.appendAll(parts, AiInput.make(combined))), | ||
semaphore.release(1) | ||
) | ||
)) | ||
}).pipe(Stream.withSpan("AiChat.toolkitStream", { | ||
attributes: { input: options.input }, | ||
captureStackTrace: false | ||
})) | ||
} | ||
}) | ||
} | ||
) | ||
toolkitStream<Tools extends AiToolkit.Tool.AnySchema>( | ||
options: { | ||
readonly input: AiInput.Input | ||
readonly tools: AiToolkit.Handlers<Tools> | ||
readonly required?: Tools["_tag"] | boolean | undefined | ||
readonly concurrency?: Concurrency | undefined | ||
} | ||
): Stream.Stream< | ||
WithResolved<AiToolkit.Tool.Success<Tools>>, | ||
AiError | AiToolkit.Tool.Failure<Tools>, | ||
AiToolkit.Tool.Context<Tools> | ||
> { | ||
return Stream.suspend(() => { | ||
let combined = WithResolved.empty as WithResolved<AiToolkit.Tool.Success<Tools>> | ||
return Stream.fromChannel(Channel.acquireUseRelease( | ||
this.semaphore.take(1).pipe( | ||
Effect.zipRight(Ref.get(this.historyRef)), | ||
Effect.map(Chunk.appendAll(AiInput.make(options.input))) | ||
), | ||
(parts) => | ||
this.completions.toolkitStream({ | ||
...options, | ||
input: parts | ||
}).pipe( | ||
Stream.map((chunk) => { | ||
combined = combined.concat(chunk) | ||
return chunk | ||
}), | ||
Stream.toChannel | ||
), | ||
(parts) => | ||
Effect.zipRight( | ||
Ref.set(this.historyRef, Chunk.appendAll(parts, AiInput.make(combined))), | ||
this.semaphore.release(1) | ||
) | ||
)) | ||
}).pipe(Stream.withSpan("AiChat.toolkitStream", { attributes: { input: options.input }, captureStackTrace: false })) | ||
} | ||
} | ||
/** | ||
@@ -255,0 +246,0 @@ * @since 1.0.0 |
@@ -13,2 +13,3 @@ /** | ||
import * as Stream from "effect/Stream" | ||
import type { Span } from "effect/Tracer" | ||
import type { Concurrency } from "effect/Types" | ||
@@ -38,6 +39,19 @@ import { AiError } from "./AiError.js" | ||
* @since 1.0.0 | ||
* @models | ||
* @category models | ||
*/ | ||
export interface StructuredSchema<A, I, R> extends Schema.Schema<A, I, R> { | ||
readonly _tag?: string | ||
export type StructuredSchema<A, I, R> = TaggedSchema<A, I, R> | IdentifiedSchema<A, I, R> | ||
/** | ||
* @since 1.0.0 | ||
* @category models | ||
*/ | ||
export interface TaggedSchema<A, I, R> extends Schema.Schema<A, I, R> { | ||
readonly _tag: string | ||
} | ||
/** | ||
* @since 1.0.0 | ||
* @category models | ||
*/ | ||
export interface IdentifiedSchema<A, I, R> extends Schema.Schema<A, I, R> { | ||
readonly identifier: string | ||
@@ -48,3 +62,3 @@ } | ||
* @since 1.0.0 | ||
* @models | ||
* @category models | ||
*/ | ||
@@ -54,8 +68,13 @@ export interface Service { | ||
readonly stream: (input: AiInput.Input) => Stream.Stream<AiResponse, AiError> | ||
readonly structured: <A, I, R>( | ||
options: { | ||
readonly structured: { | ||
<A, I, R>(options: { | ||
readonly input: AiInput.Input | ||
readonly schema: StructuredSchema<A, I, R> | ||
} | ||
) => Effect.Effect<WithResolved<A>, AiError, R> | ||
}): Effect.Effect<WithResolved<A>, AiError, R> | ||
<A, I, R>(options: { | ||
readonly input: AiInput.Input | ||
readonly schema: Schema.Schema<A, I, R> | ||
readonly toolCallId: string | ||
}): Effect.Effect<WithResolved<A>, AiError, R> | ||
} | ||
readonly toolkit: <Tools extends AiToolkit.Tool.AnySchema>( | ||
@@ -121,2 +140,3 @@ options: { | ||
readonly required: boolean | string | ||
readonly span: Span | ||
}) => Effect.Effect<AiResponse, AiError> | ||
@@ -133,2 +153,3 @@ readonly stream: (options: { | ||
readonly required: boolean | string | ||
readonly span: Span | ||
}) => Stream.Stream<AiResponse, AiError> | ||
@@ -139,12 +160,17 @@ }): Effect.Effect<Completions.Service> => | ||
create(input) { | ||
return Effect.serviceOption(AiInput.SystemInstruction).pipe( | ||
Effect.flatMap((system) => | ||
options.create({ | ||
input: AiInput.make(input) as Chunk.NonEmptyChunk<Message>, | ||
system: Option.orElse(system, () => parentSystem), | ||
tools: [], | ||
required: false | ||
}) | ||
), | ||
Effect.withSpan("Completions.create", { captureStackTrace: false }) | ||
return Effect.useSpan( | ||
"Completions.create", | ||
{ captureStackTrace: false }, | ||
(span) => | ||
Effect.serviceOption(AiInput.SystemInstruction).pipe( | ||
Effect.flatMap((system) => | ||
options.create({ | ||
input: AiInput.make(input) as Chunk.NonEmptyChunk<Message>, | ||
system: Option.orElse(system, () => parentSystem), | ||
tools: [], | ||
required: false, | ||
span | ||
}) | ||
) | ||
) | ||
) | ||
@@ -154,4 +180,5 @@ }, | ||
const input = AiInput.make(input_) | ||
return Effect.serviceOption(AiInput.SystemInstruction).pipe( | ||
Effect.map((system) => | ||
return Effect.makeSpanScoped("Completions.stream", { captureStackTrace: false }).pipe( | ||
Effect.zip(Effect.serviceOption(AiInput.SystemInstruction)), | ||
Effect.map(([span, system]) => | ||
options.stream({ | ||
@@ -161,7 +188,7 @@ input: input as Chunk.NonEmptyChunk<Message>, | ||
tools: [], | ||
required: false | ||
required: false, | ||
span | ||
}) | ||
), | ||
Stream.unwrap, | ||
Stream.withSpan("Completions.stream", { captureStackTrace: false }) | ||
Stream.unwrapScoped | ||
) | ||
@@ -171,53 +198,58 @@ }, | ||
const input = AiInput.make(opts.input) | ||
const schema = opts.schema | ||
const decode = Schema.decodeUnknown(schema) | ||
const toolId = schema._tag ?? schema.identifier | ||
return Effect.serviceOption(AiInput.SystemInstruction).pipe( | ||
Effect.flatMap((system) => | ||
options.create({ | ||
input: input as Chunk.NonEmptyChunk<Message>, | ||
system: Option.orElse(system, () => parentSystem), | ||
tools: [convertTool(schema, true)], | ||
required: true | ||
}) | ||
), | ||
Effect.flatMap((response) => | ||
Chunk.findFirst( | ||
response.parts, | ||
(part): part is ToolCallPart => part._tag === "ToolCall" && part.name === toolId | ||
).pipe( | ||
Option.match({ | ||
onNone: () => | ||
Effect.fail( | ||
new AiError({ | ||
module: "Completions", | ||
method: "structured", | ||
description: `Tool call '${toolId}' not found in response` | ||
}) | ||
), | ||
onSome: (toolCall) => | ||
Effect.matchEffect(decode(toolCall.params), { | ||
onFailure: (cause) => | ||
new AiError({ | ||
module: "Completions", | ||
method: "structured", | ||
description: `Failed to decode tool call '${toolId}' parameters`, | ||
cause | ||
}), | ||
onSuccess: (resolved) => | ||
Effect.succeed( | ||
new WithResolved({ | ||
response, | ||
resolved: new Map([[toolCall.id, resolved]]), | ||
encoded: new Map([[toolCall.id, toolCall.params]]) | ||
const decode = Schema.decodeUnknown(opts.schema) | ||
const toolId = "toolCallId" in opts | ||
? opts.toolCallId | ||
: "_tag" in opts.schema | ||
? opts.schema._tag | ||
: opts.schema.identifier | ||
return Effect.useSpan( | ||
"Completions.structured", | ||
{ attributes: { toolId }, captureStackTrace: false }, | ||
(span) => | ||
Effect.serviceOption(AiInput.SystemInstruction).pipe( | ||
Effect.flatMap((system) => | ||
options.create({ | ||
input: input as Chunk.NonEmptyChunk<Message>, | ||
system: Option.orElse(system, () => parentSystem), | ||
tools: [convertTool(toolId, opts.schema, true)], | ||
required: true, | ||
span | ||
}) | ||
), | ||
Effect.flatMap((response) => | ||
Chunk.findFirst( | ||
response.parts, | ||
(part): part is ToolCallPart => part._tag === "ToolCall" && part.name === toolId | ||
).pipe( | ||
Option.match({ | ||
onNone: () => | ||
Effect.fail( | ||
new AiError({ | ||
module: "Completions", | ||
method: "structured", | ||
description: `Tool call '${toolId}' not found in response` | ||
}) | ||
) | ||
), | ||
onSome: (toolCall) => | ||
Effect.matchEffect(decode(toolCall.params), { | ||
onFailure: (cause) => | ||
new AiError({ | ||
module: "Completions", | ||
method: "structured", | ||
description: `Failed to decode tool call '${toolId}' parameters`, | ||
cause | ||
}), | ||
onSuccess: (resolved) => | ||
Effect.succeed( | ||
new WithResolved({ | ||
response, | ||
resolved: new Map([[toolCall.id, resolved]]), | ||
encoded: new Map([[toolCall.id, toolCall.params]]) | ||
}) | ||
) | ||
}) | ||
}) | ||
}), | ||
Effect.withSpan("Completions.structured", { | ||
attributes: { tool: toolId }, | ||
captureStackTrace: false | ||
}) | ||
) | ||
) | ||
) | ||
) | ||
) | ||
@@ -234,22 +266,21 @@ }, | ||
for (const [, tool] of tools.toolkit.tools) { | ||
toolArr.push(convertTool(tool as any)) | ||
toolArr.push(convertTool(tool._tag, tool as any)) | ||
} | ||
return Effect.serviceOption(AiInput.SystemInstruction).pipe( | ||
Effect.flatMap((system) => | ||
options.create({ | ||
input: input as Chunk.NonEmptyChunk<Message>, | ||
system: Option.orElse(system, () => parentSystem), | ||
tools: toolArr, | ||
required: required as any | ||
}) | ||
), | ||
Effect.flatMap((response) => resolveParts({ response, tools, concurrency, method: "toolkit" })), | ||
Effect.withSpan("Completions.toolkit", { | ||
captureStackTrace: false, | ||
attributes: { | ||
concurrency, | ||
required | ||
} | ||
}) | ||
) as any | ||
return Effect.useSpan( | ||
"Completions.toolkit", | ||
{ attributes: { concurrency, required }, captureStackTrace: false }, | ||
(span) => | ||
Effect.serviceOption(AiInput.SystemInstruction).pipe( | ||
Effect.flatMap((system) => | ||
options.create({ | ||
input: input as Chunk.NonEmptyChunk<Message>, | ||
system: Option.orElse(system, () => parentSystem), | ||
tools: toolArr, | ||
required: required as any, | ||
span | ||
}) | ||
), | ||
Effect.flatMap((response) => resolveParts({ response, tools, concurrency, method: "toolkit" })) | ||
) as any | ||
) | ||
}, | ||
@@ -264,6 +295,10 @@ toolkitStream({ concurrency, input, required = false, tools }) { | ||
for (const [, tool] of tools.toolkit.tools) { | ||
toolArr.push(convertTool(tool as any)) | ||
toolArr.push(convertTool(tool._tag, tool as any)) | ||
} | ||
return Effect.serviceOption(AiInput.SystemInstruction).pipe( | ||
Effect.map((system) => | ||
return Effect.makeSpanScoped("Completions.stream", { | ||
captureStackTrace: false, | ||
attributes: { required, concurrency } | ||
}).pipe( | ||
Effect.zip(Effect.serviceOption(AiInput.SystemInstruction)), | ||
Effect.map(([span, system]) => | ||
options.stream({ | ||
@@ -273,17 +308,11 @@ input: AiInput.make(input) as Chunk.NonEmptyChunk<Message>, | ||
tools: toolArr, | ||
required: required as any | ||
required: required as any, | ||
span | ||
}) | ||
), | ||
Stream.unwrap, | ||
Stream.unwrapScoped, | ||
Stream.mapEffect( | ||
(chunk) => resolveParts({ response: chunk, tools, concurrency, method: "toolkitStream" }), | ||
{ concurrency: "unbounded" } | ||
), | ||
Stream.withSpan("Completions.toolkitStream", { | ||
captureStackTrace: false, | ||
attributes: { | ||
concurrency, | ||
required | ||
} | ||
}) | ||
) | ||
) as any | ||
@@ -294,6 +323,10 @@ } | ||
const convertTool = <A, I, R>(tool: Completions.StructuredSchema<A, I, R>, structured = false) => ({ | ||
name: tool._tag ?? tool.identifier, | ||
description: getDescription(tool.ast), | ||
parameters: makeJsonSchema(tool.ast), | ||
const convertTool = <A, I, R>( | ||
name: string, | ||
schema: Schema.Schema<A, I, R>, | ||
structured = false | ||
) => ({ | ||
name, | ||
description: getDescription(schema.ast), | ||
parameters: makeJsonSchema(schema.ast), | ||
structured | ||
@@ -300,0 +333,0 @@ }) |
@@ -29,2 +29,7 @@ /** | ||
*/ | ||
export * as AiTelemetry from "./AiTelemetry.js" | ||
/** | ||
* @since 1.0.0 | ||
*/ | ||
export * as AiToolkit from "./AiToolkit.js" | ||
@@ -40,2 +45,7 @@ | ||
*/ | ||
export * as Embeddings from "./Embeddings.js" | ||
/** | ||
* @since 1.0.0 | ||
*/ | ||
export * as Tokenizer from "./Tokenizer.js" |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
348078
91
6680
3