Give the model a tool
Add a typed operation the model can call.
A tool is a typed function the model can call. This page adds one to a new
bookmarks extension: bookmarks.check takes a link and says whether it is an
http or https URL. It stores nothing yet; the next page
does.
Define the tool
Section titled “Define the tool”import { Mycelium } from "@fungi.computer/mycelium";import { defineExtension } from "@fungi.computer/shiitake/extensions";import * as Schema from "effect/Schema";
const isWebLink = (url: string) => URL.canParse(url) && ["http:", "https:"].includes(new URL(url).protocol);
/** One stateless tool: the model checks a link before it shares it. */export const bookmarks = defineExtension("bookmarks").define({ prompt: () => [ { name: "guide", text: "Check a link with bookmarks.check first." }, ], tools: () => Mycelium.module({ id: "example.bookmarks", name: "bookmarks", operations: { check: Mycelium.operation({ replay: "safe", description: "Check that a link is an http(s) URL; return its host.", input: Schema.Struct({ url: Schema.String }), output: Schema.Struct({ ok: Schema.Boolean, host: Schema.String }), execute: ({ url }) => Promise.resolve( isWebLink(url) ? { ok: true, host: new URL(url).host } : { ok: false, host: "" }, ), }), }, }),});Tools are Mycelium operations grouped in a
module. The module’s name is how the model reaches it: bookmarks.check. Each
operation declares its input and output with an Effect schema. Mycelium decodes
the model’s input before execute runs and describes both shapes to the model,
so the model learns the tool from the schema itself.
replay: "safe" says calling it twice with the same input is harmless. Mark a
tool "unsafe" (the default) when a repeated call would change something twice.
Script a model that calls it
Section titled “Script a model that calls it”import { callTool, memoryHost } from "@fungi.computer/shiitake/testing";import { bookmarks } from "./extension.js";
/** A scripted model calls bookmarks.check, then replies. */export async function modelToolRun() { const host = await memoryHost({ extensions: [bookmarks], script: [ callTool("bookmarks.check", { url: "https://shiitake.shiit.app/" }), "That link is fine.", ], }); try { await host.agent .session("links") .send("Is this link OK?", { requestId: "check" }); return await host.settle("links"); } finally { await host.close(); }}callTool("bookmarks.check", input) scripts one model turn that calls the tool;
the string after it is the model’s next reply. The test host runs the scripted
call through the same admitted operation a real model would reach. It evaluates
no model-written code, so it is for tests and examples only.
import { modelToolRun } from "./run.js";
const snapshot = await modelToolRun();// The tool result the model received, then its reply.for (const { value } of snapshot.messages.slice(-2)) if (value.role === "toolResult" || value.role === "assistant") for (const part of value.content) if (part.type === "text") console.log(part.text);Install Mycelium and Effect alongside Shiitake, then run it:
npm install @fungi.computer/mycelium effect@4.0.0node --import tsx main.tsIt prints the tool result the model received, then the model’s reply:
{"ok":true,"host":"shiitake.shiit.app"}That link is fine.Next: Remember things.