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
194 changes: 1 addition & 193 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,196 +9,4 @@
Active Agent provides that missing AI layer in the Rails framework, offering a structured approach to building AI-powered applications through Agent Oriented Programming. **Now Agents are Controllers!** Designing applications using agents allows developers to create modular, reusable components that can be easily integrated into existing systems. This approach promotes code reusability, maintainability, and scalability, making it easier to build complex AI-driven applications with the Object Oriented Ruby code you already use today.

## Documentation
[docs.activeagents.ai](https://docs.activeagents.ai) - The official documentation site for Active Agent.

## Install

### Add activeagent to your Gemfile
```bash
bundle add activeagent
bundle install
```

### Rails Generator
After installing the gem, run the Rails installation generator:

```bash
$ rails generate active_agent:install
```

This will create:
```
create config/active_agent.yml
create app/agents/application_agent.rb
create app/agents
```
```bash
bin/rails credentials:edit
```
Then add the following to `config/credentials.yml.enc`:
```yaml
openai:
api_key: sk-your_api_key_here
```
- A YAML configuration file for provider settings, such as OpenAI and might include environment-specific configurations:

```yaml
# config/active_agent.yml
development:
openai:
service: "OpenAI"
api_key: <%= Rails.application.credentials.dig(:openai, :api_key) %>
model: "gpt-3.5-turbo"
temperature: 0.7
ollama:
service: "Local Ollama"
model: "llama3.2"
temperature: 0.7

production:
openai:
service: "OpenAI"
api_key: <%= Rails.application.credentials.dig(:openai, :api_key) %>
model: "gpt-3.5-turbo"
temperature: 0.7

```
- A base application agent class
```ruby
# app/agents/application_agent.rb
class ApplicationAgent < ActiveAgent::Base
layout 'agent'
generate_with :openai,
instructions: "You are a helpful assistant.",
model: "gpt-4o-mini",
temperature: 0.7
end
```
- The agents directory structure

## Agent
Create agents that take instructions, prompts, and perform actions

### Rails Generator
To use the Rails Active Agent generator to create a new agent and the associated views for the requested action prompts:

```bash
$ rails generate active_agent:agent travel search book plans
```
This will create:
```
create app/agents/travel_agent.rb
create app/views/agents/travel/search.text.erb
create app/views/agents/travel/book.text.erb
create app/views/agents/travel/plans.text.erb
```

The generator creates:
- An agent class inheriting from ApplicationAgent
- Text template views for each action
- Action methods in the agent class for processing prompts

### Agent Actions
```ruby
class TravelAgent < ApplicationAgent
def search

prompt { |format| format.text { render plain: "Searching for travel options" } }
end

def book
prompt { |format| format.text { render plain: "Booking travel plans" } }
end

def plans
prompt { |format| format.text { render plain: "Making travel plans" } }
end
end
```

## Action Prompt

Action Prompt provides the structured interface for composing AI interactions through messages, actions/tools, and conversation context. [Read more about Action Prompt](lib/active_agent/action_prompt/README.md)

```ruby
agent.prompt(message: "Find hotels in Paris",
actions: [{name: "search", params: {query: "hotels paris"}}])
```

The prompt interface manages:
- Message content and roles (system/user/assistant)
- Action/tool definitions and requests
- Headers and context tracking
- Content types and multipart handling

### Generation Provider

Generation Provider defines how prompts are sent to AI services for completion and embedding generation. [Read more about Generation Providers](lib/active_agent/generation_provider/README.md)

```ruby
class VacationAgent < ActiveAgent::Base
generate_with :openai,
model: "gpt-4",
temperature: 0.7

embed_with :openai,
model: "text-embedding-ada-002"
end
```

Providers handle:
- API client configuration
- Prompt/completion generation
- Stream processing
- Embedding generation
- Context management
- Error handling

### Queue Generation

Active Agent also supports queued generation with Active Job using a common Generation Job interface.

### Perform actions

Active Agents can define methods that are autoloaded as callable tools. These actions’ default schema will be provided to the agent’s context as part of the prompt request to the Generation Provider.

## Actions

```ruby
def get_cat_image_base64
uri = URI("https://cataas.com/cat")
response = Net::HTTP.get_response(uri)

if response.is_a?(Net::HTTPSuccess)
image_data = response.body
Base64.strict_encode64(image_data)
else
raise "Failed to fetch cat image. Status code: #{response.code}"
end
end

class SupportAgent < ActiveAgent
generate_with :openai,
model: "gpt-4o",
instructions: "Help people with their problems",
temperature: 0.7

def get_cat_image
prompt { |format| format.text { render plain: get_cat_image_base64 } }
end
end
```

## Prompts

### Basic

#### Plain text prompt and response templates

### HTML

### Action Schema JSON

response = SupportAgent.prompt(‘show me a picture of a cat’).generate_now

response.message
[docs.activeagents.ai](https://docs.activeagents.ai) - The official documentation site for Active Agent.
2 changes: 2 additions & 0 deletions bin/docs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/bin/bash
npm run docs:dev
13 changes: 8 additions & 5 deletions docs/docs/action-prompt/actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Active Agent Actions render views, just like Action Controller or Action Mailer. Similar to Action Mailer that renders Mail objects with Messages, Active Agent renders Prompt objects with Messages.

## Prompt
The `prompt` method is used to render the action's content in the prompt. The `prompt()` method is similar to `mail()` in Action Mailer or `render()` in Action Controller, it allows you to specify the content type and view template for the action's response.
The `prompt` method is used to render the action's content as a message in a prompt. The `prompt` method is similar to `mail` in Action Mailer or `render` in Action Controller, it allows you to specify the content type and view template for the action's response.

These Prompt objects contain the context Messages and available Actions. These actions are the interface that agents can use to interact with tools through text and JSON views or interact with users through text and HTML views.

Expand All @@ -21,16 +21,19 @@ You can define actions in your agent class that can be used to interact with the
## Call to Actions
These actions can be invoked by the agent to perform specific tasks and receive the results or in your Rails app's controllers, models, or jobs to prompt the agent for generation with a templated prompt message. By default, public instance methods defined in the agent class are included in the context as available actions. You can also define actions in a separate concern module and include them in your agent class.

::: code-group
<<< @/../test/agents/translation_agent_test.rb#translation_agent_render_translate_prompt{ruby} [test/agents/translation_agent_test.rb:6..8]
:::

## Action params
Agent Actions can accept parameters, which are passed as a hash to the action method. You pass arguments to agent's using the `with` method and access parameters using the `params` method, just like Mailer Actions.

## Using Actions to prompt the Agent with a templated message
You can call these actions directly to render a prompt to the agent directly to generate the requested object.

```ruby
translate_prompt_context = TranslationAgent.with(message: "Hi, I'm Justin", locale: 'japanese').translate
response = translate_prompt_context.generate_now
```
::: code-group
<<< @/../test/agents/translation_agent_test.rb#translation_agent_translate_prompt_generation{ruby} [test/agents/translation_agent_test.rb:15..16]
:::

## Using Agents to call Actions
You can also provide an Agent with a prompt context that includes actions and messages. The agent can then use these actions to perform tasks and generate responses based on the provided context.
Expand Down
31 changes: 15 additions & 16 deletions docs/docs/action-prompt/prompts.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,21 @@ The Prompt is structured to include the following components:
- **Actions**: An array of actions that the agent can perform in response to user input. By default, the prompt will use the agent's action methods, but you can also specify custom actions.

## Example Prompt
Prompts are built and rendered in the agent's action methods, typically using the `prompt` method. This is an example of creating a prompt by manually building the context; assigning `actions`, `message` and `messages`.
```ruby
prompt = ActiveAgent::ActionPrompt::Prompt.new(
actions: SupportAgent.new.action_schemas,
message: "I need help with my account.",
messages: [
{ content: "Hello, how can I assist you today?", role: "assistant" },
]
)
```
Prompts are built and rendered in the agent's action methods, typically using the `prompt` method. This is an example of creating a prompt by manually building the context; assigning `actions`, the prompt `message` and context `messages`.

<<< @/../test/action_prompt/prompt_test.rb#support_agent_prompt_initialization{ruby:line-numbers} [support_agent.rb]


## Rendering Prompts
Prompts can be rendered using the `prompt` method, which generates the structured prompt object with the provided context.
Prompts can be rendered using the `prompt` method inside an Agent's action method, which generates the structured prompt object with the provided context. In this example the `translate` action renders the translate.text.erb template with the provided message and locale parameters, and returns a prompt context that can be used to generate a response.


::: code-group
<<< @/../test/dummy/app/agents/translation_agent.rb{5 ruby:line-numbers} [app/agents/translation_agent.rb]

<<< @/../test/dummy/app/views/translation_agent/translate.text.erb{5 erb:line-numbers} [translate.text.erb]
:::

```ruby
agent_prompt_context = SupportAgent.with(message: "I need help with my account.", messages: [
{ content: "Hello, how can I assist you today?", role: :assistant },
]).prompt_context
```
::: code-group
<<< @/../test/agents/translation_agent_test.rb#translation_agent_render_translate_prompt{ruby} [test/agents/translation_agent_test.rb:6..8]
:::
33 changes: 1 addition & 32 deletions docs/docs/active-agent/callbacks.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,35 +41,4 @@ On stream callbacks are triggered during the streaming of responses from an agen

Below is a verbose example to demonstrate how to handle streaming responses and broadcast updates to a chat interface. The shows the runtime instance of the Agent's [`generation_provider`](/docs/framework/generation-provider) and its [`response`](/docs/framework/generation-provider#response) object. This example assumes you have a `Chat` model with associated messages, and you want to update the chat in real-time as the agent generates a response.

```ruby
class ApplicationAgent < ActiveAgent::Base
generate_with :openai

on_stream :broadcast_message

private
def broadcast_message
@chat ||= Chat.find(generation_provider.prompt.context_id)

# Create or find the assistant message
@message ||= @chat.messages.create(content: "", role: 'assistant')

# Update the message content with the streaming content
@message.update(content: generation_provider.response.message.content)

puts "Broadcasting message... #{generation_provider.response.message.content}"

# Handle broadcasting directly here instead of relying on model callbacks
if @message.persisted?
if @message.content.present?
# Broadcast the updated message during streaming
@message.broadcast_append_to(
"#{ActionView::RecordIdentifier.dom_id(@chat)}_messages",
partial: "messages/message",
locals: { message: @message, scroll_to: true },
target: "#{ActionView::RecordIdentifier.dom_id(@chat)}_messages"
)
end
end
end
```
<<< @/../test/dummy/app/agents/streaming_agent.rb{ruby:line-numbers} [streaming_agent.rb]
12 changes: 3 additions & 9 deletions docs/docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,9 @@ end
This code snippet sets up the `ApplicationAgent` to use OpenAI as the generation provider. You can replace `:openai` with any other supported provider, such as `:anthropic`, `:google`, or `:ollama`.

Now, you can interact with your application agent:
```ruby
ApplicationAgent.with(
instructions: "Help users with their queries.",
actions: [:weather],
messages: [
{ role: 'user', content: 'What is the weather like today?' }
]
).text_prompt.generate_now
```

<<< @/../test/agents/application_agent_test.rb#application_agent_prompt_context_message_generation{ruby}

This code parameterizes the `ApplicationAgent` `with` a set of `params`.

## Configuration
Expand Down
15 changes: 8 additions & 7 deletions lib/active_agent/action_prompt/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -204,11 +204,6 @@ def embed
# attr_accessor :context

def perform_generation
context.options.merge(options)
if (action_methods - ActiveAgent::Base.descendants.first.action_methods).include? action_name
context.message = context.messages.last
context.actions = []
end
generation_provider.generate(context) if context && generation_provider
handle_response(generation_provider.response)
end
Expand Down Expand Up @@ -298,13 +293,16 @@ def prompt_with(*)

def prompt(headers = {}, &block)
return context if @_prompt_was_called && headers.blank? && !block

context.options.merge!(options)
content_type = headers[:content_type]
headers = apply_defaults(headers)
context.messages = headers[:messages] || []
context.context_id = headers[:context_id]
context.params = params

context.charset = charset = headers[:charset]
headers[:message] ||= context.messages.last

if headers[:message].present? && headers[:message].is_a?(ActiveAgent::ActionPrompt::Message)
headers[:body] = headers[:message].content
headers[:role] = headers[:message].role
Expand All @@ -317,7 +315,6 @@ def prompt(headers = {}, &block)
# assign_headers_to_context(context, headers)
responses = collect_responses(headers, &block)

context.params = params
@_prompt_was_called = true

create_parts_from_responses(context, responses)
Expand All @@ -326,6 +323,10 @@ def prompt(headers = {}, &block)
context.charset = charset
context.actions = headers[:actions] || action_schemas

if (action_methods - ActiveAgent::Base.descendants.first.action_methods).include? action_name
context.actions = (action_methods - [ action_name ])
end

context
end

Expand Down
8 changes: 5 additions & 3 deletions lib/active_agent/action_prompt/prompt.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,12 @@ def to_s
end

def add_part(message)
@message = message
set_message if @content_type == message.content_type && @message.content.present?
if @content_type == message.content_type && message.content.present?
@message = message
set_message
end

@parts << context
@parts << message
end

def multipart?
Expand Down
Loading