effect-assistant 0.3.0 → 0.4.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/dist/Assistant.d.ts +5 -11
- package/dist/Assistant.d.ts.map +1 -1
- package/dist/Assistant.js.map +1 -1
- package/dist/AssistantEvent.d.ts +55 -7
- package/dist/AssistantEvent.d.ts.map +1 -1
- package/dist/AssistantEvent.js +61 -7
- package/dist/AssistantEvent.js.map +1 -1
- package/dist/AssistantLoop.d.ts +4 -2
- package/dist/AssistantLoop.d.ts.map +1 -1
- package/dist/AssistantLoop.js +4 -2
- package/dist/AssistantLoop.js.map +1 -1
- package/dist/Compaction.d.ts +1 -1
- package/dist/Compaction.d.ts.map +1 -1
- package/dist/Compaction.js +13 -6
- package/dist/Compaction.js.map +1 -1
- package/dist/Skill.d.ts +85 -43
- package/dist/Skill.d.ts.map +1 -1
- package/dist/Skill.js +93 -44
- package/dist/Skill.js.map +1 -1
- package/dist/Specialist.d.ts +6 -9
- package/dist/Specialist.d.ts.map +1 -1
- package/dist/Specialist.js.map +1 -1
- package/dist/Timeline.d.ts +40 -6
- package/dist/Timeline.d.ts.map +1 -1
- package/dist/Timeline.js +74 -15
- package/dist/Timeline.js.map +1 -1
- package/dist/ToolApproval.d.ts +146 -0
- package/dist/ToolApproval.d.ts.map +1 -0
- package/dist/ToolApproval.js +201 -0
- package/dist/ToolApproval.js.map +1 -0
- package/dist/ToolDiscovery.d.ts +6 -4
- package/dist/ToolDiscovery.d.ts.map +1 -1
- package/dist/ToolDiscovery.js +5 -4
- package/dist/ToolDiscovery.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- package/dist/internal/chat.js +4 -1
- package/dist/internal/chat.js.map +1 -1
- package/dist/internal/history.js +28 -3
- package/dist/internal/history.js.map +1 -1
- package/dist/internal/loop.d.ts +10 -5
- package/dist/internal/loop.d.ts.map +1 -1
- package/dist/internal/loop.js +54 -21
- package/dist/internal/loop.js.map +1 -1
- package/dist/internal/specialists.js +9 -16
- package/dist/internal/specialists.js.map +1 -1
- package/dist/internal/toolkit.d.ts +5 -7
- package/dist/internal/toolkit.d.ts.map +1 -1
- package/dist/internal/toolkit.js +28 -18
- package/dist/internal/toolkit.js.map +1 -1
- package/dist/internal/turns.d.ts.map +1 -1
- package/dist/internal/turns.js +18 -13
- package/dist/internal/turns.js.map +1 -1
- package/package.json +1 -1
- package/src/Assistant.ts +7 -13
- package/src/AssistantEvent.ts +60 -6
- package/src/AssistantLoop.ts +4 -2
- package/src/Compaction.ts +19 -2
- package/src/Skill.ts +134 -57
- package/src/Specialist.ts +8 -11
- package/src/Timeline.ts +104 -18
- package/src/ToolApproval.ts +249 -0
- package/src/ToolDiscovery.ts +8 -3
- package/src/index.ts +5 -0
- package/src/internal/chat.ts +11 -1
- package/src/internal/history.ts +58 -3
- package/src/internal/loop.ts +120 -34
- package/src/internal/specialists.ts +7 -13
- package/src/internal/toolkit.ts +47 -30
- package/src/internal/turns.ts +24 -7
- package/dist/internal/skills.js +0 -33
- package/dist/internal/skills.js.map +0 -1
- package/src/internal/skills.ts +0 -48
package/dist/AssistantLoop.js
CHANGED
|
@@ -61,8 +61,10 @@ const toolsOf = (toolkit) => Effect.map(TurnTools, (turn) => {
|
|
|
61
61
|
* the step's result and decides what comes next, with `AssistantLoop.next(state)` or
|
|
62
62
|
* `AssistantLoop.stop()`.
|
|
63
63
|
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
64
|
+
* In a turn's loop, a call to a tool with Effect's `needsApproval` doesn't run: the step emits an
|
|
65
|
+
* `ApprovalRequested` for it, and ends the turn, which waits for the user (see `ToolApproval`). A
|
|
66
|
+
* call the user approved runs at the start of the next step, before the model is called. A
|
|
67
|
+
* specialist's run can't wait for the user: such a call fails, and the run reads why.
|
|
66
68
|
*
|
|
67
69
|
* @category constructors
|
|
68
70
|
* @since 0.1.0
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AssistantLoop.js","names":["Loop.value","Loop.next","Loop.stop","Loop.loop","Loop.TurnTools","Toolkits.empty","Toolkits.fromToolkit","Toolkits.merge","Loop.step","Loop.run"],"sources":["../src/AssistantLoop.ts"],"sourcesContent":["/**\n * Runs the agent loop, which calls the model, runs its tool calls, and repeats.\n *\n * Most handlers use `turn.loop` or `run.loop`. This module is for a loop of your own: {@link run}\n * runs the loop until the model is done, and {@link step} is one step, as a body for\n * {@link loop}.\n *\n * Tools are a native toolkit (`effect/ai/Toolkit`) whose handlers come from the handler's layer.\n * In a turn, the assistant's own tools (`send_user_message`, `wait`, and the specialist tools)\n * are added to them.\n *\n * @since 0.1.0\n */\nimport type * as AiError from \"effect/ai/AiError\"\nimport type * as LanguageModel from \"effect/ai/LanguageModel\"\nimport type * as Prompt from \"effect/ai/Prompt\"\nimport type * as Tool from \"effect/ai/Tool\"\nimport type * as Toolkit from \"effect/ai/Toolkit\"\nimport * as Effect from \"effect/Effect\"\nimport * as Option from \"effect/Option\"\nimport * as Stream from \"effect/Stream\"\nimport type * as Instructions from \"./Instructions.ts\"\nimport * as Loop from \"./internal/loop.ts\"\nimport * as Toolkits from \"./internal/toolkit.ts\"\n\n/**\n * Represents what the loop emits.\n *\n * For each model call, the loop emits the call's output. Then, for each of its tool calls, it\n * emits a `ToolStarted` before the call runs and the call's result.\n *\n * @category models\n * @since 0.1.0\n */\nexport type Event = Loop.Event\n\n/**\n * Represents a chat's history as the model reads it.\n *\n * It holds the messages, and the last `seq` of the chat they cover.\n *\n * @category models\n * @since 0.1.0\n */\nexport type ChatHistory = Loop.ChatHistory\n\n/**\n * Represents how a step went, for deciding what comes next.\n *\n * @category models\n * @since 0.1.0\n */\nexport type StepResult = Loop.StepResult\n\n/**\n * Represents what a loop body emits, which is either a value or what to do next.\n *\n * @category models\n * @since 0.1.0\n */\nexport type Step<A, S> = Loop.Step<A, S>\n\n/**\n * Emits a value from a loop body.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const value = Loop.value\n\n/**\n * Ends a loop body's step and continues the loop with the given state.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const next = Loop.next\n\n/**\n * Ends the loop.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const stop = Loop.stop\n\n/**\n * Runs a loop body from an initial state.\n *\n * It emits the body's values, and runs the body again with the state it passes to `next`, until\n * the body calls `stop`.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const loop = Loop.loop\n\ntype NoTools = Record<never, never>\n\n/**\n * Extracts the services a toolkit's tools need when they run, which are their handlers and the\n * services those handlers use.\n *\n * @category utility types\n * @since 0.1.0\n */\nexport type ToolkitServices<T extends Record<string, Tool.Any>> = Tool.HandlersFor<T> | Toolkits.ToolServices<T>\n\n/**\n * Represents the options for {@link step}.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface StepOptions<T extends Record<string, Tool.Any> = NoTools> {\n /** The full system prompt, like `turn.instructions(text)`, optionally with span attributes. */\n readonly instructions: string | Instructions.Resolved\n /**\n * The tools the model can call, in addition to the assistant's own tools in a turn. Their\n * handlers come from the layer.\n */\n readonly toolkit?: Toolkit.Toolkit<T> | undefined\n /**\n * The current history, read at the start of the step. It is either messages, or a chat's\n * history (`turn.history()`), whose `throughSeq` the step's `ModelOutput` records as how far\n * the model had read.\n */\n readonly history: Effect.Effect<ReadonlyArray<Prompt.Message> | ChatHistory>\n /**\n * Defaults to `\"auto\"`, where a step without a tool call ends the loop. Forcing a call\n * (`\"required\"`, or a tool by name) is refused by newer models, and with extended thinking.\n */\n readonly toolChoice?: LanguageModel.ToolChoice<string> | undefined\n}\n\n/**\n * Represents the options for {@link run}.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Options<T extends Record<string, Tool.Any> = NoTools> extends StepOptions<T> {\n /** The model calls allowed. Defaults to 16. */\n readonly maxSteps?: number | undefined\n}\n\nconst toolsOf = <T extends Record<string, Tool.Any>>(toolkit: Toolkit.Toolkit<T> | undefined) =>\n Effect.map(Loop.TurnTools, (turn) => {\n const own = toolkit === undefined ? Toolkits.empty : Toolkits.fromToolkit(toolkit)\n\n return Option.match(turn, { onNone: () => own, onSome: (builtins) => Toolkits.merge(builtins, own) })\n })\n\n/**\n * Runs one step of the agent loop, as a body for {@link loop}.\n *\n * **Details**\n *\n * The step calls the model with the current history and emits its `ModelOutput`. It then runs\n * the output's tool calls and emits a `ToolStarted` and a `ToolResult` for each. `then` receives\n * the step's result and decides what comes next, with `AssistantLoop.next(state)` or\n * `AssistantLoop.stop()`.\n *\n *
|
|
1
|
+
{"version":3,"file":"AssistantLoop.js","names":["Loop.value","Loop.next","Loop.stop","Loop.loop","Loop.TurnTools","Toolkits.empty","Toolkits.fromToolkit","Toolkits.merge","Loop.step","Loop.run"],"sources":["../src/AssistantLoop.ts"],"sourcesContent":["/**\n * Runs the agent loop, which calls the model, runs its tool calls, and repeats.\n *\n * Most handlers use `turn.loop` or `run.loop`. This module is for a loop of your own: {@link run}\n * runs the loop until the model is done, and {@link step} is one step, as a body for\n * {@link loop}.\n *\n * Tools are a native toolkit (`effect/ai/Toolkit`) whose handlers come from the handler's layer.\n * In a turn, the assistant's own tools (`send_user_message`, `wait`, and the specialist tools)\n * are added to them.\n *\n * @since 0.1.0\n */\nimport type * as AiError from \"effect/ai/AiError\"\nimport type * as LanguageModel from \"effect/ai/LanguageModel\"\nimport type * as Prompt from \"effect/ai/Prompt\"\nimport type * as Tool from \"effect/ai/Tool\"\nimport type * as Toolkit from \"effect/ai/Toolkit\"\nimport * as Effect from \"effect/Effect\"\nimport * as Option from \"effect/Option\"\nimport * as Stream from \"effect/Stream\"\nimport type * as Instructions from \"./Instructions.ts\"\nimport * as Loop from \"./internal/loop.ts\"\nimport * as Toolkits from \"./internal/toolkit.ts\"\n\n/**\n * Represents what the loop emits.\n *\n * For each model call, the loop emits the call's output. Then, for each of its tool calls, it\n * emits a `ToolStarted` before the call runs and the call's result.\n *\n * @category models\n * @since 0.1.0\n */\nexport type Event = Loop.Event\n\n/**\n * Represents a chat's history as the model reads it.\n *\n * It holds the messages, and the last `seq` of the chat they cover.\n *\n * @category models\n * @since 0.1.0\n */\nexport type ChatHistory = Loop.ChatHistory\n\n/**\n * Represents how a step went, for deciding what comes next.\n *\n * @category models\n * @since 0.1.0\n */\nexport type StepResult = Loop.StepResult\n\n/**\n * Represents what a loop body emits, which is either a value or what to do next.\n *\n * @category models\n * @since 0.1.0\n */\nexport type Step<A, S> = Loop.Step<A, S>\n\n/**\n * Emits a value from a loop body.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const value = Loop.value\n\n/**\n * Ends a loop body's step and continues the loop with the given state.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const next = Loop.next\n\n/**\n * Ends the loop.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const stop = Loop.stop\n\n/**\n * Runs a loop body from an initial state.\n *\n * It emits the body's values, and runs the body again with the state it passes to `next`, until\n * the body calls `stop`.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const loop = Loop.loop\n\ntype NoTools = Record<never, never>\n\n/**\n * Extracts the services a toolkit's tools need when they run, which are their handlers and the\n * services those handlers use.\n *\n * @category utility types\n * @since 0.1.0\n */\nexport type ToolkitServices<T extends Record<string, Tool.Any>> = Tool.HandlersFor<T> | Toolkits.ToolServices<T>\n\n/**\n * Represents the options for {@link step}.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface StepOptions<T extends Record<string, Tool.Any> = NoTools> {\n /** The full system prompt, like `turn.instructions(text)`, optionally with span attributes. */\n readonly instructions: string | Instructions.Resolved\n /**\n * The tools the model can call, in addition to the assistant's own tools in a turn. Their\n * handlers come from the layer.\n */\n readonly toolkit?: Toolkit.Toolkit<T> | undefined\n /**\n * The current history, read at the start of the step. It is either messages, or a chat's\n * history (`turn.history()`), whose `throughSeq` the step's `ModelOutput` records as how far\n * the model had read.\n */\n readonly history: Effect.Effect<ReadonlyArray<Prompt.Message> | ChatHistory>\n /**\n * Defaults to `\"auto\"`, where a step without a tool call ends the loop. Forcing a call\n * (`\"required\"`, or a tool by name) is refused by newer models, and with extended thinking.\n */\n readonly toolChoice?: LanguageModel.ToolChoice<string> | undefined\n}\n\n/**\n * Represents the options for {@link run}.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Options<T extends Record<string, Tool.Any> = NoTools> extends StepOptions<T> {\n /** The model calls allowed. Defaults to 16. */\n readonly maxSteps?: number | undefined\n}\n\nconst toolsOf = <T extends Record<string, Tool.Any>>(toolkit: Toolkit.Toolkit<T> | undefined) =>\n Effect.map(Loop.TurnTools, (turn) => {\n const own = toolkit === undefined ? Toolkits.empty : Toolkits.fromToolkit(toolkit)\n\n return Option.match(turn, { onNone: () => own, onSome: (builtins) => Toolkits.merge(builtins, own) })\n })\n\n/**\n * Runs one step of the agent loop, as a body for {@link loop}.\n *\n * **Details**\n *\n * The step calls the model with the current history and emits its `ModelOutput`. It then runs\n * the output's tool calls and emits a `ToolStarted` and a `ToolResult` for each. `then` receives\n * the step's result and decides what comes next, with `AssistantLoop.next(state)` or\n * `AssistantLoop.stop()`.\n *\n * In a turn's loop, a call to a tool with Effect's `needsApproval` doesn't run: the step emits an\n * `ApprovalRequested` for it, and ends the turn, which waits for the user (see `ToolApproval`). A\n * call the user approved runs at the start of the next step, before the model is called. A\n * specialist's run can't wait for the user: such a call fails, and the run reads why.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const step = <T extends Record<string, Tool.Any> = NoTools, S = never, E2 = never, R2 = never>(\n options: StepOptions<T>,\n then: (result: StepResult) => Stream.Stream<Step<never, S>, E2, R2>\n): Stream.Stream<Step<Event, S>, AiError.AiError | E2, LanguageModel.LanguageModel | ToolkitServices<T> | R2> =>\n Stream.unwrap(Effect.map(toolsOf(options.toolkit), (toolkit) => Loop.step({ ...options, toolkit }, then)))\n\n/**\n * Runs the agent loop as a stream.\n *\n * **Details**\n *\n * It runs {@link step} after step until the model is done and nothing new arrived while it\n * worked, or until it has made `maxSteps` model calls.\n *\n * **Example** (A turn's loop with a step limit)\n *\n * ```ts\n * import { AssistantLoop } from \"effect-assistant\"\n * import * as Layer from \"effect/Layer\"\n * import * as Stream from \"effect/Stream\"\n *\n * const instructions = \"You help a small marketing team.\"\n *\n * const MyAssistantLive = MyAssistant.toLayer((turn) =>\n * AssistantLoop.run({\n * instructions: turn.instructions(instructions),\n * toolkit: MyTools, // the assistant's own tools are added to them\n * history: turn.history(),\n * maxSteps: 12\n * }).pipe(Stream.concat(turn.replyIfOwed({ instructions })))\n * ).pipe(Layer.provide(MyToolsLive))\n * ```\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const run = <T extends Record<string, Tool.Any> = NoTools>(\n options: Options<T>\n): Stream.Stream<Event, AiError.AiError, LanguageModel.LanguageModel | ToolkitServices<T>> =>\n Stream.unwrap(Effect.map(toolsOf(options.toolkit), (toolkit) => Loop.run({ ...options, toolkit })))\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAoEA,MAAa,QAAQA;;;;;;;AAQrB,MAAa,OAAOC;;;;;;;AAQpB,MAAa,OAAOC;;;;;;;;;;AAWpB,MAAa,OAAOC;AAmDpB,MAAM,WAA+C,YACnD,OAAO,IAAIC,YAAiB,SAAS;CACnC,MAAM,MAAM,YAAY,KAAA,IAAYC,QAAiBC,YAAqB,OAAO;CAEjF,OAAO,OAAO,MAAM,MAAM;EAAE,cAAc;EAAK,SAAS,aAAaC,MAAe,UAAU,GAAG;CAAE,CAAC;AACtG,CAAC;;;;;;;;;;;;;;;;;;;AAoBH,MAAa,QACX,SACA,SAEA,OAAO,OAAO,OAAO,IAAI,QAAQ,QAAQ,OAAO,IAAI,YAAYC,OAAU;CAAE,GAAG;CAAS;AAAQ,GAAG,IAAI,CAAC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgC3G,MAAa,OACX,YAEA,OAAO,OAAO,OAAO,IAAI,QAAQ,QAAQ,OAAO,IAAI,YAAYC,MAAS;CAAE,GAAG;CAAS;AAAQ,CAAC,CAAC,CAAC"}
|
package/dist/Compaction.d.ts
CHANGED
|
@@ -29,7 +29,7 @@ export interface Summary {
|
|
|
29
29
|
export declare const inputTokens: (entries: ReadonlyArray<AnyEntry>) => number;
|
|
30
30
|
/**
|
|
31
31
|
* Returns the items that were open at `throughSeq` and are still open: blocks the user hasn't
|
|
32
|
-
* answered,
|
|
32
|
+
* answered, specialists that are working, and tool calls waiting for the user's approval. The result is text for
|
|
33
33
|
* the model, or `None` when nothing is open.
|
|
34
34
|
*
|
|
35
35
|
* @category utils
|
package/dist/Compaction.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Compaction.d.ts","names":[],"sources":["../src/Compaction.ts"],"mappings":";;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"Compaction.d.ts","names":[],"sources":["../src/Compaction.ts"],"mappings":";;;;;;;;;;;;;;;;iBAgCiB;WACN;WACA;WACA;;;;;;;;;qBAUE,cAAW,SAAa,cAAc;;;;;;;;;qBAmBtC,YAAS,SACX,cAAc,WAAwB,uBAE9C,OAAO;;;;;;;;qBAmEG,gBAAa,SACf,cAAc,WAAwB,uBAE9C,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAmEG,QAAK,YACH,mBAAmB,SAAS,aACxC;WAA0B;GAAc,SAC9B,cAAc,MAAgB,aACtC,cAAc,MAAgB,UAAU,OAAO"}
|
package/dist/Compaction.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { __exportAll } from "./_virtual/_rolldown/runtime.js";
|
|
2
2
|
import { attachmentLine, isLibraryEvent } from "./AssistantEvent.js";
|
|
3
|
+
import { fromEntries } from "./ToolApproval.js";
|
|
4
|
+
import { toJsonText } from "./internal/schema.js";
|
|
3
5
|
import { userText } from "./internal/history.js";
|
|
4
|
-
import { fromEntries } from "./AssistantActivity.js";
|
|
5
|
-
import { fromEntries as fromEntries$
|
|
6
|
+
import { fromEntries as fromEntries$1 } from "./AssistantActivity.js";
|
|
7
|
+
import { fromEntries as fromEntries$2 } from "./BlockKit.js";
|
|
6
8
|
import * as Predicate from "effect/Predicate";
|
|
7
9
|
import * as Option from "effect/Option";
|
|
8
10
|
//#region src/Compaction.ts
|
|
@@ -25,7 +27,7 @@ const inputTokens = (entries) => {
|
|
|
25
27
|
};
|
|
26
28
|
/**
|
|
27
29
|
* Returns the items that were open at `throughSeq` and are still open: blocks the user hasn't
|
|
28
|
-
* answered,
|
|
30
|
+
* answered, specialists that are working, and tool calls waiting for the user's approval. The result is text for
|
|
29
31
|
* the model, or `None` when nothing is open.
|
|
30
32
|
*
|
|
31
33
|
* @category utils
|
|
@@ -34,13 +36,18 @@ const inputTokens = (entries) => {
|
|
|
34
36
|
const openItems = (entries, throughSeq) => {
|
|
35
37
|
const seqOf = (found) => entries.find(({ event }) => found(event))?.seq ?? Number.POSITIVE_INFINITY;
|
|
36
38
|
const opened = (seq) => seq <= throughSeq;
|
|
37
|
-
const blocks = [...fromEntries$
|
|
39
|
+
const blocks = [...fromEntries$2(entries)].flatMap(([messageId, state]) => {
|
|
38
40
|
const shown = entries.find(({ event }) => isLibraryEvent(event) && Predicate.isTagged(event, "AssistantMessage") && event.id === messageId);
|
|
39
41
|
const message = shown?.event;
|
|
40
42
|
return state.open && shown !== void 0 && opened(shown.seq) && message !== void 0 && isLibraryEvent(message) && Predicate.isTagged(message, "AssistantMessage") ? [`- message ${messageId}, a ${message.block?.kind} block: ${message.text}`] : [];
|
|
41
43
|
});
|
|
42
|
-
const specialists = [...fromEntries(entries).specialists.values()].flatMap((run) => opened(seqOf((event) => isLibraryEvent(event) && Predicate.isTagged(event, "SpecialistStarted") && event.runId === run.runId)) ? [`- your ${run.specialist} specialist (${run.runId}), working on: ${run.goal}`] : []);
|
|
43
|
-
const
|
|
44
|
+
const specialists = [...fromEntries$1(entries).specialists.values()].flatMap((run) => opened(seqOf((event) => isLibraryEvent(event) && Predicate.isTagged(event, "SpecialistStarted") && event.runId === run.runId)) ? [`- your ${run.specialist} specialist (${run.runId}), working on: ${run.goal}`] : []);
|
|
45
|
+
const approvals = [...fromEntries(entries).values()].flatMap((approval) => approval.status === "pending" && opened(seqOf((event) => isLibraryEvent(event) && Predicate.isTagged(event, "ApprovalRequested") && event.callId === approval.request.callId)) ? [`- your call to ${approval.request.tool} (${approval.request.callId}): ${toJsonText(approval.request.params)}`] : []);
|
|
46
|
+
const sections = [
|
|
47
|
+
blocks.length > 0 ? `Blocks the user hasn't answered:\n${blocks.join("\n")}` : "",
|
|
48
|
+
specialists.length > 0 ? `Specialists still working:\n${specialists.join("\n")}` : "",
|
|
49
|
+
approvals.length > 0 ? `Calls waiting for the user's approval:\n${approvals.join("\n")}` : ""
|
|
50
|
+
].filter((section) => section.length > 0);
|
|
44
51
|
return sections.length === 0 ? Option.none() : Option.some(sections.join("\n\n"));
|
|
45
52
|
};
|
|
46
53
|
/**
|
package/dist/Compaction.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Compaction.js","names":["AssistantEvent.isLibraryEvent","BlockKit.fromEntries","AssistantActivity.fromEntries","AssistantEvent.attachmentLine"],"sources":["../src/Compaction.ts"],"sourcesContent":["/**\n * This module provides what a long chat needs so that the model can read a summary in place of\n * everything before it.\n *\n * The app decides when to compact, which model writes the summary, and what the summary says.\n * Its handler reads the chat's entries, has a model write a summary, and stores the summary as one\n * of the assistant's own events (see {@link Summary}). Its loop then reads the chat through\n * {@link since}. {@link inputTokens} helps decide when the prompt has grown large enough to\n * compact. The log keeps every entry, so the user still sees the whole chat.\n *\n * @since 0.1.0\n */\nimport type * as Prompt from \"effect/ai/Prompt\"\nimport * as Option from \"effect/Option\"\nimport * as Predicate from \"effect/Predicate\"\nimport type * as Assistant from \"./Assistant.ts\"\nimport * as AssistantActivity from \"./AssistantActivity.ts\"\nimport * as AssistantEvent from \"./AssistantEvent.ts\"\nimport * as BlockKit from \"./BlockKit.ts\"\nimport { userText } from \"./internal/history.ts\"\n\n/**\n * Represents the fields a summary event must have: its text and the last `seq` it covers.\n *\n * Entries after `throughSeq` are kept as they are, so an input that arrived while the summary was\n * being written isn't lost.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Summary {\n readonly _tag: string\n readonly text: string\n readonly throughSeq: number\n}\n\n/**\n * Returns the number of input tokens used by the chat's last model call, which shows how large\n * the prompt has grown. Returns 0 before the first call.\n *\n * @category utils\n * @since 0.1.0\n */\nexport const inputTokens = (entries: ReadonlyArray<AssistantEvent.AnyEntry>): number => {\n const last = entries.findLast(\n ({ event }) =>\n AssistantEvent.isLibraryEvent(event) && Predicate.isTagged(event, \"ModelOutput\") && event.usage !== undefined\n )?.event\n\n return last !== undefined && AssistantEvent.isLibraryEvent(last) && Predicate.isTagged(last, \"ModelOutput\")\n ? (last.usage?.inputTokens.total ?? 0)\n : 0\n}\n\n/**\n * Returns the items that were open at `throughSeq` and are still open: blocks the user hasn't\n * answered, and specialists that are working. The result is text for\n * the model, or `None` when nothing is open.\n *\n * @category utils\n * @since 0.1.0\n */\nexport const openItems = (\n entries: ReadonlyArray<AssistantEvent.AnyEntry>,\n throughSeq: number\n): Option.Option<string> => {\n const seqOf = (found: (event: AssistantEvent.AnyEntry[\"event\"]) => boolean) =>\n entries.find(({ event }) => found(event))?.seq ?? Number.POSITIVE_INFINITY\n\n const opened = (seq: number) => seq <= throughSeq\n\n const blocks = [...BlockKit.fromEntries(entries)].flatMap(([messageId, state]) => {\n const shown = entries.find(\n ({ event }) =>\n AssistantEvent.isLibraryEvent(event) && Predicate.isTagged(event, \"AssistantMessage\") && event.id === messageId\n )\n\n const message = shown?.event\n\n return state.open &&\n shown !== undefined &&\n opened(shown.seq) &&\n message !== undefined &&\n AssistantEvent.isLibraryEvent(message) &&\n Predicate.isTagged(message, \"AssistantMessage\")\n ? [`- message ${messageId}, a ${message.block?.kind} block: ${message.text}`]\n : []\n })\n\n const specialists = [...AssistantActivity.fromEntries(entries).specialists.values()].flatMap((run) =>\n opened(\n seqOf(\n (event) =>\n AssistantEvent.isLibraryEvent(event) &&\n Predicate.isTagged(event, \"SpecialistStarted\") &&\n event.runId === run.runId\n )\n )\n ? [`- your ${run.specialist} specialist (${run.runId}), working on: ${run.goal}`]\n : []\n )\n\n const sections = [\n blocks.length > 0 ? `Blocks the user hasn't answered:\\n${blocks.join(\"\\n\")}` : \"\",\n specialists.length > 0 ? `Specialists still working:\\n${specialists.join(\"\\n\")}` : \"\"\n ].filter((section) => section.length > 0)\n\n return sections.length === 0 ? Option.none() : Option.some(sections.join(\"\\n\\n\"))\n}\n\n/**\n * Returns the files the user attached up to `throughSeq`, as lines of text for the model, or\n * `None` when there are none. A summary may leave out a file's id, which tools need.\n *\n * @category utils\n * @since 0.1.0\n */\nexport const attachedFiles = (\n entries: ReadonlyArray<AssistantEvent.AnyEntry>,\n throughSeq: number\n): Option.Option<string> => {\n const lines = entries.flatMap(({ seq, event }) =>\n seq <= throughSeq && AssistantEvent.isLibraryEvent(event) && Predicate.isTagged(event, \"UserMessage\")\n ? (event.attachments ?? []).map(AssistantEvent.attachmentLine)\n : []\n )\n\n return lines.length === 0 ? Option.none() : Option.some(lines.join(\"\\n\"))\n}\n\n/**\n * Creates a history view that starts at the last summary.\n *\n * **Details**\n *\n * `isSummary` identifies the app's summary events. The model reads the last summary, the files\n * attached earlier ({@link attachedFiles}), and the items that are still open\n * ({@link openItems}), followed by the entries after the summary. When the chat has no summary,\n * the view returns every entry.\n *\n * The app's other events after the summary stay entries, which the model doesn't read: map those\n * it should read to messages after this view, as with any history view (see\n * `Assistant.HistoryView`). A summary the handler appends wakes no turn.\n *\n * **Example** (Compacting a long chat after each turn)\n *\n * ```ts\n * import { Assistant, type AssistantEvent, Compaction } from \"effect-assistant\"\n * import * as LanguageModel from \"effect/ai/LanguageModel\"\n * import * as Effect from \"effect/Effect\"\n * import * as Schema from \"effect/Schema\"\n * import * as Stream from \"effect/Stream\"\n *\n * class Summary extends Schema.TaggedClass<Summary>()(\"Summary\", {\n * text: Schema.String,\n * throughSeq: Schema.Int\n * }) {}\n *\n * declare const transcript: (entries: ReadonlyArray<AssistantEvent.AnyEntry>) => string\n *\n * export const MyAssistant = Assistant.make(\"my-assistant\", { events: [Summary] })\n *\n * const MyAssistantLive = MyAssistant.toLayer((turn) => {\n * const compact = Effect.gen(function* () {\n * const chat = yield* MyAssistant.Chat\n * const entries = yield* chat.entries\n *\n * if (Compaction.inputTokens(entries) < 100_000) return\n *\n * const { text } = yield* LanguageModel.generateText({\n * prompt: `Summarize this chat:\\n\\n${transcript(entries)}`\n * })\n *\n * yield* chat.append(Summary.make({ text, throughSeq: entries.at(-1)?.seq ?? 0 }))\n * })\n *\n * // After the reply, so the user doesn't wait for the summary.\n * return Stream.concat(\n * turn.loop({ instructions: \"Be brief.\", history: Compaction.since(Schema.is(Summary)) }),\n * Stream.drain(Stream.fromEffect(compact))\n * )\n * })\n * ```\n *\n * @category utils\n * @since 0.1.0\n */\nexport const since =\n (isSummary: (event: unknown) => event is Summary) =>\n <Events extends { readonly _tag: string }>(\n entries: ReadonlyArray<Assistant.Entry<Events>>\n ): ReadonlyArray<Assistant.Entry<Events> | Prompt.Message> => {\n const last = entries.findLast(({ event }) => isSummary(event))?.event\n\n if (last === undefined || !isSummary(last)) return entries\n\n const open = Option.match(openItems(entries, last.throughSeq), {\n onNone: () => \"\",\n onSome: (items) => `\\n\\n<still_open>\\n${items}\\n</still_open>`\n })\n\n const files = Option.match(attachedFiles(entries, last.throughSeq), {\n onNone: () => \"\",\n onSome: (lines) => `\\n\\n<files_attached_earlier>\\n${lines}\\n</files_attached_earlier>`\n })\n\n return [\n userText(`<earlier_in_this_chat>\\n${last.text}\\n</earlier_in_this_chat>${files}${open}`),\n ...entries.filter(({ seq, event }) => seq > last.throughSeq && !isSummary(event))\n ]\n }\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA2CA,MAAa,eAAe,YAA4D;CACtF,MAAM,OAAO,QAAQ,UAClB,EAAE,YACDA,eAA8B,KAAK,KAAK,UAAU,SAAS,OAAO,aAAa,KAAK,MAAM,UAAU,KAAA,CACxG,CAAC,EAAE;CAEH,OAAO,SAAS,KAAA,KAAaA,eAA8B,IAAI,KAAK,UAAU,SAAS,MAAM,aAAa,IACrG,KAAK,OAAO,YAAY,SAAS,IAClC;AACN;;;;;;;;;AAUA,MAAa,aACX,SACA,eAC0B;CAC1B,MAAM,SAAS,UACb,QAAQ,MAAM,EAAE,YAAY,MAAM,KAAK,CAAC,CAAC,EAAE,OAAO,OAAO;CAE3D,MAAM,UAAU,QAAgB,OAAO;CAEvC,MAAM,SAAS,CAAC,GAAGC,cAAqB,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,WAAW,WAAW;EAChF,MAAM,QAAQ,QAAQ,MACnB,EAAE,YACDD,eAA8B,KAAK,KAAK,UAAU,SAAS,OAAO,kBAAkB,KAAK,MAAM,OAAO,SAC1G;EAEA,MAAM,UAAU,OAAO;EAEvB,OAAO,MAAM,QACX,UAAU,KAAA,KACV,OAAO,MAAM,GAAG,KAChB,YAAY,KAAA,KACZA,eAA8B,OAAO,KACrC,UAAU,SAAS,SAAS,kBAAkB,IAC5C,CAAC,aAAa,UAAU,MAAM,QAAQ,OAAO,KAAK,UAAU,QAAQ,MAAM,IAC1E,CAAC;CACP,CAAC;CAED,MAAM,cAAc,CAAC,GAAGE,YAA8B,OAAO,CAAC,CAAC,YAAY,OAAO,CAAC,CAAC,CAAC,SAAS,QAC5F,OACE,OACG,UACCF,eAA8B,KAAK,KACnC,UAAU,SAAS,OAAO,mBAAmB,KAC7C,MAAM,UAAU,IAAI,KACxB,CACF,IACI,CAAC,UAAU,IAAI,WAAW,eAAe,IAAI,MAAM,iBAAiB,IAAI,MAAM,IAC9E,CAAC,CACP;CAEA,MAAM,WAAW,CACf,OAAO,SAAS,IAAI,qCAAqC,OAAO,KAAK,IAAI,MAAM,IAC/E,YAAY,SAAS,IAAI,+BAA+B,YAAY,KAAK,IAAI,MAAM,EACrF,CAAC,CAAC,QAAQ,YAAY,QAAQ,SAAS,CAAC;CAExC,OAAO,SAAS,WAAW,IAAI,OAAO,KAAK,IAAI,OAAO,KAAK,SAAS,KAAK,MAAM,CAAC;AAClF;;;;;;;;AASA,MAAa,iBACX,SACA,eAC0B;CAC1B,MAAM,QAAQ,QAAQ,SAAS,EAAE,KAAK,YACpC,OAAO,cAAcA,eAA8B,KAAK,KAAK,UAAU,SAAS,OAAO,aAAa,KAC/F,MAAM,eAAe,CAAC,EAAA,CAAG,IAAIG,cAA6B,IAC3D,CAAC,CACP;CAEA,OAAO,MAAM,WAAW,IAAI,OAAO,KAAK,IAAI,OAAO,KAAK,MAAM,KAAK,IAAI,CAAC;AAC1E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2DA,MAAa,SACV,eAEC,YAC4D;CAC5D,MAAM,OAAO,QAAQ,UAAU,EAAE,YAAY,UAAU,KAAK,CAAC,CAAC,EAAE;CAEhE,IAAI,SAAS,KAAA,KAAa,CAAC,UAAU,IAAI,GAAG,OAAO;CAEnD,MAAM,OAAO,OAAO,MAAM,UAAU,SAAS,KAAK,UAAU,GAAG;EAC7D,cAAc;EACd,SAAS,UAAU,qBAAqB,MAAM;CAChD,CAAC;CAED,MAAM,QAAQ,OAAO,MAAM,cAAc,SAAS,KAAK,UAAU,GAAG;EAClE,cAAc;EACd,SAAS,UAAU,iCAAiC,MAAM;CAC5D,CAAC;CAED,OAAO,CACL,SAAS,2BAA2B,KAAK,KAAK,2BAA2B,QAAQ,MAAM,GACvF,GAAG,QAAQ,QAAQ,EAAE,KAAK,YAAY,MAAM,KAAK,cAAc,CAAC,UAAU,KAAK,CAAC,CAClF;AACF"}
|
|
1
|
+
{"version":3,"file":"Compaction.js","names":["AssistantEvent.isLibraryEvent","BlockKit.fromEntries","AssistantActivity.fromEntries","ToolApproval.fromEntries","AssistantEvent.attachmentLine"],"sources":["../src/Compaction.ts"],"sourcesContent":["/**\n * This module provides what a long chat needs so that the model can read a summary in place of\n * everything before it.\n *\n * The app decides when to compact, which model writes the summary, and what the summary says.\n * Its handler reads the chat's entries, has a model write a summary, and stores the summary as one\n * of the assistant's own events (see {@link Summary}). Its loop then reads the chat through\n * {@link since}. {@link inputTokens} helps decide when the prompt has grown large enough to\n * compact. The log keeps every entry, so the user still sees the whole chat.\n *\n * @since 0.1.0\n */\nimport type * as Prompt from \"effect/ai/Prompt\"\nimport * as Option from \"effect/Option\"\nimport * as Predicate from \"effect/Predicate\"\nimport type * as Assistant from \"./Assistant.ts\"\nimport * as AssistantActivity from \"./AssistantActivity.ts\"\nimport * as AssistantEvent from \"./AssistantEvent.ts\"\nimport * as BlockKit from \"./BlockKit.ts\"\nimport { userText } from \"./internal/history.ts\"\nimport { toJsonText } from \"./internal/schema.ts\"\nimport * as ToolApproval from \"./ToolApproval.ts\"\n\n/**\n * Represents the fields a summary event must have: its text and the last `seq` it covers.\n *\n * Entries after `throughSeq` are kept as they are, so an input that arrived while the summary was\n * being written isn't lost.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Summary {\n readonly _tag: string\n readonly text: string\n readonly throughSeq: number\n}\n\n/**\n * Returns the number of input tokens used by the chat's last model call, which shows how large\n * the prompt has grown. Returns 0 before the first call.\n *\n * @category utils\n * @since 0.1.0\n */\nexport const inputTokens = (entries: ReadonlyArray<AssistantEvent.AnyEntry>): number => {\n const last = entries.findLast(\n ({ event }) =>\n AssistantEvent.isLibraryEvent(event) && Predicate.isTagged(event, \"ModelOutput\") && event.usage !== undefined\n )?.event\n\n return last !== undefined && AssistantEvent.isLibraryEvent(last) && Predicate.isTagged(last, \"ModelOutput\")\n ? (last.usage?.inputTokens.total ?? 0)\n : 0\n}\n\n/**\n * Returns the items that were open at `throughSeq` and are still open: blocks the user hasn't\n * answered, specialists that are working, and tool calls waiting for the user's approval. The result is text for\n * the model, or `None` when nothing is open.\n *\n * @category utils\n * @since 0.1.0\n */\nexport const openItems = (\n entries: ReadonlyArray<AssistantEvent.AnyEntry>,\n throughSeq: number\n): Option.Option<string> => {\n const seqOf = (found: (event: AssistantEvent.AnyEntry[\"event\"]) => boolean) =>\n entries.find(({ event }) => found(event))?.seq ?? Number.POSITIVE_INFINITY\n\n const opened = (seq: number) => seq <= throughSeq\n\n const blocks = [...BlockKit.fromEntries(entries)].flatMap(([messageId, state]) => {\n const shown = entries.find(\n ({ event }) =>\n AssistantEvent.isLibraryEvent(event) && Predicate.isTagged(event, \"AssistantMessage\") && event.id === messageId\n )\n\n const message = shown?.event\n\n return state.open &&\n shown !== undefined &&\n opened(shown.seq) &&\n message !== undefined &&\n AssistantEvent.isLibraryEvent(message) &&\n Predicate.isTagged(message, \"AssistantMessage\")\n ? [`- message ${messageId}, a ${message.block?.kind} block: ${message.text}`]\n : []\n })\n\n const specialists = [...AssistantActivity.fromEntries(entries).specialists.values()].flatMap((run) =>\n opened(\n seqOf(\n (event) =>\n AssistantEvent.isLibraryEvent(event) &&\n Predicate.isTagged(event, \"SpecialistStarted\") &&\n event.runId === run.runId\n )\n )\n ? [`- your ${run.specialist} specialist (${run.runId}), working on: ${run.goal}`]\n : []\n )\n\n const approvals = [...ToolApproval.fromEntries(entries).values()].flatMap((approval) =>\n approval.status === \"pending\" &&\n opened(\n seqOf(\n (event) =>\n AssistantEvent.isLibraryEvent(event) &&\n Predicate.isTagged(event, \"ApprovalRequested\") &&\n event.callId === approval.request.callId\n )\n )\n ? [`- your call to ${approval.request.tool} (${approval.request.callId}): ${toJsonText(approval.request.params)}`]\n : []\n )\n\n const sections = [\n blocks.length > 0 ? `Blocks the user hasn't answered:\\n${blocks.join(\"\\n\")}` : \"\",\n specialists.length > 0 ? `Specialists still working:\\n${specialists.join(\"\\n\")}` : \"\",\n approvals.length > 0 ? `Calls waiting for the user's approval:\\n${approvals.join(\"\\n\")}` : \"\"\n ].filter((section) => section.length > 0)\n\n return sections.length === 0 ? Option.none() : Option.some(sections.join(\"\\n\\n\"))\n}\n\n/**\n * Returns the files the user attached up to `throughSeq`, as lines of text for the model, or\n * `None` when there are none. A summary may leave out a file's id, which tools need.\n *\n * @category utils\n * @since 0.1.0\n */\nexport const attachedFiles = (\n entries: ReadonlyArray<AssistantEvent.AnyEntry>,\n throughSeq: number\n): Option.Option<string> => {\n const lines = entries.flatMap(({ seq, event }) =>\n seq <= throughSeq && AssistantEvent.isLibraryEvent(event) && Predicate.isTagged(event, \"UserMessage\")\n ? (event.attachments ?? []).map(AssistantEvent.attachmentLine)\n : []\n )\n\n return lines.length === 0 ? Option.none() : Option.some(lines.join(\"\\n\"))\n}\n\n/**\n * Creates a history view that starts at the last summary.\n *\n * **Details**\n *\n * `isSummary` identifies the app's summary events. The model reads the last summary, the files\n * attached earlier ({@link attachedFiles}), and the items that are still open\n * ({@link openItems}), followed by the entries after the summary. When the chat has no summary,\n * the view returns every entry.\n *\n * The app's other events after the summary stay entries, which the model doesn't read: map those\n * it should read to messages after this view, as with any history view (see\n * `Assistant.HistoryView`). A summary the handler appends wakes no turn.\n *\n * **Example** (Compacting a long chat after each turn)\n *\n * ```ts\n * import { Assistant, type AssistantEvent, Compaction } from \"effect-assistant\"\n * import * as LanguageModel from \"effect/ai/LanguageModel\"\n * import * as Effect from \"effect/Effect\"\n * import * as Schema from \"effect/Schema\"\n * import * as Stream from \"effect/Stream\"\n *\n * class Summary extends Schema.TaggedClass<Summary>()(\"Summary\", {\n * text: Schema.String,\n * throughSeq: Schema.Int\n * }) {}\n *\n * declare const transcript: (entries: ReadonlyArray<AssistantEvent.AnyEntry>) => string\n *\n * export const MyAssistant = Assistant.make(\"my-assistant\", { events: [Summary] })\n *\n * const MyAssistantLive = MyAssistant.toLayer((turn) => {\n * const compact = Effect.gen(function* () {\n * const chat = yield* MyAssistant.Chat\n * const entries = yield* chat.entries\n *\n * if (Compaction.inputTokens(entries) < 100_000) return\n *\n * const { text } = yield* LanguageModel.generateText({\n * prompt: `Summarize this chat:\\n\\n${transcript(entries)}`\n * })\n *\n * yield* chat.append(Summary.make({ text, throughSeq: entries.at(-1)?.seq ?? 0 }))\n * })\n *\n * // After the reply, so the user doesn't wait for the summary.\n * return Stream.concat(\n * turn.loop({ instructions: \"Be brief.\", history: Compaction.since(Schema.is(Summary)) }),\n * Stream.drain(Stream.fromEffect(compact))\n * )\n * })\n * ```\n *\n * @category utils\n * @since 0.1.0\n */\nexport const since =\n (isSummary: (event: unknown) => event is Summary) =>\n <Events extends { readonly _tag: string }>(\n entries: ReadonlyArray<Assistant.Entry<Events>>\n ): ReadonlyArray<Assistant.Entry<Events> | Prompt.Message> => {\n const last = entries.findLast(({ event }) => isSummary(event))?.event\n\n if (last === undefined || !isSummary(last)) return entries\n\n const open = Option.match(openItems(entries, last.throughSeq), {\n onNone: () => \"\",\n onSome: (items) => `\\n\\n<still_open>\\n${items}\\n</still_open>`\n })\n\n const files = Option.match(attachedFiles(entries, last.throughSeq), {\n onNone: () => \"\",\n onSome: (lines) => `\\n\\n<files_attached_earlier>\\n${lines}\\n</files_attached_earlier>`\n })\n\n return [\n userText(`<earlier_in_this_chat>\\n${last.text}\\n</earlier_in_this_chat>${files}${open}`),\n ...entries.filter(({ seq, event }) => seq > last.throughSeq && !isSummary(event))\n ]\n }\n"],"mappings":"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"}
|
package/dist/Skill.d.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { Instructions, Instructions_d_exports } from "./Instructions.js";
|
|
2
|
+
import { ToolFailure, ToolFailure_d_exports } from "./ToolFailure.js";
|
|
3
|
+
import * as Layer from "effect/Layer";
|
|
4
|
+
import * as Schema from "effect/Schema";
|
|
2
5
|
import * as Tool from "effect/ai/Tool";
|
|
6
|
+
import * as AiToolkit from "effect/ai/Toolkit";
|
|
3
7
|
declare namespace Skill_d_exports {
|
|
4
|
-
export { Services, Skill, make,
|
|
8
|
+
export { LoadSkill, Services, Skill, Toolkit, make, toolkit };
|
|
5
9
|
}
|
|
6
10
|
/**
|
|
7
11
|
* Represents a skill.
|
|
@@ -17,11 +21,6 @@ export interface Skill<R = never> {
|
|
|
17
21
|
readonly description: string;
|
|
18
22
|
/** Text, or an Effect resolved each time the skill is loaded. */
|
|
19
23
|
readonly instructions: Instructions<R>;
|
|
20
|
-
/**
|
|
21
|
-
* The tools its instructions use. A loop offers the skill only when its toolkit has every one of
|
|
22
|
-
* them, matched by name. Unset, a loop always offers it.
|
|
23
|
-
*/
|
|
24
|
-
readonly tools?: ReadonlyArray<Tool.Any> | undefined;
|
|
25
24
|
}
|
|
26
25
|
/**
|
|
27
26
|
* Creates a skill from its name, a description of when to use it, and its instructions.
|
|
@@ -30,12 +29,7 @@ export interface Skill<R = never> {
|
|
|
30
29
|
*
|
|
31
30
|
* Write the description as a trigger ("Use when…"), since it's all the model sees until it loads
|
|
32
31
|
* the skill. The instructions are text, or an Effect that is resolved each time the skill is
|
|
33
|
-
* loaded.
|
|
34
|
-
*
|
|
35
|
-
* `tools` lists the tools the instructions use. A loop whose toolkit lacks any of them, matched by
|
|
36
|
-
* name, leaves the skill out of its catalog and of `load_skill`, so a loop that gets its tools per
|
|
37
|
-
* user (by role, say) never offers a playbook it can't follow. A tool that only `ToolDiscovery`
|
|
38
|
-
* finds isn't in the toolkit, so it doesn't count. Without `tools`, every loop offers the skill.
|
|
32
|
+
* loaded. Offer skills to a loop with {@link toolkit}.
|
|
39
33
|
*
|
|
40
34
|
* **Example** (Defining a playbook for ordering campaigns)
|
|
41
35
|
*
|
|
@@ -48,30 +42,6 @@ export interface Skill<R = never> {
|
|
|
48
42
|
* 2. You need who it should reach and a budget. Ask for what's missing, one question at a time.
|
|
49
43
|
* 3. When you have both, call start_campaign.`
|
|
50
44
|
* })
|
|
51
|
-
*
|
|
52
|
-
* const MyAssistantLive = MyAssistant.toLayer((turn) =>
|
|
53
|
-
* turn.loop({ instructions, skills: [orderingCampaigns] })
|
|
54
|
-
* )
|
|
55
|
-
* ```
|
|
56
|
-
*
|
|
57
|
-
* **Example** (Offering a skill only with the tools it uses)
|
|
58
|
-
*
|
|
59
|
-
* ```ts
|
|
60
|
-
* import { Skill } from "effect-assistant"
|
|
61
|
-
* import * as Tool from "effect/ai/Tool"
|
|
62
|
-
* import * as Schema from "effect/Schema"
|
|
63
|
-
*
|
|
64
|
-
* const StartCampaign = Tool.make("start_campaign", {
|
|
65
|
-
* parameters: Schema.Struct({ campaignId: Schema.String, budget: Schema.Finite }),
|
|
66
|
-
* success: Schema.String
|
|
67
|
-
* })
|
|
68
|
-
*
|
|
69
|
-
* // Left out of a loop whose toolkit has no start_campaign, like a viewer's.
|
|
70
|
-
* const orderingCampaigns = Skill.make("Ordering a campaign", {
|
|
71
|
-
* description: "Use when the user wants to order or start a campaign.",
|
|
72
|
-
* instructions: "When you know who it should reach and the budget, call start_campaign.",
|
|
73
|
-
* tools: [StartCampaign]
|
|
74
|
-
* })
|
|
75
45
|
* ```
|
|
76
46
|
*
|
|
77
47
|
* @category constructors
|
|
@@ -82,8 +52,6 @@ export declare const make: <R = never>(name: string, options: {
|
|
|
82
52
|
readonly description: string;
|
|
83
53
|
/** Text, or an Effect resolved each time the skill is loaded. */
|
|
84
54
|
readonly instructions: Instructions<R>;
|
|
85
|
-
/** The tools its instructions use: a loop whose toolkit lacks one leaves the skill out. */
|
|
86
|
-
readonly tools?: ReadonlyArray<Tool.Any> | undefined;
|
|
87
55
|
}) => Skill<R>;
|
|
88
56
|
/**
|
|
89
57
|
* Extracts the services that skills need when they're loaded.
|
|
@@ -92,14 +60,88 @@ export declare const make: <R = never>(name: string, options: {
|
|
|
92
60
|
* @since 0.1.0
|
|
93
61
|
*/
|
|
94
62
|
export type Services<S> = S extends Skill<infer R> ? R : never;
|
|
63
|
+
declare const loadSkill: (description: string) => Tool.Tool<"load_skill", {
|
|
64
|
+
readonly parameters: Schema.Struct<{
|
|
65
|
+
readonly name: Schema.String;
|
|
66
|
+
}>;
|
|
67
|
+
readonly success: Schema.String;
|
|
68
|
+
readonly failure: typeof ToolFailure;
|
|
69
|
+
readonly failureMode: "error";
|
|
70
|
+
}, never>;
|
|
95
71
|
/**
|
|
96
|
-
*
|
|
97
|
-
* skills.
|
|
72
|
+
* Represents the `load_skill` tool, which reads a skill's instructions by name.
|
|
98
73
|
*
|
|
99
|
-
* @category
|
|
100
|
-
* @since 0.
|
|
74
|
+
* @category models
|
|
75
|
+
* @since 0.4.0
|
|
76
|
+
*/
|
|
77
|
+
export type LoadSkill = ReturnType<typeof loadSkill>;
|
|
78
|
+
/**
|
|
79
|
+
* Represents a list of skills as a toolkit: the `load_skill` tool, and the layer with its handler.
|
|
80
|
+
*
|
|
81
|
+
* `R` is the services the skills' instructions need, which the layer requires.
|
|
82
|
+
*
|
|
83
|
+
* @category models
|
|
84
|
+
* @since 0.4.0
|
|
85
|
+
*/
|
|
86
|
+
export interface Toolkit<R = never> extends AiToolkit.Toolkit<{
|
|
87
|
+
readonly load_skill: LoadSkill;
|
|
88
|
+
}> {
|
|
89
|
+
/** The handler of `load_skill`, which resolves the instructions of these skills. */
|
|
90
|
+
readonly layer: Layer.Layer<Tool.Handler<"load_skill">, never, R>;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Creates a toolkit from a list of skills: the `load_skill` tool, whose description lists each
|
|
94
|
+
* skill's name and when to use it, and its handler's `layer`.
|
|
95
|
+
*
|
|
96
|
+
* **Details**
|
|
97
|
+
*
|
|
98
|
+
* Merge the toolkit into a loop's own with `Toolkit.merge`, and provide its `layer`, which requires
|
|
99
|
+
* the services the skills' instructions need. The model loads a skill by name; a name that isn't
|
|
100
|
+
* in the list is a failure the model reads, with the names there are.
|
|
101
|
+
*
|
|
102
|
+
* The handler resolves only the skills of its own list. To offer each user the skills they may
|
|
103
|
+
* use, build the toolkit in the turn from that user's list, and provide its layer to the loop's
|
|
104
|
+
* stream (`Stream.provide(skills.layer)`): building one does no work.
|
|
105
|
+
*
|
|
106
|
+
* **Example** (Giving an assistant its playbooks)
|
|
107
|
+
*
|
|
108
|
+
* ```ts
|
|
109
|
+
* import { Skill } from "effect-assistant"
|
|
110
|
+
* import * as Layer from "effect/Layer"
|
|
111
|
+
* import * as Toolkit from "effect/ai/Toolkit"
|
|
112
|
+
*
|
|
113
|
+
* const AssistantSkills = Skill.toolkit([orderingCampaigns, sharingAssets])
|
|
114
|
+
*
|
|
115
|
+
* const MyAssistantLive = MyAssistant.toLayer((turn) =>
|
|
116
|
+
* turn.loop({ instructions, toolkit: Toolkit.merge(MyTools, AssistantSkills) })
|
|
117
|
+
* ).pipe(Layer.provide([MyToolsLive, AssistantSkills.layer]))
|
|
118
|
+
* ```
|
|
119
|
+
*
|
|
120
|
+
* **Example** (Offering each user the skills their role allows)
|
|
121
|
+
*
|
|
122
|
+
* ```ts
|
|
123
|
+
* import { Skill } from "effect-assistant"
|
|
124
|
+
* import * as Effect from "effect/Effect"
|
|
125
|
+
* import * as Stream from "effect/Stream"
|
|
126
|
+
* import * as Toolkit from "effect/ai/Toolkit"
|
|
127
|
+
*
|
|
128
|
+
* const MyAssistantLive = MyAssistant.toLayer((turn) =>
|
|
129
|
+
* Stream.unwrap(
|
|
130
|
+
* Effect.map(roleOf(turn.chatId), (role) => {
|
|
131
|
+
* const skills = Skill.toolkit(allSkills.filter((skill) => allowed(role, skill)))
|
|
132
|
+
*
|
|
133
|
+
* return turn
|
|
134
|
+
* .loop({ instructions, toolkit: Toolkit.merge(MyTools, skills) })
|
|
135
|
+
* .pipe(Stream.provide(skills.layer))
|
|
136
|
+
* })
|
|
137
|
+
* )
|
|
138
|
+
* )
|
|
139
|
+
* ```
|
|
140
|
+
*
|
|
141
|
+
* @category constructors
|
|
142
|
+
* @since 0.4.0
|
|
101
143
|
*/
|
|
102
|
-
export declare const
|
|
144
|
+
export declare const toolkit: <const Skills extends ReadonlyArray<Skill<unknown>>>(skills: Skills) => Toolkit<Services<Skills[number]>>;
|
|
103
145
|
//#endregion
|
|
104
146
|
export { Skill_d_exports };
|
|
105
147
|
//# sourceMappingURL=Skill.d.ts.map
|
package/dist/Skill.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Skill.d.ts","names":[],"sources":["../src/Skill.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"Skill.d.ts","names":[],"sources":["../src/Skill.ts"],"mappings":";;;;;;;;;;;;;;;;;iBAyBiB,MAAM;WACZ;;WAEA;;WAEA,cAAc,aAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;qBA4BtC,OAAQ,WAAS,cAChB;;WAGD;;WAEA,cAAc,aAA0B;MAElD,MAAM;;;;;;;YAQG,SAAS,KAAK,UAAU,YAAY,KAAK;cAE/C,YAAS,wBAAuB,KAAA;;;;;;;;;;;;;;YAc1B,YAAY,kBAAkB;;;;;;;;;iBAUzB,QAAQ,mBAAmB,UAAU;WAAmB,YAAY;;;WAE1E,OAAO,MAAM,MAAM,KAAK,8BAA8B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAgGpD,gBAAgB,eAAe,cAAc,iBACxD,QAAQ,WACL,QAAQ,SAAS"}
|
package/dist/Skill.js
CHANGED
|
@@ -1,8 +1,23 @@
|
|
|
1
1
|
import { __exportAll } from "./_virtual/_rolldown/runtime.js";
|
|
2
|
+
import { ToolFailure, fail } from "./ToolFailure.js";
|
|
3
|
+
import { resolve } from "./Instructions.js";
|
|
4
|
+
import * as Effect from "effect/Effect";
|
|
5
|
+
import * as Schema from "effect/Schema";
|
|
6
|
+
import * as Tool from "effect/ai/Tool";
|
|
7
|
+
import * as AiToolkit from "effect/ai/Toolkit";
|
|
2
8
|
//#region src/Skill.ts
|
|
9
|
+
/**
|
|
10
|
+
* Defines skills, which are playbooks with step-by-step instructions for a kind of task.
|
|
11
|
+
*
|
|
12
|
+
* A list of skills becomes a toolkit with one tool, `load_skill` ({@link toolkit}). Its
|
|
13
|
+
* description is the catalog, each skill's name and when to use it, and the model calls it to read
|
|
14
|
+
* a skill's instructions when a task matches. Merge it into a loop's toolkit like any other.
|
|
15
|
+
*
|
|
16
|
+
* @since 0.1.0
|
|
17
|
+
*/
|
|
3
18
|
var Skill_exports = /* @__PURE__ */ __exportAll({
|
|
4
19
|
make: () => make,
|
|
5
|
-
|
|
20
|
+
toolkit: () => toolkit
|
|
6
21
|
});
|
|
7
22
|
/**
|
|
8
23
|
* Creates a skill from its name, a description of when to use it, and its instructions.
|
|
@@ -11,12 +26,7 @@ var Skill_exports = /* @__PURE__ */ __exportAll({
|
|
|
11
26
|
*
|
|
12
27
|
* Write the description as a trigger ("Use when…"), since it's all the model sees until it loads
|
|
13
28
|
* the skill. The instructions are text, or an Effect that is resolved each time the skill is
|
|
14
|
-
* loaded.
|
|
15
|
-
*
|
|
16
|
-
* `tools` lists the tools the instructions use. A loop whose toolkit lacks any of them, matched by
|
|
17
|
-
* name, leaves the skill out of its catalog and of `load_skill`, so a loop that gets its tools per
|
|
18
|
-
* user (by role, say) never offers a playbook it can't follow. A tool that only `ToolDiscovery`
|
|
19
|
-
* finds isn't in the toolkit, so it doesn't count. Without `tools`, every loop offers the skill.
|
|
29
|
+
* loaded. Offer skills to a loop with {@link toolkit}.
|
|
20
30
|
*
|
|
21
31
|
* **Example** (Defining a playbook for ordering campaigns)
|
|
22
32
|
*
|
|
@@ -29,56 +39,95 @@ var Skill_exports = /* @__PURE__ */ __exportAll({
|
|
|
29
39
|
* 2. You need who it should reach and a budget. Ask for what's missing, one question at a time.
|
|
30
40
|
* 3. When you have both, call start_campaign.`
|
|
31
41
|
* })
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* @category constructors
|
|
45
|
+
* @since 0.1.0
|
|
46
|
+
*/
|
|
47
|
+
const make = (name, options) => ({
|
|
48
|
+
name,
|
|
49
|
+
description: options.description,
|
|
50
|
+
instructions: options.instructions
|
|
51
|
+
});
|
|
52
|
+
const loadSkill = (description) => Tool.make("load_skill", {
|
|
53
|
+
description,
|
|
54
|
+
parameters: Schema.Struct({ name: Schema.String }),
|
|
55
|
+
success: Schema.String,
|
|
56
|
+
failure: ToolFailure
|
|
57
|
+
});
|
|
58
|
+
const catalog = (skills) => [
|
|
59
|
+
"Load a skill, a playbook for a kind of task, by name, and follow it for the rest of the task. When one matches what you're doing, load it in the same step as your first lookups, not in a step of its own.",
|
|
60
|
+
"",
|
|
61
|
+
"Skills:",
|
|
62
|
+
...skills.map((skill) => `- ${skill.name}: ${skill.description}`)
|
|
63
|
+
].join("\n");
|
|
64
|
+
function toolkitOf(skills) {
|
|
65
|
+
const byName = new Map(skills.map((skill) => [skill.name, skill]));
|
|
66
|
+
const tools = AiToolkit.make(loadSkill(catalog(skills)));
|
|
67
|
+
const layer = tools.toLayer(Effect.gen(function* () {
|
|
68
|
+
const context = yield* Effect.context();
|
|
69
|
+
return { load_skill: Effect.fnUntraced(function* ({ name }) {
|
|
70
|
+
const skill = byName.get(name);
|
|
71
|
+
if (skill === void 0) return yield* fail(`No skill named ${name}. The skills are: ${skills.map((known) => known.name).join(", ")}.`);
|
|
72
|
+
return (yield* resolve(skill.instructions).pipe(Effect.provideContext(context))).text;
|
|
73
|
+
}) };
|
|
74
|
+
}));
|
|
75
|
+
return Object.assign(tools, { layer });
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Creates a toolkit from a list of skills: the `load_skill` tool, whose description lists each
|
|
79
|
+
* skill's name and when to use it, and its handler's `layer`.
|
|
80
|
+
*
|
|
81
|
+
* **Details**
|
|
82
|
+
*
|
|
83
|
+
* Merge the toolkit into a loop's own with `Toolkit.merge`, and provide its `layer`, which requires
|
|
84
|
+
* the services the skills' instructions need. The model loads a skill by name; a name that isn't
|
|
85
|
+
* in the list is a failure the model reads, with the names there are.
|
|
86
|
+
*
|
|
87
|
+
* The handler resolves only the skills of its own list. To offer each user the skills they may
|
|
88
|
+
* use, build the toolkit in the turn from that user's list, and provide its layer to the loop's
|
|
89
|
+
* stream (`Stream.provide(skills.layer)`): building one does no work.
|
|
90
|
+
*
|
|
91
|
+
* **Example** (Giving an assistant its playbooks)
|
|
92
|
+
*
|
|
93
|
+
* ```ts
|
|
94
|
+
* import { Skill } from "effect-assistant"
|
|
95
|
+
* import * as Layer from "effect/Layer"
|
|
96
|
+
* import * as Toolkit from "effect/ai/Toolkit"
|
|
97
|
+
*
|
|
98
|
+
* const AssistantSkills = Skill.toolkit([orderingCampaigns, sharingAssets])
|
|
32
99
|
*
|
|
33
100
|
* const MyAssistantLive = MyAssistant.toLayer((turn) =>
|
|
34
|
-
* turn.loop({ instructions,
|
|
35
|
-
* )
|
|
101
|
+
* turn.loop({ instructions, toolkit: Toolkit.merge(MyTools, AssistantSkills) })
|
|
102
|
+
* ).pipe(Layer.provide([MyToolsLive, AssistantSkills.layer]))
|
|
36
103
|
* ```
|
|
37
104
|
*
|
|
38
|
-
* **Example** (Offering
|
|
105
|
+
* **Example** (Offering each user the skills their role allows)
|
|
39
106
|
*
|
|
40
107
|
* ```ts
|
|
41
108
|
* import { Skill } from "effect-assistant"
|
|
42
|
-
* import * as
|
|
43
|
-
* import * as
|
|
109
|
+
* import * as Effect from "effect/Effect"
|
|
110
|
+
* import * as Stream from "effect/Stream"
|
|
111
|
+
* import * as Toolkit from "effect/ai/Toolkit"
|
|
44
112
|
*
|
|
45
|
-
* const
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
113
|
+
* const MyAssistantLive = MyAssistant.toLayer((turn) =>
|
|
114
|
+
* Stream.unwrap(
|
|
115
|
+
* Effect.map(roleOf(turn.chatId), (role) => {
|
|
116
|
+
* const skills = Skill.toolkit(allSkills.filter((skill) => allowed(role, skill)))
|
|
49
117
|
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
118
|
+
* return turn
|
|
119
|
+
* .loop({ instructions, toolkit: Toolkit.merge(MyTools, skills) })
|
|
120
|
+
* .pipe(Stream.provide(skills.layer))
|
|
121
|
+
* })
|
|
122
|
+
* )
|
|
123
|
+
* )
|
|
56
124
|
* ```
|
|
57
125
|
*
|
|
58
126
|
* @category constructors
|
|
59
|
-
* @since 0.
|
|
60
|
-
*/
|
|
61
|
-
const make = (name, options) => ({
|
|
62
|
-
name,
|
|
63
|
-
description: options.description,
|
|
64
|
-
instructions: options.instructions,
|
|
65
|
-
tools: options.tools
|
|
66
|
-
});
|
|
67
|
-
/**
|
|
68
|
-
* Returns the prompt with the skills' catalog appended, or the prompt unchanged when there are no
|
|
69
|
-
* skills.
|
|
70
|
-
*
|
|
71
|
-
* @category utils
|
|
72
|
-
* @since 0.1.0
|
|
127
|
+
* @since 0.4.0
|
|
73
128
|
*/
|
|
74
|
-
const
|
|
75
|
-
prompt,
|
|
76
|
-
"",
|
|
77
|
-
"## Skills",
|
|
78
|
-
"Playbooks for common tasks. When one matches what you're doing, load it with load_skill in the same step as your first lookups, and follow it.",
|
|
79
|
-
...skills.map((skill) => `- ${skill.name}: ${skill.description}`)
|
|
80
|
-
].join("\n");
|
|
129
|
+
const toolkit = toolkitOf;
|
|
81
130
|
//#endregion
|
|
82
|
-
export { Skill_exports, make,
|
|
131
|
+
export { Skill_exports, make, toolkit };
|
|
83
132
|
|
|
84
133
|
//# sourceMappingURL=Skill.js.map
|
package/dist/Skill.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Skill.js","names":[],"sources":["../src/Skill.ts"],"sourcesContent":["/**\n * Defines skills, which are playbooks with step-by-step instructions for a kind of task.\n *\n * A
|
|
1
|
+
{"version":3,"file":"Skill.js","names":["ToolFailure.ToolFailure","ToolFailure.fail","Instructions.resolve"],"sources":["../src/Skill.ts"],"sourcesContent":["/**\n * Defines skills, which are playbooks with step-by-step instructions for a kind of task.\n *\n * A list of skills becomes a toolkit with one tool, `load_skill` ({@link toolkit}). Its\n * description is the catalog, each skill's name and when to use it, and the model calls it to read\n * a skill's instructions when a task matches. Merge it into a loop's toolkit like any other.\n *\n * @since 0.1.0\n */\nimport * as Tool from \"effect/ai/Tool\"\nimport * as AiToolkit from \"effect/ai/Toolkit\"\nimport * as Effect from \"effect/Effect\"\nimport type * as Layer from \"effect/Layer\"\nimport * as Schema from \"effect/Schema\"\nimport * as Instructions from \"./Instructions.ts\"\nimport * as ToolFailure from \"./ToolFailure.ts\"\n\n/**\n * Represents a skill.\n *\n * `R` is the services its instructions need when they're loaded.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Skill<R = never> {\n readonly name: string\n /** When to use it, in one line. This is all the model sees until it loads the skill. */\n readonly description: string\n /** Text, or an Effect resolved each time the skill is loaded. */\n readonly instructions: Instructions.Instructions<R>\n}\n\n/**\n * Creates a skill from its name, a description of when to use it, and its instructions.\n *\n * **Details**\n *\n * Write the description as a trigger (\"Use when…\"), since it's all the model sees until it loads\n * the skill. The instructions are text, or an Effect that is resolved each time the skill is\n * loaded. Offer skills to a loop with {@link toolkit}.\n *\n * **Example** (Defining a playbook for ordering campaigns)\n *\n * ```ts\n * import { Skill } from \"effect-assistant\"\n *\n * const orderingCampaigns = Skill.make(\"Ordering a campaign\", {\n * description: \"Use when the user wants to order or start a campaign.\",\n * instructions: `1. Pick the product with list_campaign_products.\n * 2. You need who it should reach and a budget. Ask for what's missing, one question at a time.\n * 3. When you have both, call start_campaign.`\n * })\n * ```\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const make = <R = never>(\n name: string,\n options: {\n /** When to use it, in one line. This is all the model sees until it loads the skill. */\n readonly description: string\n /** Text, or an Effect resolved each time the skill is loaded. */\n readonly instructions: Instructions.Instructions<R>\n }\n): Skill<R> => ({ name, description: options.description, instructions: options.instructions })\n\n/**\n * Extracts the services that skills need when they're loaded.\n *\n * @category utility types\n * @since 0.1.0\n */\nexport type Services<S> = S extends Skill<infer R> ? R : never\n\nconst loadSkill = (description: string) =>\n Tool.make(\"load_skill\", {\n description,\n parameters: Schema.Struct({ name: Schema.String }),\n success: Schema.String,\n failure: ToolFailure.ToolFailure\n })\n\n/**\n * Represents the `load_skill` tool, which reads a skill's instructions by name.\n *\n * @category models\n * @since 0.4.0\n */\nexport type LoadSkill = ReturnType<typeof loadSkill>\n\n/**\n * Represents a list of skills as a toolkit: the `load_skill` tool, and the layer with its handler.\n *\n * `R` is the services the skills' instructions need, which the layer requires.\n *\n * @category models\n * @since 0.4.0\n */\nexport interface Toolkit<R = never> extends AiToolkit.Toolkit<{ readonly load_skill: LoadSkill }> {\n /** The handler of `load_skill`, which resolves the instructions of these skills. */\n readonly layer: Layer.Layer<Tool.Handler<\"load_skill\">, never, R>\n}\n\nconst catalog = (skills: ReadonlyArray<Skill<unknown>>): string =>\n [\n \"Load a skill, a playbook for a kind of task, by name, and follow it for the rest of the task. When one matches what you're doing, load it in the same step as your first lookups, not in a step of its own.\",\n \"\",\n \"Skills:\",\n ...skills.map((skill) => `- ${skill.name}: ${skill.description}`)\n ].join(\"\\n\")\n\nfunction toolkitOf<const Skills extends ReadonlyArray<Skill<unknown>>>(\n skills: Skills\n): Toolkit<Services<Skills[number]>>\n// The overload states what the layer requires: the services of these skills' instructions.\nfunction toolkitOf(skills: ReadonlyArray<Skill<unknown>>): Toolkit<unknown> {\n const byName = new Map(skills.map((skill) => [skill.name, skill]))\n const tools = AiToolkit.make(loadSkill(catalog(skills)))\n\n const layer = tools.toLayer(\n Effect.gen(function* () {\n const context = yield* Effect.context<unknown>()\n\n return {\n load_skill: Effect.fnUntraced(function* ({ name }) {\n const skill = byName.get(name)\n\n if (skill === undefined) {\n return yield* ToolFailure.fail(\n `No skill named ${name}. The skills are: ${skills.map((known) => known.name).join(\", \")}.`\n )\n }\n\n const resolved = yield* Instructions.resolve(skill.instructions).pipe(Effect.provideContext(context))\n\n return resolved.text\n })\n }\n })\n )\n\n return Object.assign(tools, { layer })\n}\n\n/**\n * Creates a toolkit from a list of skills: the `load_skill` tool, whose description lists each\n * skill's name and when to use it, and its handler's `layer`.\n *\n * **Details**\n *\n * Merge the toolkit into a loop's own with `Toolkit.merge`, and provide its `layer`, which requires\n * the services the skills' instructions need. The model loads a skill by name; a name that isn't\n * in the list is a failure the model reads, with the names there are.\n *\n * The handler resolves only the skills of its own list. To offer each user the skills they may\n * use, build the toolkit in the turn from that user's list, and provide its layer to the loop's\n * stream (`Stream.provide(skills.layer)`): building one does no work.\n *\n * **Example** (Giving an assistant its playbooks)\n *\n * ```ts\n * import { Skill } from \"effect-assistant\"\n * import * as Layer from \"effect/Layer\"\n * import * as Toolkit from \"effect/ai/Toolkit\"\n *\n * const AssistantSkills = Skill.toolkit([orderingCampaigns, sharingAssets])\n *\n * const MyAssistantLive = MyAssistant.toLayer((turn) =>\n * turn.loop({ instructions, toolkit: Toolkit.merge(MyTools, AssistantSkills) })\n * ).pipe(Layer.provide([MyToolsLive, AssistantSkills.layer]))\n * ```\n *\n * **Example** (Offering each user the skills their role allows)\n *\n * ```ts\n * import { Skill } from \"effect-assistant\"\n * import * as Effect from \"effect/Effect\"\n * import * as Stream from \"effect/Stream\"\n * import * as Toolkit from \"effect/ai/Toolkit\"\n *\n * const MyAssistantLive = MyAssistant.toLayer((turn) =>\n * Stream.unwrap(\n * Effect.map(roleOf(turn.chatId), (role) => {\n * const skills = Skill.toolkit(allSkills.filter((skill) => allowed(role, skill)))\n *\n * return turn\n * .loop({ instructions, toolkit: Toolkit.merge(MyTools, skills) })\n * .pipe(Stream.provide(skills.layer))\n * })\n * )\n * )\n * ```\n *\n * @category constructors\n * @since 0.4.0\n */\nexport const toolkit: <const Skills extends ReadonlyArray<Skill<unknown>>>(\n skills: Skills\n) => Toolkit<Services<Skills[number]>> = toolkitOf\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0DA,MAAa,QACX,MACA,aAMc;CAAE;CAAM,aAAa,QAAQ;CAAa,cAAc,QAAQ;AAAa;AAU7F,MAAM,aAAa,gBACjB,KAAK,KAAK,cAAc;CACtB;CACA,YAAY,OAAO,OAAO,EAAE,MAAM,OAAO,OAAO,CAAC;CACjD,SAAS,OAAO;CAChB,SAASA;AACX,CAAC;AAuBH,MAAM,WAAW,WACf;CACE;CACA;CACA;CACA,GAAG,OAAO,KAAK,UAAU,KAAK,MAAM,KAAK,IAAI,MAAM,aAAa;AAClE,CAAC,CAAC,KAAK,IAAI;AAMb,SAAS,UAAU,QAAyD;CAC1E,MAAM,SAAS,IAAI,IAAI,OAAO,KAAK,UAAU,CAAC,MAAM,MAAM,KAAK,CAAC,CAAC;CACjE,MAAM,QAAQ,UAAU,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;CAEvD,MAAM,QAAQ,MAAM,QAClB,OAAO,IAAI,aAAa;EACtB,MAAM,UAAU,OAAO,OAAO,QAAiB;EAE/C,OAAO,EACL,YAAY,OAAO,WAAW,WAAW,EAAE,QAAQ;GACjD,MAAM,QAAQ,OAAO,IAAI,IAAI;GAE7B,IAAI,UAAU,KAAA,GACZ,OAAO,OAAOC,KACZ,kBAAkB,KAAK,oBAAoB,OAAO,KAAK,UAAU,MAAM,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE,EAC1F;GAKF,QAAO,OAFiBC,QAAqB,MAAM,YAAY,CAAC,CAAC,KAAK,OAAO,eAAe,OAAO,CAAC,EAAA,CAEpF;EAClB,CAAC,EACH;CACF,CAAC,CACH;CAEA,OAAO,OAAO,OAAO,OAAO,EAAE,MAAM,CAAC;AACvC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsDA,MAAa,UAE4B"}
|
package/dist/Specialist.d.ts
CHANGED
|
@@ -3,7 +3,6 @@ import { Instructions_d_exports, Resolved } from "./Instructions.js";
|
|
|
3
3
|
import { AssistantLoop_d_exports, Event, ToolkitServices } from "./AssistantLoop.js";
|
|
4
4
|
import { CurrentChat } from "./CurrentChat.js";
|
|
5
5
|
import { RunId } from "./RunId.js";
|
|
6
|
-
import { Skill } from "./Skill.js";
|
|
7
6
|
import * as Context from "effect/Context";
|
|
8
7
|
import * as Effect from "effect/Effect";
|
|
9
8
|
import * as Layer from "effect/Layer";
|
|
@@ -103,15 +102,13 @@ export type RunServices = CurrentChat | Runner | LanguageModel.LanguageModel;
|
|
|
103
102
|
* @category models
|
|
104
103
|
* @since 0.1.0
|
|
105
104
|
*/
|
|
106
|
-
export interface LoopOptions<T extends Record<string, Tool.Any> = Record<never, never
|
|
105
|
+
export interface LoopOptions<T extends Record<string, Tool.Any> = Record<never, never>> {
|
|
107
106
|
readonly instructions: string | Resolved;
|
|
108
|
-
/** The loop's tools, as a native toolkit whose handlers come from the layer. */
|
|
109
|
-
readonly toolkit?: Toolkit.Toolkit<T> | undefined;
|
|
110
107
|
/**
|
|
111
|
-
*
|
|
112
|
-
*
|
|
108
|
+
* The loop's tools, as a native toolkit whose handlers come from the layer. Merge in a
|
|
109
|
+
* `Skill.toolkit(...)` for playbooks.
|
|
113
110
|
*/
|
|
114
|
-
readonly
|
|
111
|
+
readonly toolkit?: Toolkit.Toolkit<T> | undefined;
|
|
115
112
|
/** The model calls allowed per brief. Defaults to 8. */
|
|
116
113
|
readonly maxSteps?: number | undefined;
|
|
117
114
|
}
|
|
@@ -131,8 +128,8 @@ export interface Run {
|
|
|
131
128
|
* (follow-ups and steering). Read it before every model call, as the loop does.
|
|
132
129
|
*/
|
|
133
130
|
readonly history: Effect.Effect<ReadonlyArray<Prompt.Message>>;
|
|
134
|
-
/** Runs the agent loop over the run's history, with the given instructions
|
|
135
|
-
readonly loop: <T extends Record<string, Tool.Any> = Record<never, never
|
|
131
|
+
/** Runs the agent loop over the run's history, with the given instructions and tools. */
|
|
132
|
+
readonly loop: <T extends Record<string, Tool.Any> = Record<never, never>>(options: LoopOptions<T>) => Stream.Stream<Event, AiError.AiError, LanguageModel.LanguageModel | ToolkitServices<T>>;
|
|
136
133
|
}
|
|
137
134
|
/**
|
|
138
135
|
* Represents a specialist's handler, which works on a brief and returns a stream of what the loop
|
package/dist/Specialist.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Specialist.d.ts","names":[],"sources":["../src/Specialist.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"Specialist.d.ts","names":[],"sources":["../src/Specialist.ts"],"mappings":";;;;;;;;;;;;;;;;;;;cA0BM;;;;;;;iBAQW,WAAW;YAChB,gBAAgB;WACjB,MAAM;;WAEN;;WAEA,SAAS,QAAQ,IAAI,kBAAkB,OAAO;;;;;WAK9C,UAAU,WAAW,YAAY,WAAW,YACnD,QAAQ,QAAQ,GAAG,MAAM,OAAO,OAAO,QAAQ,GAAG,KAAK,GAAG,OACxD,cAAc,QAAQ,IAAI,IAAI,cAAc,KAAK,KAAK,sBACrD,MAAM,MAAM,kBAAkB,cAAc,QAAQ,IAAI,MAAM,QAAQ,eAAe,aAAa,IAAI;;;;;;;;;;;;;;YAejG,cAAc,eAAe;WAG1B;aACE;aACA,UAAU;;;;;;;;;;;;YAaf,aAAa,KAAK,QAAQ,GAAG,KAAK;;;;;;;YAQlC,MAAM;;;;;;;iBAQD,kBAAkB;WACxB,oBAAoB;;;;;;;;YASnB,UAAU,KAAK,UAAU,iBAAiB,QAAQ,kBAAkB;;;;;;;;YASpE,cAAc,cAAc,SAAS,cAAc;;;;;;;iBAQ9C,YAAY,UAAU,eAAe,KAAK,OAAO;WACvD,uBAAuB;;;;;WAKvB,UAAU,QAAQ,QAAQ;;WAE1B;;;;;;;;iBASM;WACN,OAAO;;WAEP,QAAQ;WACR,OAAO;;;;;WAKP,SAAS,OAAO,OAAO,cAAc,OAAO;;WAE5C,OAAO,UAAU,eAAe,KAAK,OAAO,sBACnD,SAAS,YAAY,OAClB,OAAO,OACV,OACA,QAAQ,SACR,cAAc,gBAAgB,gBAA8B;;;;;;;;;;;;YAcpD,QAAQ,WAAW,KAAK,MAClC,KAAK,QAEH,OAAO,OAAO,gBAA8B,KAC5C,OAAO,OAAO,OAAO,OAAO,gBAA8B,aAAa;;;;;;;;;;iBAW1D;WACN,MAAM,OAAO,QACnB,KAAK,QAAQ,OAAO,OAAO,gBAA8B,qBAE1D,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBA8CL,aAAc,qBAAmB,MACtC,MAAI;;WAGC;MAEV,WAAW;;;;;;;;;qBA0CD,OAAK,OAAA;;;;;;;;;;;;;YAoBN,eAAe,MAAM;;;;;;;iBAQhB;;WAEN;WACA,OAAO;;;;;;;;;;;;;;;;;;;qBAeL,wBAAwB;;;WAkBjB,QAAC,SAAS,iBAAiB,OAAO,OAAO,OAAO;;WAE9C,UAAC,OAAO,OAAO,iBAAiB,OAAO,gCAAgC;;WAE1E,OAAC,OAAO,UAAU,OAAO,aAAa;;;;;;;;;;qBAR5C,eAAe"}
|