EdgeAIG's picture
download
raw
24.2 kB
/**
* Defines prompts sent to AI language models.
*
* A prompt is an ordered list of messages. Messages can use roles such as
* system, user, assistant, and tool, and their content can be split into typed
* parts such as text, files, reasoning, tool calls, tool results, and approval
* messages. This module helps build prompts, combine them, and convert raw
* input or response parts into the shared prompt shape.
*
* @since 4.0.0
*/
import * as Arr from "../../Array.js";
import * as Effect from "../../Effect.js";
import { dual } from "../../Function.js";
import * as Option from "../../Option.js";
import { pipeArguments } from "../../Pipeable.js";
import * as Predicate from "../../Predicate.js";
import * as Schema from "../../Schema.js";
import * as SchemaIssue from "../../SchemaIssue.js";
import * as SchemaParser from "../../SchemaParser.js";
import * as SchemaTransformation from "../../SchemaTransformation.js";
// =============================================================================
// Options
// =============================================================================
/**
* Schema for provider-specific options that can be attached to content parts
* and messages.
*
* **Details**
*
* Provider-specific options are keyed by provider-specific names, and each
* value is JSON or `null`.
*
* @category options
* @since 4.0.0
*/
export const ProviderOptions = /*#__PURE__*/Schema.Record(Schema.String, /*#__PURE__*/Schema.NullOr(Schema.Json));
// =============================================================================
// Base Part
// =============================================================================
const PartTypeId = "~effect/ai/Prompt/Part";
/**
* Type guard to check if a value is a Part.
*
* @category guards
* @since 4.0.0
*/
export const isPart = u => Predicate.hasProperty(u, PartTypeId);
const BasePart = /*#__PURE__*/Schema.Struct({
[PartTypeId]: /*#__PURE__*/Schema.Literal(PartTypeId).pipe(/*#__PURE__*/Schema.withDecodingDefaultKey(/*#__PURE__*/Effect.succeed(PartTypeId), {
encodingStrategy: "omit"
})),
options: /*#__PURE__*/ProviderOptions.pipe(/*#__PURE__*/Schema.withDecodingDefault(/*#__PURE__*/Effect.succeed({})))
});
/**
* Creates a new content part of the specified type.
*
* **Example** (Creating content parts)
*
* ```ts
* import { Prompt } from "effect/unstable/ai"
*
* const textPart = Prompt.makePart("text", {
* text: "Hello, world!"
* })
*
* const filePart = Prompt.makePart("file", {
* mediaType: "image/png",
* fileName: "screenshot.png",
* data: new Uint8Array([1, 2, 3])
* })
* ```
*
* @category constructors
* @since 4.0.0
*/
export const makePart = (
/**
* The type of part to create.
*/
type,
/**
* Parameters specific to the part type being created.
*/
params) => ({
...params,
[PartTypeId]: PartTypeId,
type,
options: params.options ?? {}
});
/**
* Schema for validation and encoding of text parts.
*
* @category schemas
* @since 4.0.0
*/
export const TextPart = /*#__PURE__*/Schema.Struct({
...BasePart.fields,
type: Schema.Literal("text"),
text: Schema.String
}).annotate({
identifier: "TextPart"
});
/**
* Constructs a new text part.
*
* @category constructors
* @since 4.0.0
*/
export const textPart = params => makePart("text", params);
/**
* Schema for validation and encoding of reasoning parts.
*
* @category schemas
* @since 4.0.0
*/
export const ReasoningPart = /*#__PURE__*/Schema.Struct({
...BasePart.fields,
type: Schema.Literal("reasoning"),
text: Schema.String
}).annotate({
identifier: "ReasoningPart"
});
/**
* Constructs a new reasoning part.
*
* @category constructors
* @since 4.0.0
*/
export const reasoningPart = params => makePart("reasoning", params);
/**
* Schema for validation and encoding of file parts.
*
* @category schemas
* @since 4.0.0
*/
export const FilePart = /*#__PURE__*/Schema.Struct({
...BasePart.fields,
type: Schema.Literal("file"),
mediaType: Schema.String,
fileName: Schema.optional(Schema.String),
data: Schema.Union([Schema.String, Schema.Uint8Array, Schema.URL])
}).annotate({
identifier: "FilePart"
});
/**
* Constructs a `FilePart` for prompt file attachments.
*
* **When to use**
*
* Use to create the file-attachment part of a prompt from typed file part
* parameters.
*
* @see {@link makePart} for the generic part constructor
*
* @category constructors
* @since 4.0.0
*/
export const filePart = params => makePart("file", params);
/**
* Schema for validation and encoding of tool call parts.
*
* @category schemas
* @since 4.0.0
*/
export const ToolCallPart = /*#__PURE__*/Schema.Struct({
...BasePart.fields,
type: Schema.Literal("tool-call"),
id: Schema.String,
name: Schema.String,
params: Schema.Unknown,
providerExecuted: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false)))
}).annotate({
identifier: "ToolCallPart"
});
/**
* Constructs a new tool call part.
*
* @category constructors
* @since 4.0.0
*/
export const toolCallPart = params => makePart("tool-call", params);
/**
* Schema for validation and encoding of tool result parts.
*
* @category schemas
* @since 4.0.0
*/
export const ToolResultPart = /*#__PURE__*/Schema.Struct({
...BasePart.fields,
type: Schema.Literal("tool-result"),
id: Schema.String,
name: Schema.String,
isFailure: Schema.Boolean,
result: Schema.Unknown
}).annotate({
identifier: "ToolResultPart"
});
/**
* Constructs a new tool result part.
*
* @category constructors
* @since 4.0.0
*/
export const toolResultPart = params => makePart("tool-result", params);
/**
* Schema for validation and encoding of tool approval response parts.
*
* @category schemas
* @since 4.0.0
*/
export const ToolApprovalResponsePart = /*#__PURE__*/Schema.Struct({
...BasePart.fields,
type: Schema.Literal("tool-approval-response"),
approvalId: Schema.String,
approved: Schema.Boolean,
reason: Schema.optional(Schema.String)
}).annotate({
identifier: "ToolApprovalResponsePart"
});
/**
* Constructs a new tool approval response part.
*
* @category constructors
* @since 4.0.0
*/
export const toolApprovalResponsePart = params => makePart("tool-approval-response", params);
/**
* Schema for validation and encoding of tool approval request parts.
*
* @category schemas
* @since 4.0.0
*/
export const ToolApprovalRequestPart = /*#__PURE__*/Schema.Struct({
...BasePart.fields,
type: Schema.Literal("tool-approval-request"),
approvalId: Schema.String,
toolCallId: Schema.String
}).annotate({
identifier: "ToolApprovalRequestPart"
});
/**
* Constructs a new tool approval request part.
*
* @category constructors
* @since 4.0.0
*/
export const toolApprovalRequestPart = params => makePart("tool-approval-request", params);
// =============================================================================
// Base Message
// =============================================================================
const MessageTypeId = "~effect/ai/Prompt/Message";
/**
* Type guard to check if a value is a Message.
*
* @category guards
* @since 4.0.0
*/
export const isMessage = u => Predicate.hasProperty(u, MessageTypeId);
const BaseMessage = /*#__PURE__*/Schema.Struct({
[MessageTypeId]: /*#__PURE__*/Schema.Literal(MessageTypeId).pipe(/*#__PURE__*/Schema.withDecodingDefaultKey(/*#__PURE__*/Effect.succeed(MessageTypeId), {
encodingStrategy: "omit"
})),
options: /*#__PURE__*/ProviderOptions.pipe(/*#__PURE__*/Schema.withDecodingDefault(/*#__PURE__*/Effect.succeed({})))
});
/**
* Creates a new message with the specified role.
*
* **Example** (Creating messages)
*
* ```ts
* import { Prompt } from "effect/unstable/ai"
*
* const textPart = Prompt.makePart("text", {
* text: "Hello, world!"
* })
*
* const userMessage = Prompt.makeMessage("user", {
* content: [textPart]
* })
* ```
*
* @category constructors
* @since 4.0.0
*/
export const makeMessage = (role, params) => ({
...params,
[MessageTypeId]: MessageTypeId,
role,
options: params.options ?? {}
});
/**
* Schema that decodes a string into content containing a single `TextPart` and,
* when encoding, emits the `text` value of the first part.
*
* @category schemas
* @since 4.0.0
*/
export const ContentFromString = /*#__PURE__*/Schema.String.pipe(/*#__PURE__*/Schema.decodeTo(/*#__PURE__*/Schema.NonEmptyArray(/*#__PURE__*/Schema.toType(TextPart)), /*#__PURE__*/SchemaTransformation.transform({
decode: text => Arr.of(makePart("text", {
text
})),
encode: content => content[0].text
})));
/**
* Schema for validation and encoding of system messages.
*
* @category schemas
* @since 4.0.0
*/
export const SystemMessage = /*#__PURE__*/Schema.Struct({
...BaseMessage.fields,
role: Schema.Literal("system"),
content: Schema.String
}).annotate({
identifier: "SystemMessage"
});
/**
* Constructs a new system message.
*
* @category constructors
* @since 4.0.0
*/
export const systemMessage = params => makeMessage("system", params);
/**
* Schema for validation and encoding of user messages.
*
* @category schemas
* @since 4.0.0
*/
export const UserMessage = /*#__PURE__*/Schema.Struct({
...BaseMessage.fields,
role: Schema.Literal("user"),
content: Schema.Union([ContentFromString, Schema.Array(Schema.Union([TextPart, FilePart]))])
}).annotate({
identifier: "UserMessage"
});
/**
* Constructs a new user message.
*
* @category constructors
* @since 4.0.0
*/
export const userMessage = params => makeMessage("user", params);
/**
* Schema for validation and encoding of assistant messages.
*
* **Details**
*
* Assistant content can be a string decoded through `ContentFromString` or an
* array of text, file, reasoning, tool-call, tool-result, and
* tool-approval-request parts.
*
* @category schemas
* @since 4.0.0
*/
export const AssistantMessage = /*#__PURE__*/Schema.Struct({
...BaseMessage.fields,
role: Schema.Literal("assistant"),
content: Schema.Union([ContentFromString, Schema.Array(Schema.Union([TextPart, FilePart, ReasoningPart, ToolCallPart, ToolResultPart, ToolApprovalRequestPart]))])
}).annotate({
identifier: "AssistantMessage"
});
/**
* Constructs a new assistant message.
*
* **When to use**
*
* Use to add assistant-role prompt history or model responses.
*
* **Details**
*
* This is the role-specific wrapper around `makeMessage("assistant", params)`.
*
* @category constructors
* @since 4.0.0
*/
export const assistantMessage = params => makeMessage("assistant", params);
/**
* Schema for validation and encoding of tool messages.
*
* @category schemas
* @since 4.0.0
*/
export const ToolMessage = /*#__PURE__*/Schema.Struct({
...BaseMessage.fields,
role: Schema.Literal("tool"),
content: Schema.Array(Schema.Union([ToolResultPart, ToolApprovalResponsePart]))
}).annotate({
identifier: "ToolMessage"
});
/**
* Constructs a new tool message.
*
* @category constructors
* @since 4.0.0
*/
export const toolMessage = params => makeMessage("tool", params);
/**
* Schema for validation and encoding of messages.
*
* @category schemas
* @since 4.0.0
*/
export const Message = /*#__PURE__*/Schema.Union([SystemMessage, UserMessage, AssistantMessage, ToolMessage]);
// =============================================================================
// Prompt
// =============================================================================
const TypeId = "~effect/unstable/ai/Prompt";
/**
* Type guard to check if a value is a Prompt.
*
* @category guards
* @since 4.0.0
*/
export const isPrompt = u => Predicate.hasProperty(u, TypeId);
const $Prompt = /*#__PURE__*/Schema.declare(u => isPrompt(u), {
identifier: "Prompt"
});
// TODO: is the type annotation necessary?
// TODO: shoudn't the name be `PromptFrom...`?
// TODO: is the explicit encoding necessary? maybe use the default JSON serializer?
/**
* Schema for AI prompt instances.
*
* @category schemas
* @since 4.0.0
*/
export const Prompt = /*#__PURE__*/Schema.Struct({
content: Schema.Array(Schema.toEncoded(Message))
}).pipe(/*#__PURE__*/Schema.decodeTo($Prompt, /*#__PURE__*/SchemaTransformation.transformOrFail({
decode: input => Effect.mapBothEager(SchemaParser.decodeEffect(Schema.Array(Message))(input.content), {
onSuccess: makePrompt,
onFailure: () => new SchemaIssue.InvalidValue(Option.some(input.content), {
message: "Invalid Prompt messages"
})
}),
encode: prompt => Effect.mapBothEager(SchemaParser.encodeEffect(Schema.Array(Message))(prompt.content), {
onSuccess: messages => ({
content: messages
}),
onFailure: () => new SchemaIssue.InvalidValue(Option.some(prompt.content), {
message: "Invalid Prompt messages"
})
})
})));
const Proto = {
[TypeId]: TypeId,
pipe() {
return pipeArguments(this, arguments);
}
};
const makePrompt = content => Object.assign(Object.create(Proto), {
content
});
const decodeMessagesSync = /*#__PURE__*/Schema.decodeSync(/*#__PURE__*/Schema.Array(Message));
/**
* An empty prompt with no messages.
*
* **Example** (Creating an empty prompt)
*
* ```ts
* import { Prompt } from "effect/unstable/ai"
*
* const emptyPrompt = Prompt.empty
* console.log(emptyPrompt.content) // []
* ```
*
* @category constructors
* @since 4.0.0
*/
export const empty = /*#__PURE__*/makePrompt([]);
/**
* Creates a `Prompt` from an input.
*
* **Details**
*
* This is the primary constructor for creating prompts, supporting multiple
* input formats for convenience and flexibility.
*
* **Example** (Creating prompts from inputs)
*
* ```ts
* import { Prompt } from "effect/unstable/ai"
*
* // From string - creates a user message
* const textPrompt = Prompt.make("Hello, how are you?")
*
* // From messages array
* const structuredPrompt = Prompt.make([
* { role: "system", content: "You are a helpful assistant." },
* { role: "user", content: [{ type: "text", text: "Hi!" }] }
* ])
*
* // From existing prompt
* declare const existingPrompt: Prompt.Prompt
* const copiedPrompt = Prompt.make(existingPrompt)
* ```
*
* @category constructors
* @since 4.0.0
*/
export const make = input => {
if (typeof input === "string") {
const part = makePart("text", {
text: input
});
const message = makeMessage("user", {
content: [part]
});
return makePrompt([message]);
}
if (Predicate.isIterable(input)) {
return makePrompt(decodeMessagesSync(Arr.fromIterable(input), {
errors: "all"
}));
}
return input;
};
/**
* Creates a Prompt from an array of messages.
*
* **Example** (Creating prompts from messages)
*
* ```ts
* import { Prompt } from "effect/unstable/ai"
*
* const messages: ReadonlyArray<Prompt.Message> = [
* Prompt.makeMessage("system", {
* content: "You are a coding assistant."
* }),
* Prompt.makeMessage("user", {
* content: [Prompt.makePart("text", { text: "Help me with TypeScript" })]
* })
* ]
*
* const prompt = Prompt.fromMessages(messages)
* ```
*
* @category constructors
* @since 4.0.0
*/
export const fromMessages = messages => makePrompt(messages);
/**
* Creates a `Prompt` from response parts by folding completed text and
* reasoning streams into assistant parts, placing tool calls and approval
* requests in an assistant message, and placing non-preliminary tool results
* in a tool message using their encoded results.
*
* **Example** (Creating prompts from response parts)
*
* ```ts
* import { Prompt, Response } from "effect/unstable/ai"
*
* const responseParts: ReadonlyArray<Response.AnyPart> = [
* Response.makePart("text", {
* text: "Hello there!"
* }),
* Response.makePart("tool-call", {
* id: "call_1",
* name: "get_time",
* params: {},
* providerExecuted: false
* }),
* Response.makePart("tool-result", {
* id: "call_1",
* name: "get_time",
* isFailure: false,
* result: "10:30 AM",
* encodedResult: "10:30 AM",
* providerExecuted: false,
* preliminary: false
* })
* ]
*
* const prompt = Prompt.fromResponseParts(responseParts)
* // Creates an assistant message with the response content
* ```
*
* @category constructors
* @since 4.0.0
*/
export const fromResponseParts = parts => {
if (parts.length === 0) {
return empty;
}
const assistantParts = [];
const toolParts = [];
const activeTextDeltas = new Map();
const activeReasoningDeltas = new Map();
for (const part of parts) {
switch (part.type) {
// Text Parts
case "text":
{
assistantParts.push(makePart("text", {
text: part.text
}));
break;
}
// Text Parts (streaming)
case "text-start":
{
activeTextDeltas.set(part.id, {
text: ""
});
break;
}
case "text-delta":
{
if (activeTextDeltas.has(part.id)) {
activeTextDeltas.get(part.id).text += part.delta;
}
break;
}
case "text-end":
{
if (activeTextDeltas.has(part.id)) {
assistantParts.push(makePart("text", activeTextDeltas.get(part.id)));
}
break;
}
// Reasoning Parts
case "reasoning":
{
assistantParts.push(makePart("reasoning", {
text: part.text
}));
break;
}
// Reasoning Parts (streaming)
case "reasoning-start":
{
activeReasoningDeltas.set(part.id, {
text: ""
});
break;
}
case "reasoning-delta":
{
if (activeReasoningDeltas.has(part.id)) {
activeReasoningDeltas.get(part.id).text += part.delta;
}
break;
}
case "reasoning-end":
{
if (activeReasoningDeltas.has(part.id)) {
assistantParts.push(makePart("reasoning", activeReasoningDeltas.get(part.id)));
}
break;
}
// Tool Call Parts
case "tool-call":
{
assistantParts.push(makePart("tool-call", {
id: part.id,
name: part.name,
params: part.params,
providerExecuted: part.providerExecuted ?? false
}));
break;
}
// Tool Result Parts (skip preliminary results)
case "tool-result":
{
if (part.preliminary !== true) {
toolParts.push(makePart("tool-result", {
id: part.id,
name: part.name,
isFailure: part.isFailure,
result: part.encodedResult
}));
}
break;
}
// Tool Approval Request Parts
case "tool-approval-request":
{
assistantParts.push(makePart("tool-approval-request", {
approvalId: part.approvalId,
toolCallId: part.toolCallId
}));
break;
}
}
}
if (assistantParts.length === 0 && toolParts.length === 0) {
return empty;
}
const messages = [];
if (assistantParts.length > 0) {
messages.push(makeMessage("assistant", {
content: assistantParts
}));
}
if (toolParts.length > 0) {
messages.push(makeMessage("tool", {
content: toolParts
}));
}
return makePrompt(messages);
};
// =============================================================================
// Merging Prompts
// =============================================================================
/**
* Concatenates a prompt with additional raw input by concatenating messages.
*
* **Details**
*
* The returned prompt contains all messages from the original prompt followed
* by the provided raw input, preserving message order.
*
* **Example** (Concatenating prompts)
*
* ```ts
* import { Prompt } from "effect/unstable/ai"
*
* const systemPrompt = Prompt.make([{
* role: "system",
* content: "You are a helpful assistant."
* }])
*
* const merged = Prompt.concat(systemPrompt, "Hello, world!")
* ```
*
* @category combinators
* @since 4.0.0
*/
export const concat = /*#__PURE__*/dual(2, (self, input) => {
const other = make(input);
if (self.content.length === 0) {
return other;
}
if (other.content.length === 0) {
return self;
}
return fromMessages([...self.content, ...other.content]);
});
// =============================================================================
// Manipulating Prompts
// =============================================================================
/**
* Creates a new prompt from the specified prompt with the system message set
* to the specified text content.
*
* **Gotchas**
*
* This method removes and replaces any previous system message from the
* prompt.
*
* **Example** (Replacing system instructions)
*
* ```ts
* import { Prompt } from "effect/unstable/ai"
*
* const systemPrompt = Prompt.make([{
* role: "system",
* content: "You are a helpful assistant."
* }])
*
* const userPrompt = Prompt.make("Hello, world!")
*
* const prompt = Prompt.concat(systemPrompt, userPrompt)
*
* const replaced = Prompt.setSystem(
* prompt,
* "You are an expert in programming"
* )
* ```
*
* @category combinators
* @since 4.0.0
*/
export const setSystem = /*#__PURE__*/dual(2, (self, content) => {
const messages = [makeMessage("system", {
content
})];
for (const message of self.content) {
if (message.role !== "system") {
messages.push(message);
}
}
return makePrompt(messages);
});
/**
* Creates a new prompt with a leading system message. If the prompt already has
* a system message, the new message uses the provided content prepended to the
* first existing system message's content; the original messages remain after
* it.
*
* **Example** (Prepending system instructions)
*
* ```ts
* import { Prompt } from "effect/unstable/ai"
*
* const systemPrompt = Prompt.make([{
* role: "system",
* content: "You are an expert in programming."
* }])
*
* const userPrompt = Prompt.make("Hello, world!")
*
* const prompt = Prompt.concat(systemPrompt, userPrompt)
*
* const replaced = Prompt.prependSystem(
* prompt,
* "You are a helpful assistant. "
* )
* // result content: "You are a helpful assistant. You are an expert in programming."
* ```
*
* @category combinators
* @since 4.0.0
*/
export const prependSystem = /*#__PURE__*/dual(2, (self, content) => {
let system = undefined;
for (const message of self.content) {
if (message.role === "system") {
system = makeMessage("system", {
content: content + message.content
});
break;
}
}
if (Predicate.isUndefined(system)) {
system = makeMessage("system", {
content
});
}
return makePrompt([system, ...self.content]);
});
/**
* Creates a new prompt with a leading system message. If the prompt already has
* a system message, the new message uses the provided content appended to the
* first existing system message's content; the original messages remain after
* it.
*
* **Example** (Appending system instructions)
*
* ```ts
* import { Prompt } from "effect/unstable/ai"
*
* const systemPrompt = Prompt.make([{
* role: "system",
* content: "You are an expert in programming."
* }])
*
* const userPrompt = Prompt.make("Hello, world!")
*
* const prompt = Prompt.concat(systemPrompt, userPrompt)
*
* const replaced = Prompt.appendSystem(
* prompt,
* " You are a helpful assistant."
* )
* // result content: "You are an expert in programming. You are a helpful assistant."
* ```
*
* @category combinators
* @since 4.0.0
*/
export const appendSystem = /*#__PURE__*/dual(2, (self, content) => {
let system = undefined;
for (const message of self.content) {
if (message.role === "system") {
system = makeMessage("system", {
content: message.content + content
});
break;
}
}
if (Predicate.isUndefined(system)) {
system = makeMessage("system", {
content
});
}
return makePrompt([system, ...self.content]);
});
//# sourceMappingURL=Prompt.js.map

Xet Storage Details

Size:
24.2 kB
·
Xet hash:
9e6278d49835b2ef615bb99637b4760ca7fcf008b0791eb751a68e1d6d636b53

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.