Chapter 16 — Tools and LLM providers¶
Examples:
27_tool_attr.mty,
28_agent_with_llm_field.mty,
29_streaming.mty,
30_stream_consume.mty.
This is where Mighty's LLM-agent surface starts. Five pieces compose into a real agent:
- A
@tool(description, cap: ...)decorator on a plain fn turns it into an LLM-visible tool with a JSON schema and a runtime- enforced capability set. - An LLM provider (
AnthropicClient,OpenAiClient,GeminiClient,BedrockClient) carrying typedcomplete()/messages_stream()calls. Tainted[T]on every provider response — the compiler tracks prompt-injection paths to sinks.- An agent that owns the panel, the budget, and the per-turn loop.
std.swarmto fan one prompt across multiple providers under one budget.
The @tool decorator¶
@tool("Read a UTF-8 text file under ./data/.", cap: fs.read("./data/**"))
fn read_doc(path: Str) -> Str!IoErr {
std.fs.read_to_string(path)?
}
What the macro generates:
__tool_descriptor_read_doc()returns aToolDescriptorwith the JSON schema derived from the fn signature.__tool_invoke_read_doc(args: Json) -> Result[Json, ToolErr]parsesargs, callsread_doc, serialises the return.__tool_register_read_doc(registry: &mut ToolRegistry)plugs the pair into a registry forMember::askto discover.
The cap: clause is enforced by the runtime. Any std.fs.read
call inside the tool body that tries to read outside
./data/** returns Result::Err(forbidden: ...).
A first LLM call¶
use std.llm.AnthropicClient
fn ask_once(client: AnthropicClient, q: Str) -> Tainted[Str] {
client.complete(
model = "claude-opus-4-7",
system = "You are a helpful assistant.",
prompt = q,
max_tokens = 1024,
)
}
Notice the return type is Tainted[Str], not Str. The compiler
forces you to sanitise (or Untaint::after(value, "audit
justification")) before feeding the reply into a sink.
Streaming¶
For long-running completions, messages_stream() returns a
MessageStream that yields Delta values:
let mut stream = client.messages_stream(model, system, prompt);
while let Some(delta) = stream.next() {
match delta {
Delta.Text(s) => print(s),
Delta.ToolUse(t) => handle_tool(t),
Delta.End(stop) => break,
}
}
The while let Some(d) = stream.next() form is v0.29 Track D.
See examples/29_streaming.mty and examples/30_stream_consume.mty.
Try it¶
mty check examples/27_tool_attr.mty
mty check examples/28_agent_with_llm_field.mty
mty check examples/29_streaming.mty
mty check examples/30_stream_consume.mty
All four print ok: <path>. To actually fire a provider call,
see Demo 07.