@pompeii-labs/magma
Version:
The Typescript framework to build AI agents quickly and easily
328 lines (262 loc) • 10.5 kB
Markdown
<div align="center">
<img alt="Magma Framework logo" src="https://db.productdialog.com/storage/v1/object/public/images/magma-header.jpg">
</div>
<br/>
<div align="center"><strong>Turn your workflows into a workforce.</strong></div>
<div align="center">Create and deploy conversational agents without any of the headaches.</div>
<br/>
<div align="center">
[](https://www.npmjs.com/package/@pompeii-labs/magma)
[](https://join.slack.com/t/magmacommunity/shared_invite/zt-2tghhq3av-Xn9k9ntwN5ZwqvxbWcfsTg)
[](https://github.com/pompeii-labs/magma)
</div>
Magma is a framework that lets you create AI agents without the headache. No complex chains, no confusing abstractions - just write the logic you want your agent to have.
Want to try it out? [Chat with Dialog](https://chat.productdialog.com/ac94ab36-c5bb-4b54-a195-2b6b2499dcff), our user research agent built with Magma!
1. Install Magma:
```bash
npm i @pompeii-labs/magma
```
2. Create your first agent:
```ts
import { MagmaAgent } from "@pompeii-labs/magma";
// Magma Agents are class based, so you can extend them with your own methods
class MyAgent extends MagmaAgent {
// Want to give it some personality? Add system prompts:
getSystemPrompts() {
return [{
role: "system",
content: "You are a friendly assistant who loves dad jokes"
}];
}
}
// That's it! You've got a working agent
const myAgent = new MyAgent();
// Run it:
const reply = await myAgent.main();
console.log(reply.content);
```
- **Simple**: Build agents in minutes with minimal code
- **Flexible**: Use any AI provider (OpenAI, Anthropic, Groq)
- **Hosted**: Deploy your agents in seconds with the [MagmaDeploy platform](https://magmadeploy.com)
- **Powerful**: Add tools and middleware when you need them
- **Observable**: See exactly what your agent is doing
## 🛠 Examples
### Add Tools
Tools give your agent the ability to perform actions. Any method decorated with @tool and @toolparam will be available for the agent to use.
**Important Notes**:
- Every tool method must return a string
- Every tool has `call` as a required parameter, which is the `MagmaToolCall` object
- Tools are executed in sequence
```ts
import { MagmaAgent } from "@pompeii-labs/magma";
import { tool, toolparam } from "@pompeii-labs/magma/decorators";
/** Decorate any agent class method with @toolparam or @tool.
* @tool is used to define the tool itself
* @toolparam is used to define the parameters of the tool (key, type, description, required)
*/
class MyAgent extends MagmaAgent {
@tool({ name: "search_database", description: "Search the database for records" })
@toolparam({
key: "query",
type: "string",
description: "Search query",
required: true
})
@toolparam({
key: "filters",
type: "object",
properties: [
{ key: "date", type: "string" },
{ key: "category", type: "string", enum: ["A", "B", "C"] }
]
})
async searchDatabase(call: MagmaToolCall) {
const { query, filters } = call.fn_args;
const results = await this.searchDatabase(query, filters);
return "Here are the results of your search: " + JSON.stringify(results);
}
}
```
Middleware is a novel concept to Magma. It allows you to add custom logic to your agent before or after a tool is executed.
This is a great way to add custom logging, validation, data sanitization, etc.
**Types**:
- "preCompletion": Runs before the LLM call is made, takes in a MagmaUserMessage
- "onCompletion": Runs after the agent generates a text response, takes in a MagmaAssistantMessage
- "preToolExecution": Runs before a tool is executed, takes in a MagmaToolCall
- "onToolExecution": Runs after a tool is executed, takes in a MagmaToolResult
**Important Notes**:
- You can have unlimited middleware methods
- Middleware methods can manipulate the message they take in
- Middleware methods can throw errors to adjust the flow of the agent
**Error Handling**:
- If preCompletion middleware throws an error, the error message is supplied as if it were the assistant message. The user and assistant messages are also removed from the conversation history
- If onCompletion middleware throws an error, the error message is supplied to the LLM, and it tries to regenerate a response. The assistant message is not added to the conversation history
- If preToolExecution middleware throws an error, the error message is supplied as if it were the response from the tool
- If onToolExecution middleware throws an error, the error message is supplied as if it were the response from the tool
```ts
import { MagmaAgent } from "@pompeii-labs/magma";
import { middleware } from "@pompeii-labs/magma/decorators";
/**
* Decorate any agent class method with @middleware to add custom logging, validation, etc.
* Types: "preCompletion", "onCompletion", "preToolExecution", "onToolExecution"
*/
class MyAgent extends MagmaAgent {
@middleware("onCompletion")
async logBeforeCompletion(message) {
if (message.content.includes("bad word")) {
throw new Error("You just used a bad word, please try again.");
}
}
}
```
Jobs allow you to schedule functions within your agent. Jobs conform to the standard UNIX cron syntax (https://crontab.guru/).
**Important Notes**:
- Jobs should be static methods, so they can run without instantiating the agent.
- Jobs do not take in any parameters, and they do not return anything.
```ts
import { MagmaAgent } from "@pompeii-labs/magma";
import { job } from "@pompeii-labs/magma/decorators";
class MyAgent extends MagmaAgent {
// Run every day at midnight
@job("0 0 * * *")
static async dailyCleanup() {
await this.cleanDatabase();
}
// Run every hour with timezone
@job("0 * * * *", { timezone: "America/New_York" })
static async hourlySync() {
await this.syncData();
}
}
```
Hooks allow you to expose your agent as an API. Any method decorated with @hook will be exposed as an endpoint.
**Important Notes**:
- Hooks are static methods, so they can run without instantiating the agent.
- Hooks are exposed at `/hooks/{hook_name}` in the Magma API
- The only parameter to hook functions is the request object, which is an instance of `express.Request`
```ts
import { MagmaAgent } from "@pompeii-labs/magma";
import { hook } from "@pompeii-labs/magma/decorators";
import { Request } from "express";
class MyAgent extends MagmaAgent {
@hook('notification')
static async handleNotification(req: Request) {
await this.processNotification(req.body);
}
}
```
You can use any supported provider by setting the providerConfig.
**Important Notes**:
- You can set the providerConfig in the constructor, or by calling `setProviderConfig`
- You do not need to adjust any of your tools, middleware, jobs, or hooks to use a different provider. Magma will handle the rest.
```ts
class Agent extends MagmaAgent {
constructor() {
// Use OpenAI (default)
super({
providerConfig: {
provider: "openai",
model: "gpt-4o"
}
});
// Use Anthropic
this.setProviderConfig({
provider: "anthropic",
model: "claude-3.5-sonnet-20240620"
});
// Use Groq
this.setProviderConfig({
provider: "groq",
model: "llama-3.1-70b-versatile"
});
}
}
```
Every Tool, Middleware, Hook, and Job is passed the instance of the agent. This allows you to manipulate agent state and call agent functions in Utility classes
```ts
class MyAgent extends MagmaAgent {
// Using a field to store data
myQuery: string;
counter: number;
async setup() {
this.myQuery = "Hello, World!";
this.counter = 0;
}
@tool({ description: "Increment the counter" })
async increment() {
this.counter++;
return `Counter is now ${this.counter}`;
}
@tool({ name: "api_call" })
async apiCall() {
const response = await fetch("https://myapi.com/data", {
body: JSON.stringify({
query: this.myQuery
})
});
return JSON.stringify(response.json());
}
}
```
```ts
import { MagmaAgent } from "@pompeii-labs/magma";
class MyAgent extends MagmaAgent {
// Initialize your agent
async setup() {
// Load resources, connect to databases, etc.
await this.loadDatabase();
return "I'm ready to help!";
}
// Handle incoming messages
async receive(message: any) {
// Process user input before main() is called
if (message.type === 'image') {
await this.processImage(message.content);
}
}
// Clean up resources
async cleanup();
// Manually trigger a specific tool
async trigger({ name: "get_weather" });
// Stop the current execution
kill();
}
```
Event handlers are optional methods that allow you to tack on custom logic to various events in the agent lifecycle.
```ts
import { MagmaAgent } from "@pompeii-labs/magma";
class MyAgent extends MagmaAgent {
// Handle agent shutdown
async onCleanup() {
console.log("Agent shutting down...");
}
// Handle errors
async onError(error: Error) {
console.error("Something went wrong:", error);
await this.notifyAdmin(error);
}
// Track token usage
async onUsageUpdate(usage: MagmaUsage) {
await this.saveUsageMetrics(usage);
}
// Process streaming responses
async onStreamChunk(chunk: MagmaStreamChunk) {
console.log("Received chunk:", chunk.content);
}
}
```
- Join our [Slack Community](https://join.slack.com/t/magmacommunity/shared_invite/zt-2tghhq3av-Xn9k9ntwN5ZwqvxbWcfsTg)
- Star us on [GitHub](https://github.com/pompeii-labs/magma)
Magma is [Apache 2.0 licensed](LICENSE).