npm package: @rubriclab/agents
Description: TypeScript framework for building AI agents with structured outputs, tools, and type-safe execution.
Latest version: 0.0.84

# @rubriclab/agents

TypeScript framework for building AI agents with structured outputs, tools, and type-safe execution.

It is part of Rubric's architecture for Generative UI when used with:
- [@rubriclab/actions](https://github.com/rubriclab/actions)
- [@rubriclab/blocks](https://github.com/rubriclab/blocks)
- [@rubriclab/chains](https://github.com/rubriclab/chains)
- [@rubriclab/agents](https://github.com/rubriclab/agents)
- [@rubriclab/events](https://github.com/rubriclab/events)

[Demo](https://chat.rubric.sh)

## Get Started
### Installation
`bun add @rubriclab/actions`

> @rubriclab scope packages are not built, they are all raw typescript. If using in a next.js app, make sure to transpile.

```ts
// next.config.ts
import type { NextConfig } from  'next' 
export default {
	transpilePackages: ['@rubriclab/agents'],
	reactStrictMode: true
} satisfies  NextConfig
```

> If using inside the monorepo (@rubric), simply add `{"@rubriclab/agents": "*"}` to dependencies and then run `bun i`

## Quick Start

### Simple Generation
```ts
import { generateObject } from '@rubriclab/agents'
import { z } from 'zod'

const result = await generateObject({
	apiKey: env.OPENAI_API_KEY,
	model: 'gpt-5.2',
	systemPrompt: 'Extract user info',
	schema: z.object({
		name: z.string(),
		age: z.number()
	}),
	messages: [{ role: 'user', content: 'I am John, 25 years old' }]
})
// { name: "John", age: 25 }
```

### Agent with Tools
A tool agent will recurse until it has what it needs to export a final answer.
By default, the format of the final answer is `{ answer: string }`.

```ts
import { createAgent, createTool } from '@rubriclab/agents'
import { z } from 'zod'

const systemPrompt = 'You are a weather agent. Use tools to get the weather for the user.'

const weatherTool = createTool({
	schema: {
		input: z.object({ city: z.string() }),
		output: z.object({ temp: z.number(), condition: z.string() })
	},
	execute: async ({ city }) => ({ temp: 72, condition: 'sunny' })
})

const { executeAgent, eventTypes, __ToolEvent, __ResponseEvent } = createAgent({
	systemPrompt,
	tools: { getWeather: weatherTool }
})
```

```ts
// Execute the agent

'use server'
import env from '~/env'
import { executeWeatherAgent } from './agent'

export async function sendMessage({ userId, message }: { userId: string; message: string }) {
	const { answer } = await executeWeatherAgent({
		messages: [{ role: 'user', content: message }],
		onEvent: async events => {
			switch (events.type) {
				case 'function_call':
					switch (events.name) {
						case 'getWeather':
							console.log(`the temperature in ${events.arguments.city} is ${events.result.temp} degrees`)
					}
				case 'assistant_message':
					console.log(events.message.answer)
					break
			}
		},
		openAIKey: env.OPENAI_API_KEY
	})

	return answer
}
```

### Response Format
You can override the default response format and agents that return complex types.
Supplying an agent BOTH tools and response format will cause the agent to recurse using tools, and when ready, the response in the right format.

```ts
const responseFormat = createResponseFormat({
	name: 'research_results',
	schema: z.object({
		answer: z.string(),
		sources: z.array(z.object({ title: z.string(), url: z.string() }))
	})
})
```

```ts
const { executeAgent } = createAgent({
	systemPrompt,
	tools: { ... },
	responseFormat
})
```

## Usage with other packages

### Usage with [@rubriclab/events](https://github.com/RubricLab/events)
The `createAgent(...)` function returns `eventTypes` which is a record of Zod types.

```ts
const { executeAgent, eventTypes } = createAgent({
	systemPrompt,
	tools: { research: researchTool },
})

export { eventTypes as researchAgentEventTypes }
export { executeAgent as executeResearchAgent }
```

You can easily pass these events to the events package
```ts
import { createEventTypes } from '@rubriclab/events'
import { researchAgentEventTypes } from './agent'

export const eventTypes = createEventTypes({
	...researchAgentEventTypes
})
```

You can then publish them safely:

```ts
import env from '~/env'
import { executeResearchAgent } from './agent'
import { publish } from './events/server'

await executeResearchAgent({
	messages: [{ role: 'user', content: 'do something' }],
	onEvent: async events => {
		switch (events.type) {
			case 'function_call':
				await publish({
					channel: userId,
					eventType: events.name,
					payload: events
				})
				break
			case 'assistant_message':
				await publish({
					channel: userId,
					eventType: events.type,
					payload: events
				})
				break
		}
	},
	openAIKey: env.OPENAI_API_KEY
})
```

And consume them on the client with end to end safety:

```ts
useEvents({
		id: userId,
		on: {
			research: ({arguments, result}) => console.log(arguments.query, result.map(r => r.title)),
			assistant_message: ({message}) => console.log(message.answer)
		}
	})
```

### Usage with [@rubriclab/actions](https://github.com/RubricLab/events)
coming soon...

### Usage with [@rubriclab/blocks](https://github.com/RubricLab/events)
coming soon...

### Usage with [@rubriclab/chains](https://github.com/RubricLab/events)
coming soon...