Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ In another terminal:

```sh
pnpm dlx @restatedev/restate deployments register http://localhost:9080
curl localhost:8080/Hello/ask --json '"Say hello in one sentence."'
curl localhost:8080/Hello/ask --json '{"message":"Say hello in one sentence."}'
```

TypeScript callers can use Restate's typed client instead, as the
Expand All @@ -98,7 +98,7 @@ import {hello} from "@restatedev/agentlib-examples/hello";

const answer = await connect({url: "http://localhost:8080"})
.client(hello)
.ask("Say hello in one sentence.");
.ask({message: "Say hello in one sentence."});
console.log(answer);
```

Expand Down Expand Up @@ -157,7 +157,7 @@ repeat a completed model call.
| MCP | `mcpTools` (`/mcp`), passed as `run(input, {tools})` | [17](packages/examples/src/lessons/17_mcp_tools.ts) |
| AG-UI clients | `onProgress`, `onApproval` and client-run tools as AG-UI events | [18](packages/examples/src/lessons/18_ag_ui.ts) |
| Guardrails | `beforeRun`, `beforeStep`, `beforeTool`, `afterTool`, `afterRun`, `before` | [13](packages/examples/src/lessons/13_tool_guardrail.ts) |
| Web search | `webSearch` (`/web-search`) | [14](packages/examples/src/lessons/14_web_search.ts) |
| Web search | Example-local `asyncTool` calling Tavily | [14](packages/examples/src/lessons/14_web_search.ts) |
| Skills | `agent({skills})` with any `SkillSource`, `skills([...])` | [19](packages/examples/src/lessons/19_skills.ts) |
| Large tool results | `agent({toolResults})`: previews, read on with `read_tool_result` | — |
| Long runs | `agent({compaction, contextWindow})`, `ContextOverflowError` | — |
Expand Down
17 changes: 8 additions & 9 deletions packages/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,14 @@ go from a first run to multi-agent workflows.

## Subpaths

| Import | Purpose |
| --------------------------------- | --------------------------------------------- |
| `@restatedev/agentlib` | Agents, tools, conversations, guardrails |
| `@restatedev/agentlib/openai` | OpenAI and Chat Completions-compatible models |
| `@restatedev/agentlib/provider` | Models through the Vercel AI SDK providers |
| `@restatedev/agentlib/mcp` | Tools from MCP servers |
| `@restatedev/agentlib/program` | Programmatic tool calling in a sandbox |
| `@restatedev/agentlib/web-search` | Web search tools |
| `@restatedev/agentlib/discovery` | Expose agents and tools for discovery |
| Import | Purpose |
| -------------------------------- | --------------------------------------------- |
| `@restatedev/agentlib` | Agents, tools, conversations, guardrails |
| `@restatedev/agentlib/openai` | OpenAI and Chat Completions-compatible models |
| `@restatedev/agentlib/provider` | Models through the Vercel AI SDK providers |
| `@restatedev/agentlib/mcp` | Tools from MCP servers |
| `@restatedev/agentlib/program` | Programmatic tool calling in a sandbox |
| `@restatedev/agentlib/discovery` | Expose agents and tools for discovery |

## License

Expand Down
4 changes: 0 additions & 4 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,6 @@
"types": "./dist/provider.d.ts",
"import": "./dist/provider.js"
},
"./web-search": {
"types": "./dist/web-search.d.ts",
"import": "./dist/web-search.js"
},
"./discovery": {
"types": "./dist/discovery.d.ts",
"import": "./dist/discovery.js"
Expand Down
46 changes: 0 additions & 46 deletions packages/core/src/web-search.ts

This file was deleted.

63 changes: 28 additions & 35 deletions packages/examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ the services in tutorial order, [app.ts](src/app.ts) serves them, and
Each prompt handler declares a Zod input schema with a scenario-specific
default for the request editor. The research example also supplies demo sources. Rediscover
the endpoint after changing a schema so Restate receives the updated defaults.
String bodies use `.meta({default: ...})` to prefill the editor while keeping
the request body required; object fields use `.default(...)`.
Prompt inputs use JSON objects with `.default(...)` on their fields. This keeps
string values correctly quoted in the Restate UI request editor, ready to send.

| Lesson | New concept | Source |
| ------ | ------------------------------------- | ------------------------------------------------------------------ |
Expand Down Expand Up @@ -46,7 +46,7 @@ If you have a specific goal, start here:
| Steer or interrupt a running agent | [09](src/lessons/09_support_session.ts) |
| Route a request by its meaning | [12](src/lessons/12_routing.ts) |
| Enforce a budget before an action | [13](src/lessons/13_tool_guardrail.ts) |
| Answer with current public sources | [14](src/lessons/14_web_search.ts) |
| Answer with public sources | [14](src/lessons/14_web_search.ts) |
| Let agents review each other\'s work | [15](src/lessons/15_peer_review_rpc.ts) |
| Show progress while an agent works | [16](src/lessons/16_progress_feed.ts) |
| Use tools from an MCP server | [17](src/lessons/17_mcp_tools.ts) |
Expand Down Expand Up @@ -86,7 +86,7 @@ to the agent and returns its answer as a JSON string. Each call starts with no
conversation history.

```sh
curl --globoff "$RESTATE_CALL_URL/Hello/ask" --json '"Say hello in one sentence."'
curl --globoff "$RESTATE_CALL_URL/Hello/ask" --json '{"message":"Say hello in one sentence."}'
```

The response is just the answer, such as `"Hello!"`. A run that stopped or was
Expand All @@ -105,7 +105,7 @@ result in its answer.

```sh
curl --globoff "$RESTATE_CALL_URL/TicketLookup/ask" \
--json '"What is the status of ticket T-42?"'
--json '{"message":"What is the status of ticket T-42?"}'
```

The lesson uses `asyncTool` with an ordinary `async execute(...)` callback.
Expand All @@ -127,8 +127,8 @@ messages. Pass them to the agent, then save `result.messages` afterward. Return
only the reply; the history stays in object state.

```sh
curl --globoff "$RESTATE_CALL_URL/ChatSession/bob/ask" --json '"My name is Bob."'
curl --globoff "$RESTATE_CALL_URL/ChatSession/bob/ask" --json '"What is my name?"'
curl --globoff "$RESTATE_CALL_URL/ChatSession/bob/ask" --json '{"message":"My name is Bob."}'
curl --globoff "$RESTATE_CALL_URL/ChatSession/bob/ask" --json '{"message":"What is my name?"}'
```

Both requests use `bob`, so the second sees the first exchange. A different key gets
Expand All @@ -142,7 +142,7 @@ answer and gives the caller the typed object directly.

```sh
curl --globoff "$RESTATE_CALL_URL/TicketTriage/classify" \
--json '"I was charged twice for my subscription."'
--json '{"message":"I was charged twice for my subscription."}'
```

A successful response is an object such as
Expand Down Expand Up @@ -180,7 +180,7 @@ Start a request in one terminal:

```sh
curl --globoff "$RESTATE_CALL_URL/TicketApproval/bob/ask" \
--json '"Close ticket T-42 after I approve it."'
--json '{"message":"Close ticket T-42 after I approve it."}'
```

This request waits. Before executing the tool, the SDK calls `onApproval(request)`
Expand Down Expand Up @@ -293,7 +293,7 @@ to one handler instead. Virtual objects and workflows also take a `key`.

```sh
curl --globoff "$RESTATE_CALL_URL/RefundAgent/ask" \
--json '"Refund order O-99 for $12.50 and tell me the refund policy."'
--json '{"message":"Refund order O-99 for $12.50 and tell me the refund policy."}'
```

Each call is a durable child invocation. Interrupting the run cancels an
Expand Down Expand Up @@ -331,7 +331,7 @@ Authenticate with `modal token new`, or set `MODAL_TOKEN_ID` and

```sh
curl --globoff "$RESTATE_CALL_URL/CodingAgent/ask" \
--json '"Write primes.js that prints the first 10 primes, then run it."'
--json '{"task":"Write primes.js that prints the first 10 primes, then run it."}'
```

## 12 — Route a request to a specialist
Expand All @@ -351,7 +351,7 @@ function* (question) {

```sh
curl --globoff "$RESTATE_CALL_URL/SupportRouter/answer" \
--json '"I cannot log into my account. How can I regain access?"'
--json '{"question":"I cannot log into my account. How can I regain access?"}'
```

The specialist starts a fresh run with only the question. Use `handoff` when
Expand Down Expand Up @@ -381,30 +381,24 @@ Raise `maxTotalCents` to `15000` to see the allowed path. The reservation is a
demo result; connect a real booking API only after obtaining the quote from your
own trusted service.

## 14 — Answer with current public sources
## 14 — Search the web with a custom tool

`webSearch` sends a public query to Tavily and journals the bounded results, so
a replay does not repeat the HTTP request. Without an API key it uses Tavily's
free, rate-limited keyless access; pass `webSearch({apiKey})` for higher limits. The agent is instructed to cite returned URLs and treat
snippets as evidence, not instructions.
[14_web_search.ts](src/lessons/14_web_search.ts) defines `searchWeb` locally with
`asyncTool`. Its `execute` function calls the [Tavily Search API](https://docs.tavily.com/documentation/api-reference/endpoint/search)
using `fetch`, validates the response, and returns up to five bounded snippets.
`asyncTool` journals the result, so replay does not repeat a completed search.
This is an example of integrating an API, not a built-in library tool.

```ts
const researcher = agent({
model,
maxSteps: 4,
instructions: "Search the web before answering. Cite URLs returned by searchWeb, ...",
tools: {searchWeb: webSearch()},
});
```
Set `TAVILY_API_KEY` in the example server's environment before starting it.
The key is read inside `execute` and is never included in the model's tool arguments.
Keep private data out of queries; the agent treats snippets as untrusted evidence
and cites the returned URLs.

```sh
curl --globoff "$RESTATE_CALL_URL/WebResearch/answer" \
--json '"Find the current Restate TypeScript SDK docs and cite their URLs."'
--json '{"question":"Find the Restate TypeScript SDK docs and cite their URLs."}'
```

Keep private data out of search queries. A key, when you pass one, stays in
application code and is not part of the model's tool schema.

## 15 — Let agents exchange feedback over RPC

`PeerReview` asks `PeerAuthor` for a draft, sends that draft to two
Expand Down Expand Up @@ -432,7 +426,7 @@ sequenceDiagram

```sh
curl --globoff "$RESTATE_CALL_URL/PeerReview/answer" \
--json '"Write a two-sentence release note for a feature that retries transient failures and resumes completed steps from a journal."'
--json '{"brief":"Write a two-sentence release note for a feature that retries transient failures and resumes completed steps from a journal."}'
```

The caller receives only the revised answer. Restate journals each RPC, so a
Expand All @@ -456,7 +450,7 @@ each tool into a journaled step. The lesson gives an agent the tools of the
Restate docs server at `https://docs.restate.dev/mcp`.

```sh
curl "$RESTATE_CALL_URL/DocsGuide/ask" --json '"What is a virtual object?"'
curl "$RESTATE_CALL_URL/DocsGuide/ask" --json '{"question":"What is a virtual object?"}'
```

## 18 — Serve an AG-UI client
Expand Down Expand Up @@ -514,7 +508,7 @@ Skills defined in code need no folder: use `skills([{name, description,
instructions, files}])` from `@restatedev/agentlib`.

```sh
curl "$RESTATE_CALL_URL/SkilledWriter/write" --json '"Release notes for 2.4.0: added CSV export, fixed the login redirect loop, bumped lodash."'
curl "$RESTATE_CALL_URL/SkilledWriter/write" --json '{"request":"Release notes for 2.4.0: added CSV export, fixed the login redirect loop, bumped lodash."}'
```

A skill's scripts are out of scope: the SDK offers no shell to run them. An
Expand Down Expand Up @@ -598,9 +592,8 @@ commit `60d1eda2e5f3aae96db2efeba498e52367a01648`:
| Racing agents | [racing-agents.ts](https://github.com/restatedev/ai-examples/blob/60d1eda2e5f3aae96db2efeba498e52367a01648/typescript-restate-only/tour-of-agents/src/racing-agents.ts) | Embedded operations, first completed success, cancellation with joined cleanup |

The later goal-based lessons draw on the same patterns described by
[Anthropic's workflow guide](https://www.anthropic.com/engineering/building-effective-agents),
[OpenAI's tool guardrails](https://openai.github.io/openai-agents-python/guardrails/),
and the [AI SDK web-search recipe](https://ai-sdk.dev/cookbook/node/web-search-agent).
[Anthropic's workflow guide](https://www.anthropic.com/engineering/building-effective-agents)
and [OpenAI's tool guardrails](https://openai.github.io/openai-agents-python/guardrails/).
They use this library's Restate handlers and tools rather than copying another
framework's control flow.

Expand Down
7 changes: 5 additions & 2 deletions packages/examples/src/lessons/01_hello.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@ export const hello = restate.service({
name: "Hello",
handlers: {
ask: restate.schemas(
{input: z.string().min(1).meta({default: "Say hello in one sentence."}), output: z.string()},
function* (message) {
{
input: z.object({message: z.string().default("Say hello in one sentence.")}),
output: z.string(),
},
function* ({message}) {
const result = yield* assistant.run(message);
if (result.status !== "completed") return result.output ?? result.reason;
return result.output;
Expand Down
4 changes: 2 additions & 2 deletions packages/examples/src/lessons/02_tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ export const ticketLookup = restate.service({
handlers: {
ask: restate.schemas(
{
input: z.string().min(1).meta({default: "What is the status of ticket T-42?"}),
input: z.object({message: z.string().default("What is the status of ticket T-42?")}),
output: z.string(),
},
function* (message) {
function* ({message}) {
const result = yield* support.run(message);
if (result.status !== "completed") return result.output ?? result.reason;
return result.output;
Expand Down
12 changes: 7 additions & 5 deletions packages/examples/src/lessons/03_conversation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ export const chatSession = restate.object({
handlers: {
ask: restate.schemas(
{
input: z
.string()
.min(1)
.meta({default: "My name is Bob. Remember it for our next conversation."}),
input: z.object({
message: z
.string()

.default("My name is Bob. Remember it for our next conversation."),
}),
output: z.string(),
},
function* (message) {
function* ({message}) {
const messages = (yield* restate.state().get<Message[]>("messages")) ?? [];
const result = yield* assistant.run(message, {messages});
restate.state().set("messages", result.messages);
Expand Down
12 changes: 7 additions & 5 deletions packages/examples/src/lessons/04_structured_output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@ export const ticketTriage = restate.service({
handlers: {
classify: restate.schemas(
{
input: z
.string()
.min(1)
.meta({default: "I was charged twice for my subscription. Please help me get a refund."}),
input: z.object({
message: z
.string()

.default("I was charged twice for my subscription. Please help me get a refund."),
}),
output: classificationSchema,
},
function* (message) {
function* ({message}) {
const result = yield* triage.run(message);
if (result.status !== "completed")
throw new TerminalError(`Triage ${result.status}: ${result.reason}`);
Expand Down
6 changes: 3 additions & 3 deletions packages/examples/src/lessons/05_evaluator_optimizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ import {z} from "zod";
import {exampleModel as model} from "../shared.js";

const MAX_ITERATIONS = 3;
const evaluationSchema = z.object({approved: z.boolean(), feedback: z.string().min(1).max(4000)});
const evaluationSchema = z.object({approved: z.boolean(), feedback: z.string().max(4000)});

const generator = agent({
name: "code-writer",
model,
maxSteps: 3,
instructions:
"Code writer. Write a small, self-contained TypeScript solution to the task. When a previous draft and feedback are supplied, correct that draft. Return code as data; nothing is executed.",
output: z.object({code: z.string().min(1).max(24000)}),
output: z.object({code: z.string().max(24000)}),
});
const evaluator = agent({
name: "code-reviewer",
Expand All @@ -33,7 +33,7 @@ export const codeReview = restate.service({
input: z.object({
task: z
.string()
.min(1)

.max(4000)
.default(
"Write a TypeScript function that returns the first item of an array, or undefined when it is empty.",
Expand Down
Loading
Loading