riffer 0.40.0 → 0.42.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/{AGENTS.md → .claude/CLAUDE.md} +0 -8
- data/.claude/rules/comments.md +13 -0
- data/.claude/rules/rbs-inline.md +93 -0
- data/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +22 -0
- data/README.md +20 -24
- data/docs/AGENTS.md +24 -14
- data/docs/AGENT_LIFECYCLE.md +2 -2
- data/docs/CONFIGURATION.md +105 -21
- data/docs/SERIALIZATION.md +4 -3
- data/docs/TOOLS.md +18 -1
- data/docs/providers/AMAZON_BEDROCK.md +14 -8
- data/docs/providers/ANTHROPIC.md +9 -7
- data/docs/providers/AZURE_OPENAI.md +12 -12
- data/docs/providers/CUSTOM_PROVIDERS.md +32 -16
- data/docs/providers/GEMINI.md +28 -5
- data/docs/providers/OPENAI.md +21 -5
- data/docs/providers/OPENROUTER.md +11 -6
- data/docs/providers/PROVIDERS.md +16 -1
- data/lib/riffer/agent/config.rb +0 -6
- data/lib/riffer/agent/run.rb +2 -4
- data/lib/riffer/agent/serializer.rb +0 -2
- data/lib/riffer/agent.rb +4 -26
- data/lib/riffer/config.rb +6 -6
- data/lib/riffer/evals/judge.rb +3 -5
- data/lib/riffer/guardrail.rb +1 -1
- data/lib/riffer/helpers/identifier.rb +41 -0
- data/lib/riffer/providers/amazon_bedrock.rb +31 -19
- data/lib/riffer/providers/anthropic.rb +23 -8
- data/lib/riffer/providers/azure_open_ai.rb +19 -11
- data/lib/riffer/providers/base.rb +32 -1
- data/lib/riffer/providers/gemini/client.rb +120 -0
- data/lib/riffer/providers/gemini.rb +13 -62
- data/lib/riffer/providers/mock.rb +5 -4
- data/lib/riffer/providers/open_ai.rb +23 -8
- data/lib/riffer/providers/open_router.rb +23 -9
- data/lib/riffer/registrable.rb +81 -0
- data/lib/riffer/tool.rb +1 -0
- data/lib/riffer/tools/toolable.rb +2 -3
- data/lib/riffer/version.rb +1 -1
- data/lib/riffer.rb +3 -0
- data/sig/_private/riffer/providers/amazon_bedrock.rbs +4 -2
- data/sig/_private/riffer/providers/anthropic.rbs +4 -2
- data/sig/_private/riffer/providers/gemini.rbs +7 -0
- data/sig/_private/riffer/providers/open_ai.rbs +4 -2
- data/sig/_private/riffer/providers/open_router.rbs +4 -2
- data/sig/generated/riffer/agent/config.rbs +1 -5
- data/sig/generated/riffer/agent.rbs +2 -18
- data/sig/generated/riffer/config.rbs +25 -15
- data/sig/generated/riffer/evals/judge.rbs +2 -4
- data/sig/generated/riffer/helpers/identifier.rbs +19 -0
- data/sig/generated/riffer/providers/amazon_bedrock.rbs +13 -2
- data/sig/generated/riffer/providers/anthropic.rbs +13 -2
- data/sig/generated/riffer/providers/azure_open_ai.rbs +14 -4
- data/sig/generated/riffer/providers/base.rbs +22 -0
- data/sig/generated/riffer/providers/gemini/client.rbs +65 -0
- data/sig/generated/riffer/providers/gemini.rbs +7 -23
- data/sig/generated/riffer/providers/mock.rbs +4 -3
- data/sig/generated/riffer/providers/open_ai.rbs +13 -2
- data/sig/generated/riffer/providers/open_router.rbs +16 -3
- data/sig/generated/riffer/registrable.rbs +51 -0
- data/sig/generated/riffer/tool.rbs +2 -0
- data/sig/generated/riffer/tools/toolable.rbs +3 -1
- data/sig/generated/riffer.rbs +4 -0
- data/sig/manual/riffer/agent.rbs +7 -0
- data/sig/manual/riffer/helpers/identifier.rbs +5 -0
- data/sig/manual/riffer/tool.rbs +7 -0
- metadata +14 -11
- data/.agents/architecture.md +0 -265
- data/.agents/code-style.md +0 -110
- data/.agents/providers.md +0 -45
- data/.agents/rbs-inline.md +0 -169
- data/.agents/testing.md +0 -60
- data/CLAUDE.md +0 -1
- data/lib/riffer/helpers/class_name_converter.rb +0 -22
- data/sig/generated/riffer/helpers/class_name_converter.rbs +0 -12
- data/sig/manual/riffer/helpers/class_name_converter.rbs +0 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: eabed1a1a6844787d92f31be0a72e210117005c19b4dd5e021a905121f83e8e1
|
|
4
|
+
data.tar.gz: 4cc6b6bde7e4b474d7ca6fb2672a3e115d4584b096f1de5d967a2b1a751f49ba
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 220febe23864053fa767e3eecc6850dc0c25ebdcb363a86d5daa58cd317c5500d08f2b50f499f133a77d7b4cf5182485c6faf0ddf36550099b22b7dd203af0c6
|
|
7
|
+
data.tar.gz: d231a4b0dc8c0468f4cba1abf9e3ace390852efc4b6adf5c23229c6729d4ad71fb0afdd5ae5b50540a188b79ec519ef58f95972357fb9854398570d0de058781
|
|
@@ -10,14 +10,6 @@ Ruby gem framework for building AI-powered agents with LLM provider adapters.
|
|
|
10
10
|
- **Model format**: `provider/model` (e.g., `openai/gpt-4`)
|
|
11
11
|
- **Docs**: when adding a public config option or message attribute, update the matching page in `docs/` (e.g., `docs/CONFIGURATION.md`, `docs/MESSAGES.md`). Adding a NEW page under `docs/` also requires a `docs-site/manifest.yml` entry — the site build fails without one. RDoc ≠ user docs.
|
|
12
12
|
|
|
13
|
-
## Topic Guides
|
|
14
|
-
|
|
15
|
-
- [Architecture](.agents/architecture.md) - Core components and project structure
|
|
16
|
-
- [Testing](.agents/testing.md) - Minitest spec DSL and VCR cassettes
|
|
17
|
-
- [Code Style](.agents/code-style.md) - RuboCop, comment, and RDoc conventions
|
|
18
|
-
- [Providers](.agents/providers.md) - Adding new LLM provider adapters
|
|
19
|
-
- [RBS Inline](.agents/rbs-inline.md) - Type annotations with rbs-inline
|
|
20
|
-
|
|
21
13
|
## Commands
|
|
22
14
|
|
|
23
15
|
All wrappers `exec bundle exec …` under the hood.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths: ["**/*.rb", "**/*.rake", "**/Gemfile"]
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Comments
|
|
6
|
+
|
|
7
|
+
A comment exists to explain a **why** when the code itself cannot — never a **how**, and never a restatement of what the code already says. This bar governs all prose, from inline comments to docstrings.
|
|
8
|
+
|
|
9
|
+
- **Internal and private code** — everything in an application, plus a library's non-exported internals — is self-documenting via clear names and strong types. A comment survives only when it explains something a competent reader cannot recover from the code alone: a non-local constraint, an external-system quirk, a deliberate non-obvious tradeoff. A description of _what_ the code does, or a why that's evident from reading it, gets cut.
|
|
10
|
+
- **A published library's public surface** gets one verb-first sentence per exported symbol ("Serializes the definition to JSON."). An optional second sentence is reserved strictly for a why — a non-obvious constraint or rationale — never a second sentence of how. Needing more than one sentence to say _what_ it does is a smell the symbol does too much.
|
|
11
|
+
- **Types are not prose's job.** Parameters, return values, and field types live in the type system (TypeScript types, rbs-inline `#:` annotations) — never restated in comments that duplicate them.
|
|
12
|
+
- **Markers.** `TODO` / `FIXME` / `HACK` are tracked work and stay; `NOTE` / `REVIEW` meet the same why-bar as any other comment.
|
|
13
|
+
- **No history.** A comment describes the present, never how the code got there — no "was X, now Y", no story of the bug that revealed a constraint. State a still-true constraint in the present tense ("the API returns null for empty results — guard").
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths: ["lib/**/*.rb", "sig/**/*"]
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# RBS Inline
|
|
6
|
+
|
|
7
|
+
Type annotations are added directly in Ruby source files using [rbs-inline](https://github.com/soutaro/rbs-inline).
|
|
8
|
+
|
|
9
|
+
## Required Header
|
|
10
|
+
|
|
11
|
+
Every `lib/**/*.rb` file starts with the two-line header `# frozen_string_literal: true` + `# rbs_inline: enabled` — rbs-inline silently skips any file missing the magic comment on line 2.
|
|
12
|
+
|
|
13
|
+
## Annotation Conventions
|
|
14
|
+
|
|
15
|
+
The **`#:`** prefix is used — standalone lines above methods (RBS method signatures) or inline on the same line (attributes, constants).
|
|
16
|
+
|
|
17
|
+
`# @rbs` is used **only** for ivar declarations; everything else uses `#:`. The `#:` shorthand on an assignment is a Steep _assertion_ — it types the expression but does **not declare the ivar**. To declare an ivar's type, use a `# @rbs` comment inside the class body:
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
class Riffer::Agent::Session
|
|
21
|
+
# @rbs @callbacks: Array[^(Riffer::Messages::Base) -> void] # instance ivar
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
module Riffer
|
|
25
|
+
# @rbs self.@config: Riffer::Config? # class/module-level ivar
|
|
26
|
+
end
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## RDoc Conventions
|
|
30
|
+
|
|
31
|
+
**The `#--` stop directive.** Place `#--` on the line immediately before a **standalone** `#:` type annotation. Without it, RDoc treats `#:` as a label-list marker and corrupts the preceding description into a `<pre>` block. Inline `#:` on the same line as code (attributes, constants) does not need it.
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
# Serializes the agent definition to a transferable JSON payload.
|
|
35
|
+
#--
|
|
36
|
+
#: (Riffer::Agent) -> String
|
|
37
|
+
def serialize(agent)
|
|
38
|
+
|
|
39
|
+
# The agent's display name.
|
|
40
|
+
attr_reader :name #: String
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Raises.** Document a raise **only when it's part of the caller's contract** — something a caller should reasonably anticipate and handle. Skip programmer-error guards and "should never happen" assertions. When the raise condition merely restates the declared `#:` type, phrase it by intent ("Raises Riffer::ArgumentError on an invalid value") rather than re-listing the type union.
|
|
44
|
+
|
|
45
|
+
**Examples.** Include an example only when a **consumer is likely to use the thing themselves** — a public entry point they construct, subclass, or call. Keep them sparing and write them as indented code blocks (2 extra spaces of indent). Usage walkthroughs belong in `docs/`.
|
|
46
|
+
|
|
47
|
+
**Inline code formatting.** Use `+word+` for single-word inline code; for multi-word expressions (spaces, colons, brackets) use `<tt>multi word expression</tt>`.
|
|
48
|
+
|
|
49
|
+
**Internal APIs.** Mark with `# :nodoc:` to exclude from generated documentation.
|
|
50
|
+
|
|
51
|
+
## Optional-dependency types (consumer-safe signatures)
|
|
52
|
+
|
|
53
|
+
`sig/generated/` ships with the gem and is loaded by downstream projects (`rbs collection` / `rbs -r riffer`). rbs-inline copies a method's `#:` signature **verbatim** into the shipped sig, so **never name an optional-dependency type in a `#:` signature** — `OpenAI::*`, `Anthropic::*`, `Aws::*`, `MCP::*`, `Async::*`, `Zeitwerk::*`, etc. A consumer who installs riffer without that gem would hit `Cannot find type`, because those providers are pluggable and the gems ship no usable RBS of their own.
|
|
54
|
+
|
|
55
|
+
**Workaround — assert the type inside the method body instead.** An inline assertion (`local = arg #: OpenAI::Models::…`) is a Steep-only hint that rbs-inline does **not** emit into the signature. Leave the SDK param/return `untyped` in the `#:` line, keep every riffer/stdlib param and return typed, and recover the SDK type with a body assertion:
|
|
56
|
+
|
|
57
|
+
```ruby
|
|
58
|
+
#: (untyped) -> String
|
|
59
|
+
def extract_content(response)
|
|
60
|
+
message = response #: Anthropic::Models::Message
|
|
61
|
+
message.content&.first&.text || "" # fully type-checked against the SDK type
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
#: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
|
|
65
|
+
def handle_stream_chunk(chunk, state:, yielder:)
|
|
66
|
+
typed = chunk #: OpenAI::Models::Chat::ChatCompletionChunk
|
|
67
|
+
# ...
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# When the return value IS the SDK object, type the return `untyped` (no body assertion needed):
|
|
71
|
+
#: (Hash[Symbol, untyped]) -> untyped
|
|
72
|
+
def execute_generate(params)
|
|
73
|
+
@client.messages.create(**params)
|
|
74
|
+
end
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`test/shipped_signatures_test.rb` enforces this — it fails if any optional-dependency type appears in `sig/generated/` or `sig/manual/`.
|
|
78
|
+
|
|
79
|
+
### Where stubs and stdlib deps live
|
|
80
|
+
|
|
81
|
+
- `sig/_private/` — signatures that must **not** ship. RBS **skips** `_`-prefixed directories in library mode, so consumers never load them; riffer's own `steep check` does (via the `Steepfile`). Two kinds, by predictable path: external-gem signatures are named by gem at the top level (`async.rbs`, `mcp.rbs`, `zeitwerk.rbs`, `openai.rbs`, `anthropic.rbs`, `aws-sdk-core/*` — full stubs for RBS-less gems plus arity patches for the provider SDKs); riffer's own hidden stubs mirror `lib/` under `riffer/` (e.g. `riffer/providers/anthropic.rbs` narrows the private `client` method to the SDK-typed client).
|
|
82
|
+
- `sig/manual/` — hand-written riffer-only signatures that are **safe to ship**, for the few things rbs-inline can't generate _at all_ (mirroring `lib/`). In practice that's `extend self` modules (`riffer/agent/run.rbs`, `riffer/helpers/call_or_value.rbs`) and modeling an include applied dynamically (`riffer/tools/toolable.rbs`). SDK-free ivars are **not** hand-written here — declare them inline with `# @rbs` (see "Annotation Conventions"). SDK-typed signatures can't ship, so they go in `_private/riffer/providers/` (the narrowed `client`).
|
|
83
|
+
- `sig/manifest.yaml` — declares the **stdlib** RBS the shipped sigs reference (`uri`, `net-http`) so `rbs -r riffer` resolves them.
|
|
84
|
+
|
|
85
|
+
## Workflow
|
|
86
|
+
|
|
87
|
+
After changing type annotations:
|
|
88
|
+
|
|
89
|
+
1. Run `bin/rbs` to regenerate `sig/generated/` files
|
|
90
|
+
2. Commit both the source changes and the generated `.rbs` files
|
|
91
|
+
3. CI checks for drift between source annotations and committed `.rbs` files
|
|
92
|
+
|
|
93
|
+
Use `bin/rbs-watch` during development to auto-regenerate on file changes.
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,28 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.42.0](https://github.com/janeapp/riffer/compare/riffer/v0.41.0...riffer/v0.42.0) (2026-08-25)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### ⚠ BREAKING CHANGES
|
|
12
|
+
|
|
13
|
+
* duplicate identifiers among named direct subclasses of Riffer::Tool or Riffer::Agent now raise Riffer::DuplicateIdentifierError on lookup. Agent.find/.all (and the new Tool.find/.all) cover named direct subclasses only - anonymous classes are excluded even with an explicit identifier. Riffer::Helpers::ClassNameConverter is removed; use Riffer::Helpers::Identifier.derive (the separator: keyword is dropped).
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
* one identifier registry pattern for tools and agents ([#406](https://github.com/janeapp/riffer/issues/406)) ([72fa7d6](https://github.com/janeapp/riffer/commit/72fa7d6377f903e4a88f9f5ec0e27e51b46dc293))
|
|
18
|
+
|
|
19
|
+
## [0.41.0](https://github.com/janeapp/riffer/compare/riffer/v0.40.0...riffer/v0.41.0) (2026-08-14)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### ⚠ BREAKING CHANGES
|
|
23
|
+
|
|
24
|
+
* Inference providers no longer support extra configuration parameters (e.g. timeouts). Instead, a provider client object can be defined in the riffer config directly. Credential and endpoint parameters are still supported.
|
|
25
|
+
|
|
26
|
+
### Features
|
|
27
|
+
|
|
28
|
+
* config-injected provider clients, retire provider_options ([#391](https://github.com/janeapp/riffer/issues/391)) ([72fa0e3](https://github.com/janeapp/riffer/commit/72fa0e32ff6cfeee29bae9e2618d001cbc387d99))
|
|
29
|
+
|
|
8
30
|
## [0.40.0](https://github.com/janeapp/riffer/compare/riffer/v0.39.0...riffer/v0.40.0) (2026-08-12)
|
|
9
31
|
|
|
10
32
|
|
data/README.md
CHANGED
|
@@ -45,31 +45,31 @@ puts agent.generate('Hello world')
|
|
|
45
45
|
|
|
46
46
|
## Documentation
|
|
47
47
|
|
|
48
|
-
Comprehensive guides live at [
|
|
49
|
-
|
|
50
|
-
- [Overview](https://
|
|
51
|
-
- [Getting Started](https://
|
|
52
|
-
- [Agents](https://
|
|
53
|
-
- [Agent Lifecycle](https://
|
|
54
|
-
- [Agent Loop](https://
|
|
55
|
-
- [Tools](https://
|
|
56
|
-
- [Advanced Tools](https://
|
|
57
|
-
- [Messages](https://
|
|
58
|
-
- [Stream Events](https://
|
|
59
|
-
- [Configuration](https://
|
|
60
|
-
- [Evals](https://
|
|
61
|
-
- [Guardrails](https://
|
|
62
|
-
- [Skills](https://
|
|
63
|
-
- [MCP](https://
|
|
64
|
-
- [Serialization](https://
|
|
65
|
-
- [Tracing](https://
|
|
66
|
-
- [Providers](https://
|
|
48
|
+
Comprehensive guides live at [riffer.ai](https://riffer.ai):
|
|
49
|
+
|
|
50
|
+
- [Overview](https://riffer.ai/guides/overview/) - Core concepts and architecture
|
|
51
|
+
- [Getting Started](https://riffer.ai/guides/getting-started/) - Installation and first steps
|
|
52
|
+
- [Agents](https://riffer.ai/guides/agents/) - Defining and configuring agents
|
|
53
|
+
- [Agent Lifecycle](https://riffer.ai/guides/agent-lifecycle/) - Generate, stream, and responses
|
|
54
|
+
- [Agent Loop](https://riffer.ai/guides/agent-loop/) - Tool execution flow and stopping
|
|
55
|
+
- [Tools](https://riffer.ai/guides/tools/) - Creating tools for agents
|
|
56
|
+
- [Advanced Tools](https://riffer.ai/guides/advanced-tools/) - Timeouts, runtime, and registration
|
|
57
|
+
- [Messages](https://riffer.ai/guides/messages/) - Message types and formats
|
|
58
|
+
- [Stream Events](https://riffer.ai/guides/stream-events/) - Streaming responses
|
|
59
|
+
- [Configuration](https://riffer.ai/guides/configuration/) - Framework configuration
|
|
60
|
+
- [Evals](https://riffer.ai/guides/evals/) - Evaluating agent quality
|
|
61
|
+
- [Guardrails](https://riffer.ai/guides/guardrails/) - Input/output validation
|
|
62
|
+
- [Skills](https://riffer.ai/guides/skills/) - Packaged agent capabilities
|
|
63
|
+
- [MCP](https://riffer.ai/guides/mcp/) - Integrating third-party MCP servers
|
|
64
|
+
- [Serialization](https://riffer.ai/guides/serialization/) - Persisting and transferring agent definitions
|
|
65
|
+
- [Tracing](https://riffer.ai/guides/tracing/) - OpenTelemetry span contract and host wiring
|
|
66
|
+
- [Providers](https://riffer.ai/guides/providers/overview/) - LLM provider adapters
|
|
67
67
|
|
|
68
68
|
The guide sources are in the [docs](docs/) directory.
|
|
69
69
|
|
|
70
70
|
### API Reference
|
|
71
71
|
|
|
72
|
-
The full API reference is published at [
|
|
72
|
+
The full API reference is published at [riffer.ai/api](https://riffer.ai/api/). Preview the site locally with:
|
|
73
73
|
|
|
74
74
|
```bash
|
|
75
75
|
bin/rake docs:serve
|
|
@@ -140,7 +140,3 @@ All notable changes to this project are documented in `CHANGELOG.md`.
|
|
|
140
140
|
## License
|
|
141
141
|
|
|
142
142
|
Licensed under the MIT License. See `LICENSE.txt` for details.
|
|
143
|
-
|
|
144
|
-
## Maintainers
|
|
145
|
-
|
|
146
|
-
- Jake Bottrall - https://github.com/bottrall
|
data/docs/AGENTS.md
CHANGED
|
@@ -128,17 +128,6 @@ end
|
|
|
128
128
|
|
|
129
129
|
Loads tools from registered [MCP](MCP.md) servers by tag. Like `uses_tools`, **`use_mcp` is not inherited**—add it on each subclass that should include MCP tools.
|
|
130
130
|
|
|
131
|
-
### provider_options
|
|
132
|
-
|
|
133
|
-
Passes options to the provider client:
|
|
134
|
-
|
|
135
|
-
```ruby
|
|
136
|
-
class MyAgent < Riffer::Agent
|
|
137
|
-
model 'openai/gpt-5-mini'
|
|
138
|
-
provider_options api_key: ENV['CUSTOM_OPENAI_KEY']
|
|
139
|
-
end
|
|
140
|
-
```
|
|
141
|
-
|
|
142
131
|
### model_options
|
|
143
132
|
|
|
144
133
|
Passes options to each LLM request:
|
|
@@ -317,6 +306,27 @@ agent.generate('Hello')
|
|
|
317
306
|
|
|
318
307
|
When `config:` is supplied, the class-level configuration is ignored for that instance.
|
|
319
308
|
|
|
309
|
+
## Looking Up Agents
|
|
310
|
+
|
|
311
|
+
Look up an agent by identifier with `Riffer::Agent.find`, or list every agent with `Riffer::Agent.all`:
|
|
312
|
+
|
|
313
|
+
```ruby
|
|
314
|
+
class SupportAgent < Riffer::Agent
|
|
315
|
+
model 'openai/gpt-5-mini'
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
Riffer::Agent.find('support_agent') # => SupportAgent
|
|
319
|
+
Riffer::Agent.find(:support_agent) # symbols work too
|
|
320
|
+
Riffer::Agent.find('missing') # => nil
|
|
321
|
+
Riffer::Agent.all # => [SupportAgent, ...]
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
Only **named direct subclasses** are found:
|
|
325
|
+
|
|
326
|
+
- Grandchildren are not visible to a grandparent's `find` or `all`. If your app defines an intermediate base class (`class ApplicationAgent < Riffer::Agent`), call `find`/`all` on the intermediate class to look up its subclasses.
|
|
327
|
+
- Anonymous classes (`Class.new(Riffer::Agent)`) are never findable, even when they set an explicit `identifier`.
|
|
328
|
+
- Two subclasses sharing an identifier raise `Riffer::DuplicateIdentifierError` at the first lookup.
|
|
329
|
+
|
|
320
330
|
## Per-Call Tags
|
|
321
331
|
|
|
322
332
|
`#generate` and `#stream` accept an optional `tags:` hash — a flat map of attribution labels scoped to that single call (for cost/usage attribution, filtering audit logs, slicing telemetry). It is **per-call only**.
|
|
@@ -359,10 +369,10 @@ Riffer does not validate tag count, key/value length, or charset — it forwards
|
|
|
359
369
|
|
|
360
370
|
## Expand Your Agent
|
|
361
371
|
|
|
362
|
-
| Goal | Feature | Guide
|
|
363
|
-
| ----------------------------- | ----------------- |
|
|
372
|
+
| Goal | Feature | Guide |
|
|
373
|
+
| ----------------------------- | ----------------- | ----------------------------------------------------------------- |
|
|
364
374
|
| Call APIs or run functions | Tools | [Tools](TOOLS.md) |
|
|
365
|
-
| Return structured JSON | Structured Output | [structured_output](#structured_output)
|
|
375
|
+
| Return structured JSON | Structured Output | [structured_output](#structured_output) |
|
|
366
376
|
| Validate or filter content | Guardrails | [Guardrails](GUARDRAILS.md) |
|
|
367
377
|
| Measure output quality | Evals | [Evals](EVALS.md) |
|
|
368
378
|
| Add packaged capabilities | Skills | [Skills](SKILLS.md) |
|
data/docs/AGENT_LIFECYCLE.md
CHANGED
|
@@ -347,7 +347,7 @@ See [Messages — Structured Output on Messages](MESSAGES.md#structured-output-o
|
|
|
347
347
|
|
|
348
348
|
### find
|
|
349
349
|
|
|
350
|
-
Find an agent class by identifier:
|
|
350
|
+
Find an agent class by identifier (named direct subclasses only — see [Agents — Looking Up Agents](AGENTS.md#looking-up-agents)):
|
|
351
351
|
|
|
352
352
|
```ruby
|
|
353
353
|
agent_class = Riffer::Agent.find('my_agent')
|
|
@@ -356,7 +356,7 @@ agent = agent_class.new
|
|
|
356
356
|
|
|
357
357
|
### all
|
|
358
358
|
|
|
359
|
-
List all agent subclasses:
|
|
359
|
+
List all registered agent subclasses (named direct subclasses only):
|
|
360
360
|
|
|
361
361
|
```ruby
|
|
362
362
|
Riffer::Agent.all.each do |agent_class|
|
data/docs/CONFIGURATION.md
CHANGED
|
@@ -9,12 +9,15 @@ Use `Riffer.configure` to set up provider credentials:
|
|
|
9
9
|
```ruby
|
|
10
10
|
Riffer.configure do |config|
|
|
11
11
|
config.openai.api_key = ENV['OPENAI_API_KEY']
|
|
12
|
+
config.openai.base_url = ENV['OPENAI_BASE_URL'] # Optional — gateways, proxies
|
|
12
13
|
config.amazon_bedrock.region = 'us-east-1'
|
|
13
14
|
config.amazon_bedrock.api_token = ENV['BEDROCK_API_TOKEN']
|
|
14
15
|
config.anthropic.api_key = ENV['ANTHROPIC_API_KEY']
|
|
15
16
|
end
|
|
16
17
|
```
|
|
17
18
|
|
|
19
|
+
Providers take no constructor arguments — these settings are the only way to give a provider its credentials.
|
|
20
|
+
|
|
18
21
|
## Accessing Configuration
|
|
19
22
|
|
|
20
23
|
Access the current configuration via `Riffer.config`:
|
|
@@ -34,6 +37,66 @@ Riffer.config.anthropic.api_key
|
|
|
34
37
|
|
|
35
38
|
For provider credentials and setup, see the individual [Provider guides](providers/PROVIDERS.md).
|
|
36
39
|
|
|
40
|
+
### Provider Clients
|
|
41
|
+
|
|
42
|
+
Out of the box, each provider builds a default SDK client from its configured credentials. For anything beyond credentials — timeouts, retries, proxies, gateways — assign your own client to `config.<provider>.client`:
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
Riffer.configure do |config|
|
|
46
|
+
config.openai.client = OpenAI::Client.new(
|
|
47
|
+
api_key: ENV['OPENAI_API_KEY'],
|
|
48
|
+
timeout: 30,
|
|
49
|
+
max_retries: 4
|
|
50
|
+
)
|
|
51
|
+
config.gemini.client = Riffer::Providers::Gemini::Client.new(
|
|
52
|
+
api_key: ENV['GEMINI_API_KEY'],
|
|
53
|
+
read_timeout: 120
|
|
54
|
+
)
|
|
55
|
+
end
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Every provider accepts a client instance or a `Proc` returning one:
|
|
59
|
+
|
|
60
|
+
| Provider | Setting | Default client built from credentials |
|
|
61
|
+
| -------------- | ------------------------------ | ------------------------------------------------------------- |
|
|
62
|
+
| OpenAI | `config.openai.client` | `OpenAI::Client` |
|
|
63
|
+
| Azure OpenAI | `config.azure_openai.client` | `OpenAI::Client` (with the Azure endpoint as `base_url`) |
|
|
64
|
+
| Anthropic | `config.anthropic.client` | `Anthropic::Client` |
|
|
65
|
+
| Amazon Bedrock | `config.amazon_bedrock.client` | `Aws::BedrockRuntime::Client` |
|
|
66
|
+
| Gemini | `config.gemini.client` | `Riffer::Providers::Gemini::Client` (riffer-owned, see below) |
|
|
67
|
+
| OpenRouter | `config.openrouter.client` | `OpenAI::Client` (pinned to the OpenRouter endpoint) |
|
|
68
|
+
|
|
69
|
+
A `Proc` takes **no arguments** and is resolved on **every LLM call**, never cached by riffer — memoize inside the Proc when construction is expensive. This makes the Proc the right tool for:
|
|
70
|
+
|
|
71
|
+
- **Fork safety** (Puma clustered, Sidekiq swarm): a client built at boot holds sockets that break across `fork`; build (and cache) per process instead.
|
|
72
|
+
- **Expiring credentials** (Azure AD tokens, STS-vended keys): re-resolve before they go stale.
|
|
73
|
+
|
|
74
|
+
```ruby
|
|
75
|
+
Riffer.configure do |config|
|
|
76
|
+
# Fork-safe shared client: one per process, built on first use after fork.
|
|
77
|
+
config.anthropic.client = -> {
|
|
78
|
+
ClientRegistry.anthropic_for(Process.pid)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
# Re-resolved before the token goes stale.
|
|
82
|
+
config.azure_openai.client = -> {
|
|
83
|
+
OpenAI::Client.new(api_key: AzureAd.current_token, base_url: ENV['AZURE_OPENAI_ENDPOINT'])
|
|
84
|
+
}
|
|
85
|
+
end
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Because the Proc receives no arguments, it can only vary the client by process-wide state — it cannot route per agent or per request. Client selection is a global concern; to talk to different accounts or endpoints from different agents, register a provider subclass with its own config (see [Multiple Configurations](#multiple-configurations)).
|
|
89
|
+
|
|
90
|
+
A configured client always wins over configured credentials: once `config.<provider>.client` is set, the credential members are unused, since riffer no longer builds the client.
|
|
91
|
+
|
|
92
|
+
### Falling through to the SDK
|
|
93
|
+
|
|
94
|
+
A credential you leave unset in riffer is omitted from the default client rather than passed as `nil`, so each vendor SDK still applies its own resolution. `OPENAI_API_KEY` / `OPENAI_BASE_URL`, `ANTHROPIC_API_KEY`, and the AWS chains — region from `AWS_REGION` / `AWS_DEFAULT_REGION` / shared config, credentials from the environment, the shared credentials file, or an instance/task IAM role — all work with no riffer configuration at all.
|
|
95
|
+
|
|
96
|
+
Two providers deliberately opt out: `OpenRouter` and `AzureOpenAI` borrow `OpenAI::Client` to reach a **different** vendor, so they always pass their credential and endpoint explicitly. Falling through would let the OpenAI SDK pick up `OPENAI_API_KEY` / `OPENAI_BASE_URL` and send an OpenAI credential to `openrouter.ai` or your Azure endpoint. With nothing configured they raise instead — set `config.openrouter.api_key` / `OPENROUTER_API_KEY`, or `config.azure_openai.api_key` and `.endpoint` / `AZURE_OPENAI_API_KEY` and `AZURE_OPENAI_ENDPOINT`.
|
|
97
|
+
|
|
98
|
+
The Gemini provider has no vendor SDK, so riffer ships its own transport: `Riffer::Providers::Gemini::Client` exposes `base_url`, `open_timeout`, `read_timeout`, `write_timeout`, and `proxy_address`/`proxy_port`. Anything implementing its two-method contract (`post`, `post_stream`) can be assigned to `config.gemini.client` — see [Gemini](providers/GEMINI.md).
|
|
99
|
+
|
|
37
100
|
### MCP (Model Context Protocol)
|
|
38
101
|
|
|
39
102
|
Optional settings for [MCP server integrations](MCP.md):
|
|
@@ -113,10 +176,10 @@ Riffer.configure do |config|
|
|
|
113
176
|
end
|
|
114
177
|
```
|
|
115
178
|
|
|
116
|
-
| Option | Description
|
|
117
|
-
| ------------------ |
|
|
118
|
-
| `enabled` | The kill switch, consulted on every span — flipping it at runtime takes effect immediately, short-circuiting to a no-op ahead of the backend. Accepts booleans or `'true'`/`'false'`/`'1'`/`'0'`. Defaults to `true`.
|
|
119
|
-
| `capture_messages` | Opt-in capture of full message content on LLM-call spans (`gen_ai.input.messages`, `gen_ai.output.messages`, `gen_ai.system_instructions`) as GenAI-semconv JSON. Defaults to `false` — message content routinely carries sensitive data. File attachments serialize as metadata-only stubs (media type and name, never bytes), and riffer applies no size limit of its own — cap oversized attributes with the OTEL SDK attribute length limits.
|
|
179
|
+
| Option | Description |
|
|
180
|
+
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
181
|
+
| `enabled` | The kill switch, consulted on every span — flipping it at runtime takes effect immediately, short-circuiting to a no-op ahead of the backend. Accepts booleans or `'true'`/`'false'`/`'1'`/`'0'`. Defaults to `true`. |
|
|
182
|
+
| `capture_messages` | Opt-in capture of full message content on LLM-call spans (`gen_ai.input.messages`, `gen_ai.output.messages`, `gen_ai.system_instructions`) as GenAI-semconv JSON. Defaults to `false` — message content routinely carries sensitive data. File attachments serialize as metadata-only stubs (media type and name, never bytes), and riffer applies no size limit of its own — cap oversized attributes with the OTEL SDK attribute length limits. |
|
|
120
183
|
| `backend` | The backend riffer routes spans through. Assign `Riffer::Tracing::Otel.build` (pass `provider:` to override the global tracer provider — e.g. an in-memory provider in tests), or any object satisfying the duck-typed contract (`in_span` / `current_context` / `with_context`) to route into a non-OTEL system (e.g. Datadog APM). Defaults to `nil` — a no-op. Raises `Riffer::ArgumentError` unless the value is `nil` or responds to `in_span`. See [Tracing → Routing to a non-OpenTelemetry backend](TRACING.md#routing-to-a-non-opentelemetry-backend). |
|
|
121
184
|
|
|
122
185
|
### Pricing
|
|
@@ -200,19 +263,6 @@ There is no per-call override and no customizable placeholder. Callers needing f
|
|
|
200
263
|
|
|
201
264
|
Override global configuration at the agent level:
|
|
202
265
|
|
|
203
|
-
### provider_options
|
|
204
|
-
|
|
205
|
-
Pass options directly to the provider client:
|
|
206
|
-
|
|
207
|
-
```ruby
|
|
208
|
-
class MyAgent < Riffer::Agent
|
|
209
|
-
model 'openai/gpt-5-mini'
|
|
210
|
-
|
|
211
|
-
# Override API key for this agent only
|
|
212
|
-
provider_options api_key: ENV['CUSTOM_OPENAI_KEY']
|
|
213
|
-
end
|
|
214
|
-
```
|
|
215
|
-
|
|
216
266
|
### model_options
|
|
217
267
|
|
|
218
268
|
Pass options to each LLM request:
|
|
@@ -305,17 +355,51 @@ end
|
|
|
305
355
|
|
|
306
356
|
## Multiple Configurations
|
|
307
357
|
|
|
308
|
-
For different environments
|
|
358
|
+
Provider credentials and clients are **global**, resolved per process. For different environments, branch at boot:
|
|
359
|
+
|
|
360
|
+
```ruby
|
|
361
|
+
Riffer.configure do |config|
|
|
362
|
+
config.openai.client = if Rails.env.production?
|
|
363
|
+
OpenAI::Client.new(api_key: ENV['PRODUCTION_OPENAI_KEY'], max_retries: 4)
|
|
364
|
+
else
|
|
365
|
+
OpenAI::Client.new(api_key: ENV['DEV_OPENAI_KEY'], timeout: 10)
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
When a single process has to reach two different accounts or endpoints, give the second one its own provider class and config, then register it under its own identifier:
|
|
371
|
+
|
|
372
|
+
```ruby
|
|
373
|
+
class InternalOpenAI < Riffer::Providers::OpenAI
|
|
374
|
+
InternalConfig = Struct.new(:api_key, :base_url, :client)
|
|
375
|
+
|
|
376
|
+
def self.config
|
|
377
|
+
@config ||= InternalConfig.new(ENV.fetch('INTERNAL_OPENAI_KEY'), ENV.fetch('INTERNAL_GATEWAY'))
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
private
|
|
381
|
+
|
|
382
|
+
def global_client
|
|
383
|
+
self.class.config.client
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
def build_client
|
|
387
|
+
::OpenAI::Client.new(api_key: self.class.config.api_key, base_url: self.class.config.base_url)
|
|
388
|
+
end
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
Riffer::Providers::Repository.register(:internal_openai) { InternalOpenAI }
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
Agents then select it by model prefix (`model 'internal_openai/gpt-5-mini'`), and the two accounts never interfere. What _can_ vary per agent is the model and the generation parameters:
|
|
309
395
|
|
|
310
396
|
```ruby
|
|
311
397
|
class ProductionAgent < Riffer::Agent
|
|
312
398
|
model 'openai/gpt-5-mini'
|
|
313
|
-
provider_options api_key: ENV['PRODUCTION_OPENAI_KEY']
|
|
314
399
|
end
|
|
315
400
|
|
|
316
401
|
class DevelopmentAgent < Riffer::Agent
|
|
317
402
|
model 'openai/gpt-5-mini'
|
|
318
|
-
|
|
319
|
-
model_options temperature: 0.0 # Deterministic for testing
|
|
403
|
+
model_options temperature: 0.0 # Deterministic for testing
|
|
320
404
|
end
|
|
321
405
|
```
|
data/docs/SERIALIZATION.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Serialization
|
|
2
2
|
|
|
3
|
-
`Riffer::Agent::Serializer` turns a **resolved agent** into a
|
|
3
|
+
`Riffer::Agent::Serializer` turns a **resolved agent** into a provider-neutral data hash (`to_h`) and rebuilds an agent from that hash (`from_h`). The hash carries the agent **definition** — not the credentials or client it needs to reach a provider (see [Secrets](#secrets)). Use it to persist agent definitions outside of code, or to transfer them across a process/service boundary.
|
|
4
4
|
|
|
5
5
|
You normally reach it through the delegators on `Riffer::Agent`:
|
|
6
6
|
|
|
@@ -43,7 +43,6 @@ The session is used **as-is**: the rebuilt agent does not prepend anything to it
|
|
|
43
43
|
model: "openai/gpt-4o", # resolved "provider/model" string
|
|
44
44
|
instructions: "You are…", # resolved system prompt
|
|
45
45
|
model_options: { temperature: 0.2 },
|
|
46
|
-
provider_options: { … }, # see the secrets warning below
|
|
47
46
|
max_steps: 8, # integer; -1 = unlimited (see below)
|
|
48
47
|
structured_output: { type: "object", … }, # JSON Schema, or null
|
|
49
48
|
tools: [ { name:, description:, parameters_schema:, timeout: }, … ]
|
|
@@ -101,7 +100,9 @@ A finite integer round-trips as-is; a hash missing the key falls back to the def
|
|
|
101
100
|
|
|
102
101
|
## Secrets
|
|
103
102
|
|
|
104
|
-
`
|
|
103
|
+
`model_options` **rides on the wire as plain data** — it is part of the hash and _will_ transfer. Keep sensitive values out of it; credentials live in global provider configuration and never serialize.
|
|
104
|
+
|
|
105
|
+
The hash describes the agent, not how to authenticate as one. The destination process configures the provider itself — `config.<provider>.client`, or the credentials riffer builds a default client from. Clients resolve lazily on the first LLM call, so `from_h` succeeds in a process with no provider configuration; the `generate` is what fails. See [Configuration](CONFIGURATION.md).
|
|
105
106
|
|
|
106
107
|
## What does **not** transfer
|
|
107
108
|
|
data/docs/TOOLS.md
CHANGED
|
@@ -108,7 +108,7 @@ Options:
|
|
|
108
108
|
| `String` | `string` |
|
|
109
109
|
| `Integer` | `integer` |
|
|
110
110
|
| `Float` | `number` |
|
|
111
|
-
| `Riffer::Params::Boolean`
|
|
111
|
+
| `Riffer::Params::Boolean` | `boolean` |
|
|
112
112
|
| `TrueClass` / `FalseClass` | `boolean` |
|
|
113
113
|
| `Array` | `array` |
|
|
114
114
|
| `Hash` | `object` |
|
|
@@ -143,6 +143,23 @@ class CreateOrderTool < Riffer::Tool
|
|
|
143
143
|
end
|
|
144
144
|
```
|
|
145
145
|
|
|
146
|
+
## Looking Up Tools
|
|
147
|
+
|
|
148
|
+
Look up a tool by identifier with `Riffer::Tool.find`, or list every tool with `Riffer::Tool.all`:
|
|
149
|
+
|
|
150
|
+
```ruby
|
|
151
|
+
Riffer::Tool.find('kb_search') # => SearchTool
|
|
152
|
+
Riffer::Tool.find(:kb_search) # symbols work too
|
|
153
|
+
Riffer::Tool.find('missing') # => nil
|
|
154
|
+
Riffer::Tool.all # => [SearchTool, ...]
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Only **named direct subclasses** are found:
|
|
158
|
+
|
|
159
|
+
- Grandchildren are not visible to a grandparent's `find` or `all`. If your app defines an intermediate base class (`class ApplicationTool < Riffer::Tool`), call `find`/`all` on the intermediate class to look up its subclasses.
|
|
160
|
+
- Anonymous classes (`Class.new(Riffer::Tool)`) are never findable, even when they set an explicit `identifier`.
|
|
161
|
+
- Two subclasses sharing an identifier raise `Riffer::DuplicateIdentifierError` at the first lookup.
|
|
162
|
+
|
|
146
163
|
## The call Method
|
|
147
164
|
|
|
148
165
|
Every tool must implement the `call` method and return a `Riffer::Tools::Response`:
|
|
@@ -22,6 +22,8 @@ Riffer.configure do |config|
|
|
|
22
22
|
end
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
+
`region` resolves in order: `Riffer.config.amazon_bedrock.region` → the AWS SDK's own resolution (`AWS_REGION`, `AWS_DEFAULT_REGION`, shared config). Leaving it unset in riffer defers to the SDK rather than failing.
|
|
26
|
+
|
|
25
27
|
### Bearer Token Authentication
|
|
26
28
|
|
|
27
29
|
For API token authentication:
|
|
@@ -33,15 +35,22 @@ Riffer.configure do |config|
|
|
|
33
35
|
end
|
|
34
36
|
```
|
|
35
37
|
|
|
36
|
-
|
|
38
|
+
### Custom client
|
|
39
|
+
|
|
40
|
+
AWS auth beyond the bearer token or default credential chain (profiles, STS, IRSA), plus retries, timeouts, and endpoints, is configured on your own `Aws::BedrockRuntime::Client`:
|
|
37
41
|
|
|
38
42
|
```ruby
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
43
|
+
Riffer.configure do |config|
|
|
44
|
+
config.amazon_bedrock.client = Aws::BedrockRuntime::Client.new(
|
|
45
|
+
region: 'us-east-1',
|
|
46
|
+
credentials: Aws::AssumeRoleCredentials.new(role_arn: ENV['BEDROCK_ROLE_ARN'], role_session_name: 'riffer'),
|
|
47
|
+
retry_limit: 5
|
|
48
|
+
)
|
|
42
49
|
end
|
|
43
50
|
```
|
|
44
51
|
|
|
52
|
+
The setting accepts a client instance or a no-argument `Proc`, resolved on every LLM call — see [Configuration → Provider Clients](../CONFIGURATION.md#provider-clients).
|
|
53
|
+
|
|
45
54
|
## Supported Models
|
|
46
55
|
|
|
47
56
|
Use Bedrock model IDs in the `amazon_bedrock/model` format:
|
|
@@ -168,10 +177,7 @@ The provider converts Riffer messages to Bedrock format:
|
|
|
168
177
|
## Direct Provider Usage
|
|
169
178
|
|
|
170
179
|
```ruby
|
|
171
|
-
provider = Riffer::Providers::AmazonBedrock.new
|
|
172
|
-
region: 'us-east-1',
|
|
173
|
-
api_token: ENV['BEDROCK_API_TOKEN'] # Optional
|
|
174
|
-
)
|
|
180
|
+
provider = Riffer::Providers::AmazonBedrock.new
|
|
175
181
|
|
|
176
182
|
response = provider.generate_text(
|
|
177
183
|
prompt: "Hello!",
|
data/docs/providers/ANTHROPIC.md
CHANGED
|
@@ -20,15 +20,19 @@ Riffer.configure do |config|
|
|
|
20
20
|
end
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
For anything beyond the API key — timeouts, retries, proxies — supply your own `Anthropic::Client`:
|
|
24
24
|
|
|
25
25
|
```ruby
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
Riffer.configure do |config|
|
|
27
|
+
config.anthropic.client = Anthropic::Client.new(
|
|
28
|
+
api_key: ENV['ANTHROPIC_API_KEY'],
|
|
29
|
+
timeout: 60
|
|
30
|
+
)
|
|
29
31
|
end
|
|
30
32
|
```
|
|
31
33
|
|
|
34
|
+
The setting accepts a client instance or a no-argument `Proc`, resolved on every LLM call — see [Configuration → Provider Clients](../CONFIGURATION.md#provider-clients).
|
|
35
|
+
|
|
32
36
|
## Supported Models
|
|
33
37
|
|
|
34
38
|
Use Anthropic model IDs in the `anthropic/model` format:
|
|
@@ -224,9 +228,7 @@ The provider converts Riffer messages to Anthropic format:
|
|
|
224
228
|
## Direct Provider Usage
|
|
225
229
|
|
|
226
230
|
```ruby
|
|
227
|
-
provider = Riffer::Providers::Anthropic.new
|
|
228
|
-
api_key: ENV['ANTHROPIC_API_KEY']
|
|
229
|
-
)
|
|
231
|
+
provider = Riffer::Providers::Anthropic.new
|
|
230
232
|
|
|
231
233
|
response = provider.generate_text(
|
|
232
234
|
prompt: "Hello!",
|