omni_agent 0.1.11 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +43 -0
- data/README.md +96 -2
- data/Rakefile +4 -0
- data/app/jobs/omni_agent/embed_job.rb +10 -0
- data/app/jobs/omni_agent/ingest_job.rb +12 -0
- data/docs/agent/callbacks.md +60 -0
- data/docs/agent/delegation.md +56 -0
- data/docs/agent/provider.md +93 -0
- data/docs/agent/run-entrypoints.md +81 -0
- data/docs/agent/streaming.md +62 -0
- data/docs/agent/structured-output.md +80 -0
- data/docs/configuration.md +68 -0
- data/docs/evals/caching.md +21 -0
- data/docs/evals/generating-and-running.md +87 -0
- data/docs/evals/golden-sets.md +32 -0
- data/docs/evals/judge-provider.md +20 -0
- data/docs/generators.md +143 -0
- data/docs/index.md +98 -0
- data/docs/intro.md +93 -0
- data/docs/mcp/authentication.md +226 -0
- data/docs/mcp/defining-servers.md +196 -0
- data/docs/mcp/transports.md +166 -0
- data/docs/rag.md +372 -0
- data/docs/stock-agents/ocr.md +52 -0
- data/docs/stock-agents/overview.md +91 -0
- data/docs/supported-providers.md +47 -0
- data/docs/tools.md +171 -0
- data/exe/omni_agent +48 -10
- data/lib/generators/omni_agent/chunks/chunks_generator.rb +130 -0
- data/lib/generators/omni_agent/embeddings/embeddings_generator.rb +72 -0
- data/lib/generators/omni_agent/install/install_generator.rb +78 -1
- data/lib/generators/omni_agent/stock_agent/stock_agent_generator.rb +73 -0
- data/lib/generators/omni_agent/vector_store.rb +113 -0
- data/lib/omni_agent/agent.rb +168 -39
- data/lib/omni_agent/chunkable.rb +181 -0
- data/lib/omni_agent/chunkers/hierarchical.rb +20 -0
- data/lib/omni_agent/chunkers/markdown.rb +41 -0
- data/lib/omni_agent/chunkers/recursive.rb +53 -0
- data/lib/omni_agent/chunkers.rb +31 -0
- data/lib/omni_agent/configuration.rb +14 -1
- data/lib/omni_agent/docs.rb +32 -0
- data/lib/omni_agent/embeddable.rb +131 -0
- data/lib/omni_agent/engine.rb +11 -0
- data/lib/omni_agent/errors.rb +15 -0
- data/lib/omni_agent/loaders/html.rb +21 -0
- data/lib/omni_agent/loaders/markdown.rb +18 -0
- data/lib/omni_agent/loaders/pdf.rb +25 -0
- data/lib/omni_agent/loaders/text.rb +15 -0
- data/lib/omni_agent/loaders.rb +68 -0
- data/lib/omni_agent/providers/base.rb +14 -0
- data/lib/omni_agent/providers/mock.rb +89 -4
- data/lib/omni_agent/providers/ollama.rb +4 -0
- data/lib/omni_agent/providers/openai.rb +33 -2
- data/lib/omni_agent/providers/response.rb +6 -1
- data/lib/omni_agent/providers.rb +7 -0
- data/lib/omni_agent/schema.rb +200 -0
- data/lib/omni_agent/semantic_search.rb +45 -0
- data/lib/omni_agent/stock_agent.rb +59 -0
- data/lib/omni_agent/stock_agents/ocr/prompt.md.erb +6 -0
- data/lib/omni_agent/stock_agents/ocr.rb +70 -0
- data/lib/omni_agent/stock_agents.rb +26 -0
- data/lib/omni_agent/tool.rb +7 -107
- data/lib/omni_agent/version.rb +1 -1
- data/lib/omni_agent.rb +12 -1
- data/lib/tasks/omni_agent_chunks_tasks.rake +16 -0
- data/lib/tasks/omni_agent_embeddings_tasks.rake +15 -0
- metadata +49 -8
- data/app/assets/stylesheets/omni_agent/application.css +0 -15
- data/app/controllers/omni_agent/application_controller.rb +0 -4
- data/app/helpers/omni_agent/application_helper.rb +0 -4
- data/app/jobs/omni_agent/application_job.rb +0 -4
- data/app/mailers/omni_agent/application_mailer.rb +0 -6
- data/app/models/omni_agent/application_record.rb +0 -5
- data/app/views/layouts/omni_agent/application.html.erb +0 -17
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 619f71e19ebcd85894c197fa69aa6603f9c21eededca2c249e0a6ac3ceaa38a9
|
|
4
|
+
data.tar.gz: dcb21d896ea82f2127fe793d58668ea2e95162a3b77f12bee110ff9091d1aa96
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c92ffa2cc815d03ba69a6033cac9f96bfadc95725f926de704df33a31d7651f4afd586342dff02987ba86736ad52a08314308f4875ff68a18f98a4ae474c92a3
|
|
7
|
+
data.tar.gz: 00a41896240d9ef66e1cf739ae0bc693a424138fe47073ab0937a853067b274292d41ff21a193f2e5a78258d5c21977faa50fd9125a93136d6c00984e3d1ab8a
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,49 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.2.0](https://github.com/ACR1209/omni_agent/compare/omni_agent/v0.1.11...omni_agent/v0.2.0) (2026-10-29)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### ⚠ BREAKING CHANGES
|
|
9
|
+
|
|
10
|
+
* **agent:** agent subclasses now inherit their parent's class-level settings: `provider`/model, `options`, tags, `before_generation`/ `after_generation` callbacks, tools, delegated tools and `output`. Apps with agent hierarchies (e.g. an `ApplicationAgent` base class) will pick up the parent's settings in every subclass.
|
|
11
|
+
|
|
12
|
+
### Features
|
|
13
|
+
|
|
14
|
+
* add OmniAgent.embed with configurable embedding provider and model ([19214df](https://github.com/ACR1209/omni_agent/commit/19214df320c33b60559ce4eca9e65d77aedbafb8))
|
|
15
|
+
* **agent:** add searches macro for semantic search tools ([764a756](https://github.com/ACR1209/omni_agent/commit/764a7568cc23c47bf42cea8719757d01fc8a3dbd))
|
|
16
|
+
* **agent:** add structured output and stock agents scaffold ([c23fb4a](https://github.com/ACR1209/omni_agent/commit/c23fb4a89f30dbb55916ce886deafafd65d7dba8))
|
|
17
|
+
* **agent:** address review feedback on structured output and inheritance ([143ce4b](https://github.com/ACR1209/omni_agent/commit/143ce4b5668edab2c06ed65071fd208846fd7457))
|
|
18
|
+
* **agent:** search has_chunks models with searches ([b794bb2](https://github.com/ACR1209/omni_agent/commit/b794bb2d1ef0cb94f45dfec674cf3670828987b7))
|
|
19
|
+
* **chunkable:** add has_chunks to chunk, embed and store documents ([414504b](https://github.com/ACR1209/omni_agent/commit/414504b3e5a26b7868a2fa1da938d48dd635518c))
|
|
20
|
+
* **chunkers:** add recursive, markdown and hierarchical chunkers ([2f8a97d](https://github.com/ACR1209/omni_agent/commit/2f8a97d8b9cc7016092a0233882960fff9f9270c))
|
|
21
|
+
* **docs:** bundle plain-Markdown docs in the gem for coding agents ([3cf1270](https://github.com/ACR1209/omni_agent/commit/3cf12705bef39064680463dc088322969de50518))
|
|
22
|
+
* **embeddable:** add has_embedding macro for ActiveRecord models ([32d716d](https://github.com/ACR1209/omni_agent/commit/32d716dded4e9820f841a6c89fa59b9a04281bc4))
|
|
23
|
+
* **embeddable:** configure embed job options globally and per model ([f4dcb39](https://github.com/ACR1209/omni_agent/commit/f4dcb39850fe53e5b80a408f548ae4b8cdea3bb4))
|
|
24
|
+
* **generators:** add omni_agent:chunks to set up a chunk model ([ad60360](https://github.com/ACR1209/omni_agent/commit/ad60360f630c33eae6d32f9d2acdd3b246caad45))
|
|
25
|
+
* **generators:** add omni_agent:embeddings generator ([5af765b](https://github.com/ACR1209/omni_agent/commit/5af765b454975b23f85903558d1832ba34ec32aa))
|
|
26
|
+
* **generators:** write a commented initializer in omni_agent:install ([c0f22aa](https://github.com/ACR1209/omni_agent/commit/c0f22aabe3affc4e17e60548288fa855cb968764))
|
|
27
|
+
* **loaders:** read PDF, HTML, Markdown and text documents with auto-detection ([c294323](https://github.com/ACR1209/omni_agent/commit/c2943237772e44983fa3bc62bb669d256ba2b4a3))
|
|
28
|
+
* **providers:** add embed for text embeddings ([519f169](https://github.com/ACR1209/omni_agent/commit/519f16968896c8f0580591e2e7af6b93e26df3ae))
|
|
29
|
+
* **stock_agents:** add OCR stock agent with image and PDF input ([b2683f3](https://github.com/ACR1209/omni_agent/commit/b2683f3532772e3d00a037e22d425164f8f785dd))
|
|
30
|
+
* **tasks:** add omni_agent:embeddings:backfill rake task ([2aa06f7](https://github.com/ACR1209/omni_agent/commit/2aa06f7c24895caa2eb1b7ce8a1493f783e0ffe9))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
### Bug Fixes
|
|
34
|
+
|
|
35
|
+
* **agent:** separate search results with a rule ([bd018cc](https://github.com/ACR1209/omni_agent/commit/bd018cc09626dcfcde524fccdbcbc2fc8222052e))
|
|
36
|
+
* **chunkable:** lock the document while replacing its chunks ([5e695e6](https://github.com/ACR1209/omni_agent/commit/5e695e63384e57afbbd96571bbf59bd633a0bda7))
|
|
37
|
+
* **chunkable:** skip unreadable documents in ingest_all ([9b6b68d](https://github.com/ACR1209/omni_agent/commit/9b6b68dab3cb8686d3da7c46c0c4032015b2d4d7))
|
|
38
|
+
* **chunkers:** skip Markdown headings with nothing under them ([e228627](https://github.com/ACR1209/omni_agent/commit/e228627ac450902f33064f4dc1bd25c589b1f5bb))
|
|
39
|
+
* **embeddable:** discard embed jobs for deleted records ([1b69efc](https://github.com/ACR1209/omni_agent/commit/1b69efcba25e26419ffadaa9721f0c888b9a856a))
|
|
40
|
+
* **embeddable:** explain how to enable ActiveJob for later: true ([7f46446](https://github.com/ACR1209/omni_agent/commit/7f46446d993f036b4fa27fbb21035b65ae7bbae3))
|
|
41
|
+
* **engine:** load ActiveJob with the engine ([1392629](https://github.com/ACR1209/omni_agent/commit/1392629e4892a5f6ed1c38b5a2eb34cdddda7e00))
|
|
42
|
+
* **engine:** remove unused plugin scaffolding ([fa97033](https://github.com/ACR1209/omni_agent/commit/fa97033a96a7bff2ce4d4355008766d7fed7df89))
|
|
43
|
+
* **generators:** explain connection errors when detecting MariaDB ([a13a770](https://github.com/ACR1209/omni_agent/commit/a13a770ba607905fad8ee4d21fb168ed91b75429))
|
|
44
|
+
* **generators:** number migrations after the newest one ([01683a9](https://github.com/ACR1209/omni_agent/commit/01683a9e4efa7d05ea05e590615392ae78545180))
|
|
45
|
+
* **loaders:** drop scripts and styles anywhere in HTML ([972fa94](https://github.com/ACR1209/omni_agent/commit/972fa944fd224ace1c909ccbd1c4a57d254a81f7))
|
|
46
|
+
* **providers:** pass content-part arrays through to OpenAI ([22a5354](https://github.com/ACR1209/omni_agent/commit/22a535464b8053365a9fe4b8f1f850768e5a6472))
|
|
47
|
+
|
|
5
48
|
## [0.1.11](https://github.com/ACR1209/omni_agent/compare/omni_agent/v0.1.10...omni_agent/v0.1.11) (2026-10-15)
|
|
6
49
|
|
|
7
50
|
|
data/README.md
CHANGED
|
@@ -8,10 +8,13 @@ tool schemas, and generation lifecycle callbacks.
|
|
|
8
8
|
|
|
9
9
|
- `OmniAgent::Agent` runtime with provider abstraction and tool-calling loop
|
|
10
10
|
- `OmniAgent::Tool` DSL with JSON-schema-style input definitions
|
|
11
|
+
- Structured, validated agent output declared with the same DSL as tool input
|
|
12
|
+
- Stock agents: ready-made agents shipped with the gem that projects subclass and customize
|
|
11
13
|
- Prompt composition from ERB files in `app/agents/<agent_name>/`
|
|
12
14
|
- Agent callbacks (`before_generation`, `after_generation`)
|
|
13
15
|
- Agent and tool tags to support filtering strategies
|
|
14
16
|
- OpenAI provider integration out of the box, plus an Ollama provider for local models
|
|
17
|
+
- RAG: `has_embedding` models, `has_chunks` documents (PDF, Markdown, HTML) and `searches` tools over pgvector, sqlite-vec, MariaDB or Meilisearch
|
|
15
18
|
- MCP server support: expose tools and whole agents to Claude Code, Claude Desktop, Cursor and other MCP clients
|
|
16
19
|
- Rake tasks and Rails generators for scaffolding
|
|
17
20
|
|
|
@@ -38,12 +41,14 @@ bundle install
|
|
|
38
41
|
|
|
39
42
|
## Quick Start
|
|
40
43
|
|
|
41
|
-
1. Install base directories:
|
|
44
|
+
1. Install base directories and the initializer:
|
|
42
45
|
|
|
43
46
|
```bash
|
|
44
47
|
bundle exec rails generate omni_agent:install
|
|
45
48
|
```
|
|
46
49
|
|
|
50
|
+
This also adds an OmniAgent section to `AGENTS.md` (and `CLAUDE.md`, if present) that points coding agents at the bundled docs. Pass `--skip-agents-md` to leave those files alone.
|
|
51
|
+
|
|
47
52
|
2. Generate an agent scaffold:
|
|
48
53
|
|
|
49
54
|
```bash
|
|
@@ -108,6 +113,48 @@ module ResearchAgent::Tools
|
|
|
108
113
|
end
|
|
109
114
|
```
|
|
110
115
|
|
|
116
|
+
## Structured Output
|
|
117
|
+
|
|
118
|
+
Declare an `output` block, using the same DSL as tool `input`, to get a validated object back instead of free text:
|
|
119
|
+
|
|
120
|
+
```ruby
|
|
121
|
+
class InvoiceAgent < OmniAgent::Agent
|
|
122
|
+
output do
|
|
123
|
+
string :invoice_number
|
|
124
|
+
number :total, min: 0
|
|
125
|
+
enum :currency, values: %w[USD EUR]
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
InvoiceAgent.new.run(invoice_text).structured_output
|
|
130
|
+
# => { invoice_number: "A-1042", total: 120.5, currency: "USD" }
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
The schema is sent to the provider (as a `json_schema` `response_format` for OpenAI and Ollama), and the final answer is parsed and validated with the same rules as tool input. Invalid JSON or a schema mismatch raises `OmniAgent::StructuredOutputError`.
|
|
134
|
+
|
|
135
|
+
## Stock Agents
|
|
136
|
+
|
|
137
|
+
Stock agents are ready-made agents shipped with the gem under `OmniAgent::StockAgents`. Subclass one in your app and declare the output you need; the subclass keeps the stock prompt, model and tools:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
rails generate omni_agent:stock_agent InvoiceOcr --from=ocr --fields=invoice_number:string total:number
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
```ruby
|
|
144
|
+
class InvoiceOcr < OmniAgent::StockAgents::OCR
|
|
145
|
+
output do
|
|
146
|
+
string :invoice_number
|
|
147
|
+
number :total
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
InvoiceOcr.new.run([ Pathname("page1.jpg"), Pathname("page2.jpg") ]).structured_output
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
The OCR stock agent reads images and PDFs from paths, uploaded files, ActiveStorage attachments or URLs, one at a time or several pages together.
|
|
155
|
+
|
|
156
|
+
To add a stock agent to the gem itself, run `bin/rake "stock_agent:new[name]"` from the gem repository.
|
|
157
|
+
|
|
111
158
|
## Multi-Agent Delegation
|
|
112
159
|
|
|
113
160
|
`delegate_to` turns an agent into a supervisor: it wraps another agent class as a tool, so the supervisor's LLM can decide when to hand off to it. The delegated agent is a normal, independently defined agent (e.g. `app/agents/research_agent.rb`) — no manual tool file needed.
|
|
@@ -157,6 +204,41 @@ end
|
|
|
157
204
|
|
|
158
205
|
`.stream` must come before the call (`.stream.run(...)`, not `.run(...).stream`) — without it, or without a block, behavior is unchanged and the same `Response` is returned either way. Only the `openai`, `ollama`, and `mock` providers stream today. See [Streaming Responses](omniagent-docs/docs/agent/streaming.mdx) for the full event reference.
|
|
159
206
|
|
|
207
|
+
## RAG
|
|
208
|
+
|
|
209
|
+
Make a model searchable and give an agent a search tool for it:
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
rails generate omni_agent:embeddings Document title content # migration + has_embedding
|
|
213
|
+
bin/rails db:migrate
|
|
214
|
+
bin/rails "omni_agent:embeddings:backfill[Document]"
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
```ruby
|
|
218
|
+
class Document < ApplicationRecord
|
|
219
|
+
has_embedding :title, :content # embedded on save via OmniAgent.embed
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
class SupportAgent < OmniAgent::Agent
|
|
223
|
+
searches Document, scope: ->(context) { where(account_id: context[:account_id]) }
|
|
224
|
+
end
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Vectors are stored with [neighbor](https://github.com/ankane/neighbor) on Postgres (pgvector), SQLite (sqlite-vec) or MariaDB. Models using meilisearch-rails work with `searches` as they are, and so does any object with a `semantic_search(query, limit:)` method. For files and long text, `has_chunks` splits them into searchable chunks:
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
rails generate omni_agent:chunks Document file # DocumentChunk model + has_chunks
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
```ruby
|
|
234
|
+
class Document < ApplicationRecord
|
|
235
|
+
has_one_attached :file
|
|
236
|
+
has_chunks :file # PDF, Markdown, HTML or text, detected per file
|
|
237
|
+
end
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
See [RAG](omniagent-docs/docs/rag.mdx) for the options, loaders, chunkers, Meilisearch setup and embedding configuration.
|
|
241
|
+
|
|
160
242
|
## Evals
|
|
161
243
|
|
|
162
244
|
`OmniAgent::Eval` lets you test agent quality: deterministic assertions (tool calls, output matching) and pluggable LLM-as-judge scoring.
|
|
@@ -292,7 +374,7 @@ Tools can declare MCP-only hints with `title "Get weather"` and `annotations rea
|
|
|
292
374
|
|
|
293
375
|
## Configuration
|
|
294
376
|
|
|
295
|
-
Global defaults
|
|
377
|
+
`rails generate omni_agent:install` writes `config/initializers/omni_agent.rb` with every setting commented out at its default. Global defaults are set through `OmniAgent.configure`:
|
|
296
378
|
|
|
297
379
|
```ruby
|
|
298
380
|
OmniAgent.configure do |config|
|
|
@@ -301,6 +383,18 @@ OmniAgent.configure do |config|
|
|
|
301
383
|
end
|
|
302
384
|
```
|
|
303
385
|
|
|
386
|
+
## Documentation for Coding Agents
|
|
387
|
+
|
|
388
|
+
The full documentation from https://omniagent.activetaskers.com/docs/intro ships inside the gem as plain Markdown in `docs/`, so coding agents (Claude Code, Cursor, Codex...) working in your app can read it offline:
|
|
389
|
+
|
|
390
|
+
```bash
|
|
391
|
+
bundle exec omni_agent docs # index: conventions, typical flows and every page
|
|
392
|
+
bundle exec omni_agent docs mcp/authentication # one page
|
|
393
|
+
bundle exec omni_agent docs --path # directory holding the Markdown files
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
`docs/` is generated from `omniagent-docs/docs` by `bundle exec rake docs:sync`, which `rake build` runs first. It isn't committed, so a gem installed from git (`gem "omni_agent", github: ...`) doesn't include it; use the docs site instead.
|
|
397
|
+
|
|
304
398
|
## Running Tests
|
|
305
399
|
|
|
306
400
|
```bash
|
data/Rakefile
CHANGED
|
@@ -13,6 +13,10 @@ require "bundler/gem_tasks"
|
|
|
13
13
|
Rake::Task["release"].clear
|
|
14
14
|
task release: [ "build", "release:rubygem_push" ]
|
|
15
15
|
|
|
16
|
+
# docs/ is generated from omniagent-docs/ (see rakelib/docs.rake) and not
|
|
17
|
+
# committed, but the gemspec packages it, so generate it before every build.
|
|
18
|
+
task build: "docs:sync"
|
|
19
|
+
|
|
16
20
|
RSpec::Core::RakeTask.new(:spec)
|
|
17
21
|
|
|
18
22
|
task default: :spec
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module OmniAgent
|
|
2
|
+
class IngestJob < ActiveJob::Base
|
|
3
|
+
# The record was deleted before or while the job ran; there's nothing to ingest.
|
|
4
|
+
discard_on ActiveJob::DeserializationError, ActiveRecord::RecordNotFound
|
|
5
|
+
# No loader can read the file, so retrying won't help.
|
|
6
|
+
discard_on OmniAgent::UnsupportedDocumentError
|
|
7
|
+
|
|
8
|
+
def perform(record)
|
|
9
|
+
record.ingest!
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<!-- Generated from omniagent-docs/docs/agent/callbacks.mdx by `bundle exec rake docs:sync`. Do not edit by hand. -->
|
|
2
|
+
<!-- Online version: https://omniagent.activetaskers.com/docs/agent/callbacks -->
|
|
3
|
+
|
|
4
|
+
# Generation Callbacks
|
|
5
|
+
|
|
6
|
+
Agents support `before_generation` and `after_generation` callbacks, letting you hook into the lifecycle of a single `run` without overriding it yourself.
|
|
7
|
+
|
|
8
|
+
## Before Generation
|
|
9
|
+
|
|
10
|
+
Runs before any provider call is made — useful for setting up instance variables that your prompt template needs.
|
|
11
|
+
|
|
12
|
+
```ruby
|
|
13
|
+
class ResearchAgent < OmniAgent::Agent
|
|
14
|
+
use_model "gpt-4o-mini"
|
|
15
|
+
|
|
16
|
+
before_generation :set_current_user
|
|
17
|
+
|
|
18
|
+
def set_current_user
|
|
19
|
+
@user = "Test User"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Any instance variable you set here is automatically available inside `prompt.md.erb` via ERB interpolation.
|
|
25
|
+
|
|
26
|
+
## After Generation
|
|
27
|
+
|
|
28
|
+
Runs once the agent has a final response (no more tool calls to make, or a tool explicitly stopped generation).
|
|
29
|
+
|
|
30
|
+
```ruby
|
|
31
|
+
class ResearchAgent < OmniAgent::Agent
|
|
32
|
+
after_generation :log_response
|
|
33
|
+
|
|
34
|
+
def log_response(payload)
|
|
35
|
+
Rails.logger.info("Response: #{payload[:response].content}")
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Callback methods can take zero arguments or one. If they accept an argument, they receive a payload hash:
|
|
41
|
+
|
|
42
|
+
| Key | Before generation | After generation |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| `input` | ✓ | ✓ |
|
|
45
|
+
| `context` | ✓ | ✓ |
|
|
46
|
+
| `messages` | ✓ | ✓ |
|
|
47
|
+
| `generated_messages` | | ✓ |
|
|
48
|
+
| `response` | | ✓ |
|
|
49
|
+
|
|
50
|
+
## Multiple Callbacks
|
|
51
|
+
|
|
52
|
+
Both directives accept multiple method names and are additive across calls — calling `before_generation` again appends rather than replacing:
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
before_generation :set_current_user, :load_preferences
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Context Binding
|
|
59
|
+
|
|
60
|
+
Instance variables you set inside a callback (or anywhere in the agent) are synced back into the run's `context` hash after each callback finishes, so later callbacks and the prompt template see the latest values.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<!-- Generated from omniagent-docs/docs/agent/delegation.mdx by `bundle exec rake docs:sync`. Do not edit by hand. -->
|
|
2
|
+
<!-- Online version: https://omniagent.activetaskers.com/docs/agent/delegation -->
|
|
3
|
+
|
|
4
|
+
# Multi-Agent Delegation
|
|
5
|
+
|
|
6
|
+
`delegate_to` wraps another agent as a tool your agent's LLM can call, turning your agent into a **supervisor** that routes work to sub-agents. The delegated agent is defined and used like any other agent — a normal class under `app/agents/`, not a manual tool file.
|
|
7
|
+
|
|
8
|
+
```ruby
|
|
9
|
+
class SupervisorAgent < OmniAgent::Agent
|
|
10
|
+
use_model "gpt-4o"
|
|
11
|
+
|
|
12
|
+
delegate_to ResearchAgent, as: :research, description: "Look up factual info"
|
|
13
|
+
delegate_to MathAgent, as: :calculate, description: "Do arithmetic"
|
|
14
|
+
end
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The model decides when (and whether) to call `research` or `calculate` based on the conversation, exactly like any other tool call. Each delegated agent runs its own `run` loop — including its own tool calls, if it has any — and returns its final answer as the tool result.
|
|
18
|
+
|
|
19
|
+
* **`agent_class`**: Any `OmniAgent::Agent` subclass.
|
|
20
|
+
* **`as:`**: Name exposed to the model as the tool/function name (e.g. `:research` → `Research`).
|
|
21
|
+
* **`description:`**: Sent to the provider so the model knows when to delegate. Defaults to `"Delegate to <AgentClass>."`.
|
|
22
|
+
|
|
23
|
+
## Choosing a Run Entrypoint
|
|
24
|
+
|
|
25
|
+
By default, delegation calls the sub-agent's plain `#run`. Pass `run_alias:` to call a `run_aliases` method (or any zero-arg [run entrypoint](run-entrypoints.md)) instead — useful when the sub-agent should render a different prompt file for delegated calls:
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
class SupervisorAgent < OmniAgent::Agent
|
|
29
|
+
delegate_to SupportAgent, as: :triage_ticket, run_alias: :triage
|
|
30
|
+
end
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Forwarding Context
|
|
34
|
+
|
|
35
|
+
Delegated agents run **isolated** by default — they get only the `input` string, none of the supervisor's context. Pass `forward:` to share part or all of it:
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
class SupervisorAgent < OmniAgent::Agent
|
|
39
|
+
delegate_to ResearchAgent, as: :research, forward: [ :user, :locale ]
|
|
40
|
+
delegate_to MathAgent, as: :calculate, forward: true
|
|
41
|
+
end
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
* **`forward: [:key, ...]`**: Only the listed context keys are passed to the sub-agent's `context:`.
|
|
45
|
+
* **`forward: true`**: The entire context hash is forwarded.
|
|
46
|
+
* **`forward: []`** (default): The sub-agent runs with an empty context.
|
|
47
|
+
|
|
48
|
+
## Guarding Against Runaway Delegation
|
|
49
|
+
|
|
50
|
+
Since a delegated agent could itself delegate (directly or through a chain of other agents), delegation depth is capped by `OmniAgent.configuration.max_delegation_depth` (default `5`). Exceeding it raises `OmniAgent::MaxDelegationDepthError` instead of recursing forever. Raise the limit in your initializer if you have legitimately deep delegation chains:
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
OmniAgent.configure do |config|
|
|
54
|
+
config.max_delegation_depth = 8
|
|
55
|
+
end
|
|
56
|
+
```
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
<!-- Generated from omniagent-docs/docs/agent/provider.mdx by `bundle exec rake docs:sync`. Do not edit by hand. -->
|
|
2
|
+
<!-- Online version: https://omniagent.activetaskers.com/docs/agent/provider -->
|
|
3
|
+
|
|
4
|
+
# Change an Agent's Provider or Model
|
|
5
|
+
|
|
6
|
+
By default, agents use the configured `default_provider` and `default_model`. You can override these settings on a per-agent basis using one of the following DSL directives.
|
|
7
|
+
|
|
8
|
+
## Change Provider and Model
|
|
9
|
+
|
|
10
|
+
Use `provider` when you want to change the provider and optionally specify a model.
|
|
11
|
+
|
|
12
|
+
```ruby
|
|
13
|
+
provider :openai, model: 'gpt-5.4'
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
You can also change only the provider and let it use that provider's default model:
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
provider :openai
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
To run against a local model via [Ollama](../supported-providers.md#ollama) instead:
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
provider :ollama, model: 'llama3.1'
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Change Only the Model
|
|
29
|
+
|
|
30
|
+
Use `use_model` when you want to keep the current `default_provider` and only override the model.
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
use_model 'gpt-5.4'
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Important
|
|
37
|
+
|
|
38
|
+
`provider` and `use_model` are mutually exclusive within the same agent definition. You must choose **one** of the following approaches:
|
|
39
|
+
|
|
40
|
+
* Change the provider (and optionally the model) with `provider`
|
|
41
|
+
* Keep the default provider and change only the model with `use_model`
|
|
42
|
+
|
|
43
|
+
### Valid:
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
provider :openai, model: 'gpt-5.4'
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
```ruby
|
|
50
|
+
provider :anthropic
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
```ruby
|
|
54
|
+
use_model 'gpt-5.4'
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Invalid:
|
|
58
|
+
|
|
59
|
+
```ruby
|
|
60
|
+
provider :openai, model: 'gpt-5.4'
|
|
61
|
+
use_model 'gpt-5.5'
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The example above is not allowed because both directives are defined in the same agent.
|
|
65
|
+
|
|
66
|
+
> **Note:** An agent can either override its provider using `provider` or override its model using `use_model`, but it cannot use both directives simultaneously.
|
|
67
|
+
|
|
68
|
+
## Inheritance
|
|
69
|
+
|
|
70
|
+
Subclasses inherit their parent agent's `provider`, model, `options`, tags, callbacks, tools and `output`, and can override any of them:
|
|
71
|
+
|
|
72
|
+
```ruby
|
|
73
|
+
class ApplicationAgent < OmniAgent::Agent
|
|
74
|
+
provider :openai, model: "gpt-4o"
|
|
75
|
+
options temperature: 0.2
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
class SupportAgent < ApplicationAgent
|
|
79
|
+
options max_tokens: 500 # merged: { temperature: 0.2, max_tokens: 500 }
|
|
80
|
+
end
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
A subclass may call `use_model` even if its parent called `provider`. As on its own, `use_model` means "default provider + this model", so the subclass does not keep the parent's provider:
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
class LocalSupportAgent < ApplicationAgent
|
|
87
|
+
use_model "llama3.1" # uses OmniAgent.configuration.default_provider, not :openai
|
|
88
|
+
end
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
To keep the parent's provider and only change the model, repeat it: `provider :openai, model: "gpt-4o-mini"`.
|
|
92
|
+
|
|
93
|
+
A tool in the subclass replaces a parent tool with the same name (for example `SupportAgent::Tools::Lookup` replaces `ApplicationAgent::Tools::Lookup`), and the same applies to `delegate_to ..., as:` names.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<!-- Generated from omniagent-docs/docs/agent/run-entrypoints.mdx by `bundle exec rake docs:sync`. Do not edit by hand. -->
|
|
2
|
+
<!-- Online version: https://omniagent.activetaskers.com/docs/agent/run-entrypoints -->
|
|
3
|
+
|
|
4
|
+
# Run Entrypoints
|
|
5
|
+
|
|
6
|
+
`OmniAgent::Agent` always exposes the low-level `run` method, but you'll usually call an agent through one of its higher-level entrypoints.
|
|
7
|
+
|
|
8
|
+
## Implicit Entrypoints
|
|
9
|
+
|
|
10
|
+
Any zero-argument instance method you define on an agent automatically becomes a runnable entrypoint:
|
|
11
|
+
|
|
12
|
+
```ruby
|
|
13
|
+
class ResearchAgent < OmniAgent::Agent
|
|
14
|
+
use_model "gpt-4o-mini"
|
|
15
|
+
|
|
16
|
+
def summarize
|
|
17
|
+
@message = "Summarize the latest market trends."
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Calling `ResearchAgent.new.summarize` with no arguments runs your method body and uses its return value (or `@message`, if set) as the input sent to the model. Calling it **with** arguments instead runs the agent directly:
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
ResearchAgent.new.summarize # runs `summarize`, then calls the model
|
|
26
|
+
ResearchAgent.new.summarize("Custom input") # skips to `run` with this input
|
|
27
|
+
ResearchAgent.new.summarize(context: { history: [...] })
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
This is handled by `method_added`, so it works for any method you define — no extra declaration needed. It also means the prompt file used is named after the calling method: defining `summarize` will look for `app/agents/research_agent/summarize.md.erb` in addition to the base `prompt.md.erb`.
|
|
31
|
+
|
|
32
|
+
## Explicit Run Aliases
|
|
33
|
+
|
|
34
|
+
If you'd rather declare entrypoints explicitly instead of relying on every zero-arg method, use `run_aliases`:
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
class ResearchAgent < OmniAgent::Agent
|
|
38
|
+
run_aliases :ask, :follow_up
|
|
39
|
+
end
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
This defines `ask` and `follow_up` as direct wrappers around `run`, each tagged with their own name as `prompt_method` (so they can pick up a matching `<method_name>.md.erb` prompt file).
|
|
43
|
+
|
|
44
|
+
## Calling the Agent Directly
|
|
45
|
+
|
|
46
|
+
The simplest way to run an agent is `.new.run(input)`:
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
ResearchAgent.new.run("Hello!")
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Scoped Instances with `with`
|
|
53
|
+
|
|
54
|
+
Use the class-level `with` helper to build a pre-configured instance without mutating the class itself:
|
|
55
|
+
|
|
56
|
+
```ruby
|
|
57
|
+
ResearchAgent.with(user_id: 42).run("What's new?")
|
|
58
|
+
|
|
59
|
+
ResearchAgent.with(
|
|
60
|
+
provider_override: :mock,
|
|
61
|
+
model_override: "gpt-4.1-mini"
|
|
62
|
+
).run("Test run")
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`with` accepts:
|
|
66
|
+
|
|
67
|
+
* **Context kwargs** (e.g. `user_id: 42`) — merged into every `run` call made on the returned instance.
|
|
68
|
+
* **`provider_override`** / **`model_override`** — temporarily swap the provider or model for this instance only.
|
|
69
|
+
* **`options_override`** — merged into the chat options sent to the provider.
|
|
70
|
+
|
|
71
|
+
## Streaming
|
|
72
|
+
|
|
73
|
+
Any of the entrypoints above can stream by prefixing the call with `.stream` and passing a block:
|
|
74
|
+
|
|
75
|
+
```ruby
|
|
76
|
+
ResearchAgent.with(user_id: 42).stream.run("What's new?") do |event|
|
|
77
|
+
print event.text if event.text?
|
|
78
|
+
end
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
See [Streaming Responses](streaming.md) for the full event reference.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<!-- Generated from omniagent-docs/docs/agent/streaming.mdx by `bundle exec rake docs:sync`. Do not edit by hand. -->
|
|
2
|
+
<!-- Online version: https://omniagent.activetaskers.com/docs/agent/streaming -->
|
|
3
|
+
|
|
4
|
+
# Streaming Responses
|
|
5
|
+
|
|
6
|
+
Any run entrypoint — `run`, an implicit entrypoint, or a `run_aliases` method — can stream its response by prefixing the call with `.stream` and passing a block. Without `.stream` (or without a block), behavior is completely unchanged.
|
|
7
|
+
|
|
8
|
+
```ruby
|
|
9
|
+
agent.stream.run("Hello") do |event|
|
|
10
|
+
print event.text if event.text?
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
ResearchAgent.with(user_id: 42).stream.summarize("Latest trends") do |event|
|
|
14
|
+
# ...
|
|
15
|
+
end
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`.stream` must come **before** the call, not after — the LLM request happens synchronously inside `run`/the entrypoint, so there's nothing to stream once it returns. The call still returns the same `OmniAgent::Providers::Response` it always would; `.stream` only adds a side channel of events emitted while it runs.
|
|
19
|
+
|
|
20
|
+
## Event Types
|
|
21
|
+
|
|
22
|
+
Each yielded object is an `OmniAgent::Streaming::Event`:
|
|
23
|
+
|
|
24
|
+
| Type | Predicate | Fields | When |
|
|
25
|
+
| --- | --- | --- | --- |
|
|
26
|
+
| `:text` | `text?` | `text` | A chunk of the assistant's response text |
|
|
27
|
+
| `:tool_call` | `tool_call?` | `tool_name`, `tool_arguments`, `tool_id` | Right before a tool is invoked |
|
|
28
|
+
| `:tool_result` | `tool_result?` | `tool_name`, `tool_id`, `content`, `error?` | Right after a tool finishes (or raises) |
|
|
29
|
+
| `:done` | `done?` | `response` | Generation is complete; `response` is the final `Response` |
|
|
30
|
+
|
|
31
|
+
A full tool-calling round looks like:
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
:text (any content before the tool call, if the model produced any)
|
|
35
|
+
:tool_call
|
|
36
|
+
:tool_result
|
|
37
|
+
:text, :text, ... (the final answer, streamed)
|
|
38
|
+
:done
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Example: Chat UI
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
TestAgent.with(user_id: 2).stream.user_query("Search and summarize") do |event|
|
|
45
|
+
case event.type
|
|
46
|
+
when :text
|
|
47
|
+
print event.text
|
|
48
|
+
when :tool_call
|
|
49
|
+
puts "\n[using #{event.tool_name}...]"
|
|
50
|
+
when :tool_result
|
|
51
|
+
puts "[#{event.error? ? "failed" : "done"}]"
|
|
52
|
+
when :done
|
|
53
|
+
puts "\n---"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Provider Support
|
|
59
|
+
|
|
60
|
+
Streaming is implemented per-provider. The built-in `openai` provider streams via the `openai` gem's `client.chat.completions.stream` and emits `:text` events for content deltas; tool-call arguments are not streamed incrementally (a `:tool_call` event fires once the full round finishes and arguments are fully parsed — a tool can't be invoked from a partial JSON fragment). The `ollama` provider streams the same way, since it subclasses `openai` and only swaps the client's `base_url` and defaults. The `mock` provider also supports streaming, splitting its canned response into words, useful for testing UI code without a network call.
|
|
61
|
+
|
|
62
|
+
Retries via `with_retries` wrap stream *creation* only — a connection drop mid-stream is not retried.
|