Skip to content

RubricLab/agents

Repository files navigation

@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:

Demo

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.

// 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

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 }.

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 }
})
// 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.

const responseFormat = createResponseFormat({
	name: 'research_results',
	schema: z.object({
		answer: z.string(),
		sources: z.array(z.object({ title: z.string(), url: z.string() }))
	})
})
const { executeAgent } = createAgent({
	systemPrompt,
	tools: { ... },
	responseFormat
})

Usage with other packages

The createAgent(...) function returns eventTypes which is a record of Zod types.

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

import { createEventTypes } from '@rubriclab/events'
import { researchAgentEventTypes } from './agent'

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

You can then publish them safely:

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:

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

coming soon...

coming soon...

coming soon...

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Contributors 2

  •  
  •