little_ghost 0.3.0 → 0.5.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/README.md +90 -84
- data/docs/guides/assemblies.md +412 -0
- data/docs/guides/code_mode.md +275 -0
- data/docs/guides/core_concepts.md +150 -239
- data/docs/guides/getting_started.md +125 -87
- data/docs/guides/integrations.md +217 -0
- data/docs/guides/models_and_providers.md +125 -0
- data/docs/guides/production.md +253 -0
- data/docs/guides/prompt_views.md +139 -0
- data/docs/guides/sandboxing.md +282 -0
- data/docs/guides/skills.md +141 -0
- data/docs/guides/structured_outputs_and_content.md +135 -0
- data/docs/guides/tools.md +329 -0
- data/lib/little_ghost/ag_ui/adapter.rb +5 -5
- data/lib/little_ghost/agent/delegation.rb +3 -3
- data/lib/little_ghost/agent/skills.rb +6 -1
- data/lib/little_ghost/agent/tool_loop.rb +6 -2
- data/lib/little_ghost/agent.rb +411 -214
- data/lib/little_ghost/agent_builder.rb +61 -22
- data/lib/little_ghost/{agent_interruptions.rb → agent_interjections.rb} +12 -12
- data/lib/little_ghost/agent_stream_source.rb +262 -0
- data/lib/little_ghost/artifact.rb +182 -0
- data/lib/little_ghost/artifacts/presentation_budget.rb +44 -0
- data/lib/little_ghost/artifacts/workspace_store.rb +370 -0
- data/lib/little_ghost/artifacts.rb +11 -0
- data/lib/little_ghost/assembly.rb +110 -30
- data/lib/little_ghost/assembly_builder.rb +59 -21
- data/lib/little_ghost/assembly_execution.rb +110 -6
- data/lib/little_ghost/code_mode/broker.rb +164 -0
- data/lib/little_ghost/code_mode/catalog.rb +54 -0
- data/lib/little_ghost/code_mode/engine.rb +58 -0
- data/lib/little_ghost/code_mode/javascript/catalog.rb +118 -0
- data/lib/little_ghost/code_mode/javascript/client.rb +550 -0
- data/lib/little_ghost/code_mode/javascript/host.rb +347 -0
- data/lib/little_ghost/code_mode/javascript/session.rb +579 -0
- data/lib/little_ghost/code_mode/javascript_engine.rb +172 -0
- data/lib/little_ghost/code_mode/protocol.rb +78 -0
- data/lib/little_ghost/code_mode/ruby/catalog.rb +80 -0
- data/lib/little_ghost/code_mode/ruby/host.rb +102 -0
- data/lib/little_ghost/code_mode/ruby/session.rb +508 -0
- data/lib/little_ghost/code_mode/ruby_engine.rb +86 -0
- data/lib/little_ghost/code_mode/runtime.rb +335 -0
- data/lib/little_ghost/code_mode/session.rb +61 -0
- data/lib/little_ghost/code_mode/types.rb +58 -0
- data/lib/little_ghost/code_mode.rb +53 -0
- data/lib/little_ghost/configuration.rb +366 -47
- data/lib/little_ghost/content.rb +24 -13
- data/lib/little_ghost/data_map.rb +209 -0
- data/lib/little_ghost/errors.rb +28 -9
- data/lib/little_ghost/execution.rb +33 -34
- data/lib/little_ghost/graph.rb +376 -197
- data/lib/little_ghost/mcp/client.rb +487 -88
- data/lib/little_ghost/mcp/toolset.rb +210 -0
- data/lib/little_ghost/mcp/types.rb +216 -0
- data/lib/little_ghost/mcp.rb +3 -0
- data/lib/little_ghost/message.rb +4 -4
- data/lib/little_ghost/model_capabilities.rb +9 -6
- data/lib/little_ghost/model_request.rb +0 -12
- data/lib/little_ghost/model_resolver.rb +15 -5
- data/lib/little_ghost/model_response.rb +3 -7
- data/lib/little_ghost/network/authorizer_server.rb +162 -0
- data/lib/little_ghost/network/certificate_authority.rb +98 -0
- data/lib/little_ghost/network/envoy_config.rb +362 -0
- data/lib/little_ghost/network/envoy_gateway.rb +409 -0
- data/lib/little_ghost/network/external_gateway.rb +68 -0
- data/lib/little_ghost/network.rb +96 -0
- data/lib/little_ghost/prompt_resolver.rb +9 -7
- data/lib/little_ghost/provider_registry.rb +3 -3
- data/lib/little_ghost/providers/anthropic.rb +8 -1
- data/lib/little_ghost/providers/gemini.rb +10 -1
- data/lib/little_ghost/providers/vertex_ai.rb +6 -1
- data/lib/little_ghost/run.rb +162 -54
- data/lib/little_ghost/run_context.rb +42 -20
- data/lib/little_ghost/run_result.rb +0 -7
- data/lib/little_ghost/runtime/hook.rb +8 -3
- data/lib/little_ghost/runtime/hooks/artifacts.rb +338 -0
- data/lib/little_ghost/runtime.rb +160 -52
- data/lib/little_ghost/sandbox/capabilities.rb +68 -0
- data/lib/little_ghost/sandbox/environment_policy.rb +41 -0
- data/lib/little_ghost/sandbox/filesystem.rb +377 -0
- data/lib/little_ghost/sandbox/isolated_backend.rb +163 -0
- data/lib/little_ghost/sandbox/limits.rb +48 -0
- data/lib/little_ghost/sandbox/mount.rb +129 -0
- data/lib/little_ghost/sandbox/network_policy.rb +108 -0
- data/lib/little_ghost/sandbox/policy.rb +88 -0
- data/lib/little_ghost/sandbox/process_runner.rb +103 -0
- data/lib/little_ghost/sandbox/process_session.rb +335 -0
- data/lib/little_ghost/sandbox/scope.rb +304 -0
- data/lib/little_ghost/sandbox.rb +158 -32
- data/lib/little_ghost/sandboxes/bubblewrap.rb +351 -0
- data/lib/little_ghost/sandboxes/native.rb +51 -0
- data/lib/little_ghost/sandboxes/seatbelt.rb +261 -0
- data/lib/little_ghost/sandboxes/unrestricted.rb +241 -0
- data/lib/little_ghost/session.rb +39 -26
- data/lib/little_ghost/session_store.rb +9 -5
- data/lib/little_ghost/session_stores/agent_core_memory.rb +64 -56
- data/lib/little_ghost/session_stores/filesystem.rb +261 -0
- data/lib/little_ghost/session_stores/memory.rb +7 -0
- data/lib/little_ghost/skills/catalog.rb +94 -14
- data/lib/little_ghost/skills/resource_root.rb +42 -0
- data/lib/little_ghost/skills/skill.rb +0 -3
- data/lib/little_ghost/stream_event.rb +8 -13
- data/lib/little_ghost/subagents/control_tool.rb +8 -0
- data/lib/little_ghost/subagents/manager.rb +53 -50
- data/lib/little_ghost/support/callbacks.rb +3 -1
- data/lib/little_ghost/support/content_capture.rb +3 -3
- data/lib/little_ghost/support/http_client.rb +2 -2
- data/lib/little_ghost/support/redactor.rb +1 -1
- data/lib/little_ghost/swarm.rb +13 -5
- data/lib/little_ghost/tool.rb +157 -64
- data/lib/little_ghost/tool_registry.rb +1 -1
- data/lib/little_ghost/tools/filesystem.rb +1 -1
- data/lib/little_ghost/tools/shell.rb +4 -3
- data/lib/little_ghost/tools/write_todos.rb +6 -1
- data/lib/little_ghost/tracing/open_telemetry.rb +1 -1
- data/lib/little_ghost/version.rb +1 -1
- data/lib/little_ghost/workflow.rb +30 -21
- data/lib/little_ghost/workspace.rb +222 -8
- data/lib/little_ghost.rb +40 -27
- metadata +104 -3
- data/lib/little_ghost/unrestricted_sandbox.rb +0 -306
|
@@ -1,33 +1,62 @@
|
|
|
1
|
-
# Getting Started
|
|
1
|
+
# Getting Started
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
In this guide, you'll run an agent, connect it to a small help center, and stream its answer. The whole feature stays in ordinary Ruby.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Install the gem
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
LittleGhost requires Ruby 3.3 or newer. Add the gem to your `Gemfile`:
|
|
7
|
+
LittleGhost requires Ruby 3.3 or newer. Add the gem to your `Gemfile`, install it, and set a provider credential:
|
|
10
8
|
|
|
11
9
|
```ruby
|
|
12
10
|
gem "little_ghost"
|
|
13
11
|
```
|
|
14
12
|
|
|
15
|
-
Install the bundle and set a provider credential:
|
|
16
|
-
|
|
17
13
|
```sh
|
|
18
14
|
$ bundle install
|
|
19
|
-
$ export
|
|
15
|
+
$ export OPENROUTER_API_KEY="..."
|
|
20
16
|
```
|
|
21
17
|
|
|
22
|
-
|
|
18
|
+
Use your application's secret manager outside a local shell, and never commit provider credentials.
|
|
19
|
+
|
|
20
|
+
This guide uses OpenRouter because one credential is enough to begin. LittleGhost can use other provider connections too; you will configure those in [Running in Production](production.md).
|
|
23
21
|
|
|
24
|
-
##
|
|
22
|
+
## See your first answer
|
|
25
23
|
|
|
26
|
-
|
|
24
|
+
Create `customer_support_agent.rb`:
|
|
27
25
|
|
|
28
26
|
```ruby
|
|
29
27
|
require "little_ghost"
|
|
30
28
|
|
|
29
|
+
class CustomerSupportAgent < LittleGhost::Agent
|
|
30
|
+
model "openrouter:openai/gpt-5.6-luna"
|
|
31
|
+
system_prompt "Answer customer questions clearly and concisely."
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
run = CustomerSupportAgent.ask("Can I change the address on my order?")
|
|
35
|
+
|
|
36
|
+
if run.completed?
|
|
37
|
+
puts run.response
|
|
38
|
+
else
|
|
39
|
+
warn "Support request ended as #{run.outcome}: #{run.error&.class}"
|
|
40
|
+
end
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Run the file and you have a working AI feature:
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
$ ruby customer_support_agent.rb
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`CustomerSupportAgent.ask` creates a `LittleGhost::Run` for this request. When the work finishes, the Run holds the outcome and response.
|
|
50
|
+
|
|
51
|
+
The inline prompt keeps this first example visible in one place. When the instructions grow, [Prompts as Views](prompt_views.md) moves them into a conventional ERB file without adding setup to the Agent.
|
|
52
|
+
|
|
53
|
+
The selected external provider may receive system instructions, caller input, conversation history, tool results, and attachments. Model wording can vary, so use application code—not a prompt—when a rule must always hold.
|
|
54
|
+
|
|
55
|
+
## Connect the agent to your application
|
|
56
|
+
|
|
57
|
+
The first agent can answer general questions. A **tool** gives it a focused operation backed by your Ruby code:
|
|
58
|
+
|
|
59
|
+
```ruby
|
|
31
60
|
class HelpCenterLookupTool < LittleGhost::Tool
|
|
32
61
|
HELP_CENTER_ENTRIES = {
|
|
33
62
|
"refunds" => "Refunds are available within 30 days of purchase.",
|
|
@@ -50,119 +79,128 @@ class HelpCenterLookupTool < LittleGhost::Tool
|
|
|
50
79
|
end
|
|
51
80
|
```
|
|
52
81
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
The schema checks shape, not authorization. A tool that reads customer data or performs an action must enforce the application's trust rules inside its implementation.
|
|
56
|
-
|
|
57
|
-
## Define the agent
|
|
58
|
-
|
|
59
|
-
Now describe the agent's behavior and make the lookup available to it:
|
|
82
|
+
Make the tool available to the agent and tell the model when to use it:
|
|
60
83
|
|
|
61
84
|
```ruby
|
|
62
85
|
class CustomerSupportAgent < LittleGhost::Agent
|
|
63
86
|
description "Answers customer support questions."
|
|
64
|
-
model "openai
|
|
87
|
+
model "openrouter:openai/gpt-5.6-luna"
|
|
65
88
|
system_prompt <<~PROMPT
|
|
66
89
|
Answer clearly and do not invent company guidance.
|
|
67
|
-
|
|
90
|
+
Check the help center before stating company guidance.
|
|
68
91
|
PROMPT
|
|
69
|
-
|
|
70
92
|
tools HelpCenterLookupTool
|
|
71
93
|
end
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
An agent class is a reusable behavior definition. It owns its prompt, tools, model selection, limits, and other capabilities. This agent names an OpenAI connection and model directly, so the first example does not need a separate model profile.
|
|
75
94
|
|
|
76
|
-
## Ask a question
|
|
77
|
-
|
|
78
|
-
Call the agent class to run one request to completion:
|
|
79
|
-
|
|
80
|
-
```ruby
|
|
81
95
|
run = CustomerSupportAgent.ask(
|
|
82
96
|
"I bought an item two weeks ago. Can I get a refund?"
|
|
83
97
|
)
|
|
84
98
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
warn "Support request ended as #{run.outcome}: #{run.error&.class}"
|
|
89
|
-
end
|
|
99
|
+
run.response
|
|
100
|
+
# One possible response:
|
|
101
|
+
# Refunds are available within 30 days, so your purchase is eligible.
|
|
90
102
|
```
|
|
91
103
|
|
|
92
|
-
|
|
104
|
+
LittleGhost checks the model's arguments before it calls
|
|
105
|
+
`HelpCenterLookupTool#call`. The Tool's result then becomes context for the
|
|
106
|
+
model.
|
|
93
107
|
|
|
94
|
-
|
|
108
|
+
### Use application context for private data
|
|
95
109
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
110
|
+
The schema checks shape, not permission. When a Tool reads private data or
|
|
111
|
+
changes something, use identity and account information established by your
|
|
112
|
+
application rather than asking the model to supply it.
|
|
99
113
|
|
|
100
|
-
|
|
114
|
+
While an Agent is working, LittleGhost binds each Tool instance to the current
|
|
115
|
+
Run. The Tool can read request values through its `run` accessor:
|
|
101
116
|
|
|
102
|
-
|
|
117
|
+
```ruby
|
|
118
|
+
class OrderStatusTool < LittleGhost::Tool
|
|
119
|
+
ORDER_STATUSES = {
|
|
120
|
+
["user-7", "account-2", "481"] => "out for delivery"
|
|
121
|
+
}.freeze
|
|
103
122
|
|
|
104
|
-
|
|
123
|
+
description "Look up an order that belongs to the current customer."
|
|
124
|
+
input_schema(
|
|
125
|
+
type: "object",
|
|
126
|
+
properties: {order_number: {type: "string"}},
|
|
127
|
+
required: ["order_number"],
|
|
128
|
+
additionalProperties: false
|
|
129
|
+
)
|
|
105
130
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
131
|
+
def call(input)
|
|
132
|
+
lookup = [
|
|
133
|
+
run.invocation.actor_id,
|
|
134
|
+
run.invocation.context.fetch("account_id"),
|
|
135
|
+
input.fetch("order_number")
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
ORDER_STATUSES.fetch(lookup) do
|
|
139
|
+
raise LittleGhost::ToolError, "Order not found"
|
|
140
|
+
end
|
|
113
141
|
end
|
|
114
142
|
end
|
|
115
|
-
```
|
|
116
143
|
|
|
117
|
-
|
|
144
|
+
class CustomerSupportAgent < LittleGhost::Agent
|
|
145
|
+
tools HelpCenterLookupTool, OrderStatusTool
|
|
146
|
+
end
|
|
118
147
|
|
|
119
|
-
|
|
148
|
+
run = CustomerSupportAgent.ask(
|
|
149
|
+
"Where is order 481?",
|
|
150
|
+
actor_id: "user-7",
|
|
151
|
+
context: {account_id: "account-2"}
|
|
152
|
+
)
|
|
153
|
+
```
|
|
120
154
|
|
|
121
|
-
|
|
155
|
+
Here, `order_number` came from the model. The application supplied `actor_id`
|
|
156
|
+
and `account_id` after authenticating the caller. LittleGhost places those
|
|
157
|
+
request values on `run.invocation`; context keys become strings.
|
|
122
158
|
|
|
123
|
-
|
|
159
|
+
> **Safety note:** Treat model-selected Tool arguments like any other external
|
|
160
|
+
> input. Check permission using the current user and account before returning
|
|
161
|
+
> private data or performing a write.
|
|
124
162
|
|
|
125
|
-
|
|
126
|
-
LittleGhost.configure do |config|
|
|
127
|
-
config.providers = {
|
|
128
|
-
openai: {adapter: :openai, api_key: ENV.fetch("OPENAI_API_KEY")}
|
|
129
|
-
}
|
|
130
|
-
config.models = {
|
|
131
|
-
customer_support: {
|
|
132
|
-
target: "openai:gpt-5.6-luna",
|
|
133
|
-
settings: {temperature: 0.2}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
config.default_model = :customer_support
|
|
137
|
-
end
|
|
163
|
+
That is enough to authorize the first Tool safely. [Core Concepts](core_concepts.md) names the request and working-state objects behind `run`, and [Running in Production](production.md) explains what changes when you add saved conversations.
|
|
138
164
|
|
|
139
|
-
|
|
140
|
-
model :customer_support
|
|
141
|
-
end
|
|
142
|
-
```
|
|
165
|
+
## Stream the same agent
|
|
143
166
|
|
|
144
|
-
|
|
167
|
+
Use `.stream_ask` when a console, HTTP response, or user interface should receive progress as it happens:
|
|
145
168
|
|
|
146
169
|
```ruby
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
170
|
+
stream = CustomerSupportAgent.stream_ask("Can I get a refund?")
|
|
171
|
+
|
|
172
|
+
run = stream.each do |event|
|
|
173
|
+
case event.type
|
|
174
|
+
when :text_delta
|
|
175
|
+
print event.data.fetch(:text)
|
|
176
|
+
when :run_error
|
|
177
|
+
warn event.data.fetch(:message)
|
|
178
|
+
end
|
|
153
179
|
end
|
|
154
|
-
```
|
|
155
180
|
|
|
156
|
-
|
|
181
|
+
puts "\n#{run.response}" if run.completed?
|
|
182
|
+
warn run.error.class.name if run.failed?
|
|
183
|
+
```
|
|
157
184
|
|
|
158
|
-
|
|
185
|
+
The stream yields `LittleGhost::StreamEvent` values. Text, tool activity, usage, and completion all look the same across providers. When enumeration finishes, `.each` returns the same `LittleGhost::Run` that now holds the final outcome and response.
|
|
159
186
|
|
|
160
|
-
|
|
187
|
+
## Give the code a home
|
|
161
188
|
|
|
162
|
-
|
|
189
|
+
LittleGhost does not require an application layout. Keep definitions beside related application code, or use these optional conventions:
|
|
163
190
|
|
|
164
|
-
|
|
191
|
+
```text
|
|
192
|
+
app/
|
|
193
|
+
├── agents/
|
|
194
|
+
│ └── customer_support_agent.rb
|
|
195
|
+
├── assemblies/
|
|
196
|
+
│ └── response_workflow.rb
|
|
197
|
+
├── prompts/
|
|
198
|
+
│ └── customer_support/
|
|
199
|
+
│ └── system.erb
|
|
200
|
+
└── tools/
|
|
201
|
+
└── help_center_lookup_tool.rb
|
|
202
|
+
```
|
|
165
203
|
|
|
166
|
-
|
|
204
|
+
You now have the smallest useful LittleGhost application: one Agent, one Tool, and one familiar Ruby call.
|
|
167
205
|
|
|
168
|
-
|
|
206
|
+
When the feature grows, the calling style stays the same. An **assembly** lets one or more agents work as a unit while keeping `.ask` and `.stream_ask`. Read [Core Concepts](core_concepts.md) next and grow this Agent into a larger system.
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# Connect MCP, AG-UI, and OpenTelemetry
|
|
2
|
+
|
|
3
|
+
LittleGhost can load Tools from an MCP server, translate a Run stream for an
|
|
4
|
+
interactive interface, and publish traces. Each integration uses the same
|
|
5
|
+
Agents and Runs you already have.
|
|
6
|
+
|
|
7
|
+
## Load Tools from an MCP server
|
|
8
|
+
|
|
9
|
+
An MCP Toolset connects to one server and turns its published operations into
|
|
10
|
+
LittleGhost Tool classes. Add the Toolset through the same Agent `tools`
|
|
11
|
+
declaration used for local Tools:
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
require "little_ghost/mcp"
|
|
15
|
+
|
|
16
|
+
class HelpCenterTools < LittleGhost::MCP::Toolset
|
|
17
|
+
connection url: "https://mcp.example/rpc", timeout: 20
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class CustomerSupportAgent < LittleGhost::Agent
|
|
21
|
+
system_prompt "Use help-center tools for published guidance."
|
|
22
|
+
tools HelpCenterTools
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
run = CustomerSupportAgent.ask("How long do refunds take?")
|
|
26
|
+
run.response
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`connection` requires `url` and also accepts `headers`, `timeout`, `signer`,
|
|
30
|
+
`allow_insecure_http`, and `max_response_bytes`. Pass a block when credentials
|
|
31
|
+
depend on the current Agent run:
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
connection do |binding|
|
|
35
|
+
token = McpAccessTokens.for_actor(binding.run.invocation.actor_id)
|
|
36
|
+
{
|
|
37
|
+
url: "https://mcp.example/rpc",
|
|
38
|
+
headers: {"Authorization" => "Bearer #{token}"},
|
|
39
|
+
timeout: 20
|
|
40
|
+
}
|
|
41
|
+
end
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The block's `binding` gives it access to the current Run. LittleGhost evaluates
|
|
45
|
+
the block before opening the MCP session, so each Agent run can use credentials
|
|
46
|
+
for its authenticated caller.
|
|
47
|
+
|
|
48
|
+
By default, the Agent receives every operation published by the server. Their
|
|
49
|
+
normalized server names, such as `search` and `fetch`, become Tool names.
|
|
50
|
+
|
|
51
|
+
Use `map_tool` when the Agent should receive only part of the server catalog or
|
|
52
|
+
when a generated Tool needs a different name or configuration:
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
class CuratedHelpCenterTools < LittleGhost::MCP::Toolset
|
|
56
|
+
connection url: "https://mcp.example/rpc", timeout: 20
|
|
57
|
+
|
|
58
|
+
map_tool do |tool_class, definition:, binding:|
|
|
59
|
+
next unless %w[search fetch].include?(definition.source_name)
|
|
60
|
+
|
|
61
|
+
tool_class.tool_name "help_center_#{definition.source_name}"
|
|
62
|
+
tool_class
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`definition` describes the operation published by the server, and `binding`
|
|
68
|
+
identifies the current Agent run. Return the class after configuring it, or
|
|
69
|
+
return `nil` to omit the operation. Renaming a generated Tool does not change
|
|
70
|
+
the original `Definition#source_name` sent back to the server.
|
|
71
|
+
|
|
72
|
+
The Agent can call the generated Tools like local Tools. LittleGhost uses one
|
|
73
|
+
local client and transport for the Toolset during the Agent run. The built-in
|
|
74
|
+
HTTP transport does not send an MCP session-termination request. Configure
|
|
75
|
+
server-side expiry, or arrange explicit remote cleanup when the server requires
|
|
76
|
+
it.
|
|
77
|
+
|
|
78
|
+
Most MCP results need no mapping. LittleGhost returns `structuredContent` as a
|
|
79
|
+
Ruby Hash when present, otherwise it returns the server's text. Server images
|
|
80
|
+
become Artifacts.
|
|
81
|
+
|
|
82
|
+
Use `map_result` when one operation needs application-specific conversion. This
|
|
83
|
+
example turns the server's download identifier into a deferred Artifact:
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
map_result do |result, call:, binding:|
|
|
87
|
+
next result unless call.definition.source_name == "export"
|
|
88
|
+
|
|
89
|
+
LittleGhost::Tool::Result.new(
|
|
90
|
+
value: result.structured_content,
|
|
91
|
+
artifacts: [
|
|
92
|
+
LittleGhost::Artifact.deferred(
|
|
93
|
+
reference: result.metadata.fetch("download_id"),
|
|
94
|
+
media_type: "application/octet-stream"
|
|
95
|
+
)
|
|
96
|
+
]
|
|
97
|
+
)
|
|
98
|
+
end
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`map_result` receives the complete `MCP::Result`, the `MCP::Call` that produced
|
|
102
|
+
it, and the current binding. Return any Ruby value or `Tool::Result`. Returning
|
|
103
|
+
the supplied result unchanged keeps the default conversion described above.
|
|
104
|
+
MCP images and local Tool artifacts use the same storage and presentation
|
|
105
|
+
rules when `Configuration#artifacts` is enabled. Images and documents are sent
|
|
106
|
+
as model content; their stored references are fallback information rather than
|
|
107
|
+
a second representation. LittleGhost also checks results against
|
|
108
|
+
server-advertised JSON Schema Draft 2020-12 output schemas.
|
|
109
|
+
|
|
110
|
+
An optional server can fail discovery without preventing Agent construction:
|
|
111
|
+
|
|
112
|
+
```ruby
|
|
113
|
+
class HelpCenterTools < LittleGhost::MCP::Toolset
|
|
114
|
+
connection { |binding| McpConnections.help_center(binding) }
|
|
115
|
+
optional true
|
|
116
|
+
on_error do |error, binding:|
|
|
117
|
+
McpAvailability.report(error, run_id: binding.run.invocation.run_id)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
`optional true` converts expected provider and protocol discovery failures
|
|
123
|
+
into an empty Tool set. `on_error` observes only those caught failures.
|
|
124
|
+
Cancellation, deadlines, configuration errors, and application callback
|
|
125
|
+
failures still propagate.
|
|
126
|
+
|
|
127
|
+
LittleGhost limits the number and total size of discovered operations, the
|
|
128
|
+
complexity of their schemas, and the size and number of returned images.
|
|
129
|
+
`HTTPTransport` also limits each HTTP response and requires HTTPS unless local
|
|
130
|
+
HTTP is explicitly enabled.
|
|
131
|
+
|
|
132
|
+
> **Safety note:** An MCP server supplies descriptions and results that the model
|
|
133
|
+
> can see. Structural validation does not make that content trustworthy or
|
|
134
|
+
> authorize an operation it suggests. Expose only the operations the Agent
|
|
135
|
+
> needs, use narrowly scoped credentials, and have the server authorize every
|
|
136
|
+
> sensitive call. If a result becomes a deferred Artifact, its resolver must
|
|
137
|
+
> verify that the referenced file belongs to the authenticated caller, fetch
|
|
138
|
+
> only from an intended service, and limit the response size before returning
|
|
139
|
+
> bytes to LittleGhost.
|
|
140
|
+
|
|
141
|
+
LittleGhost implements its documented client behavior for the [MCP 2025-06-18
|
|
142
|
+
specification](https://modelcontextprotocol.io/specification/2025-06-18).
|
|
143
|
+
|
|
144
|
+
Use `LittleGhost::MCP::HTTPTransport` and `LittleGhost::MCP::Client` directly
|
|
145
|
+
when you need a custom transport. They produce the same generated Tool classes
|
|
146
|
+
and accept the same mapping callbacks as Toolset.
|
|
147
|
+
|
|
148
|
+
## Send a Run stream through AG-UI
|
|
149
|
+
|
|
150
|
+
The AG-UI adapter converts LittleGhost events into protocol event hashes:
|
|
151
|
+
|
|
152
|
+
```ruby
|
|
153
|
+
require "json"
|
|
154
|
+
require "little_ghost/ag_ui"
|
|
155
|
+
|
|
156
|
+
source = CustomerSupportAgent.stream_ask(
|
|
157
|
+
question,
|
|
158
|
+
actor_id: authenticated_user.id,
|
|
159
|
+
context: {account_id: authenticated_user.account_id}
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
events = LittleGhost::AGUI::Adapter.new.stream(
|
|
163
|
+
source,
|
|
164
|
+
thread_id: conversation.id,
|
|
165
|
+
run_id: request.request_id
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
events.each { |event| websocket.write(JSON.generate(event)) }
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
The adapter translates text, reasoning, Tool activity, usage, retries, trace
|
|
172
|
+
context, subagent activity, and terminal outcomes. It is stateless between
|
|
173
|
+
calls. Your application still owns the connection, thread storage,
|
|
174
|
+
backpressure, and disconnect behavior.
|
|
175
|
+
|
|
176
|
+
LittleGhost also emits namespaced custom events. Consumers should preserve or
|
|
177
|
+
deliberately ignore event types they don't recognize. See the [AG-UI event
|
|
178
|
+
documentation](https://docs.ag-ui.com/concepts/events) when implementing the
|
|
179
|
+
client.
|
|
180
|
+
|
|
181
|
+
> **Safety note:** A Run stream can include model output, Tool arguments and
|
|
182
|
+
> results, errors, and participant activity. Check that the connected user may
|
|
183
|
+
> see the complete Run, then filter fields before sending or storing events.
|
|
184
|
+
|
|
185
|
+
Enumeration drives the source stream on the current thread. When a client
|
|
186
|
+
disconnects, stop enumerating and apply the cancellation behavior your
|
|
187
|
+
application needs. Closing the socket can't undo Tool work that already ran.
|
|
188
|
+
|
|
189
|
+
## Trace Runs with OpenTelemetry
|
|
190
|
+
|
|
191
|
+
Configure an OpenTelemetry SDK and exporter in the application, then register
|
|
192
|
+
the LittleGhost subscriber before the first Agent call:
|
|
193
|
+
|
|
194
|
+
```ruby
|
|
195
|
+
LittleGhost.configure do |config|
|
|
196
|
+
config.instrument LittleGhost::Tracing::OpenTelemetry.new
|
|
197
|
+
end
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
LittleGhost depends on `opentelemetry-api`, leaving the SDK, processor, and
|
|
201
|
+
exporter up to the application. It emits spans and events for Runs, Agents,
|
|
202
|
+
model calls, Tools, assemblies, sessions, usage, and failures. Active operations
|
|
203
|
+
can propagate W3C `traceparent` and `tracestate` fields.
|
|
204
|
+
|
|
205
|
+
Prompts, messages, responses, Tool arguments, and exception content are omitted
|
|
206
|
+
by default. If you intentionally need some of that content, install a
|
|
207
|
+
`LittleGhost::Support::ContentCapture` with a scrubber before enabling capture.
|
|
208
|
+
Avoid putting raw user, order, session, or request IDs in span attributes.
|
|
209
|
+
|
|
210
|
+
Attribute names follow the evolving [OpenTelemetry GenAI semantic
|
|
211
|
+
conventions](https://opentelemetry.io/docs/specs/semconv/gen-ai/) where they
|
|
212
|
+
apply. Flush or shut down `LittleGhost::Instrumentation` during application
|
|
213
|
+
shutdown when your backend buffers data.
|
|
214
|
+
|
|
215
|
+
See [Running in Production](production.md) for startup, shutdown, and observability,
|
|
216
|
+
[Tools](tools.md) for local and remote Tool behavior, and [Workspaces and
|
|
217
|
+
Sandboxes](sandboxing.md) for child processes and files.
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Choose Models and Providers
|
|
2
|
+
|
|
3
|
+
An Agent needs a model target: a configured provider connection plus the
|
|
4
|
+
provider's model identifier. You can write that target directly while getting
|
|
5
|
+
started, then give it an application-facing name when several Agents share it.
|
|
6
|
+
|
|
7
|
+
## Start with one direct target
|
|
8
|
+
|
|
9
|
+
A direct target has the form `connection:model-id`:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
class CustomerSupportAgent < LittleGhost::Agent
|
|
13
|
+
model "openrouter:openai/gpt-5.6-luna"
|
|
14
|
+
system_prompt "Answer customer questions clearly and concisely."
|
|
15
|
+
end
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`openrouter` names a connection configured by the application. The remainder
|
|
19
|
+
is the model identifier understood by that provider. This is a good fit when
|
|
20
|
+
one Agent owns one stable choice.
|
|
21
|
+
|
|
22
|
+
## Give shared choices a role
|
|
23
|
+
|
|
24
|
+
A model role lets several Agents share a choice without knowing its provider
|
|
25
|
+
or model identifier:
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
LittleGhost.configure do |config|
|
|
29
|
+
config.providers = {
|
|
30
|
+
primary: {
|
|
31
|
+
adapter: :openrouter,
|
|
32
|
+
api_key: ENV.fetch("OPENROUTER_API_KEY")
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
config.models = {
|
|
36
|
+
customer_support: {
|
|
37
|
+
target: "primary:openai/gpt-5.6-luna",
|
|
38
|
+
settings: {temperature: 0.2}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
config.default_model = :customer_support
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
class CustomerSupportAgent < LittleGhost::Agent
|
|
45
|
+
model :customer_support
|
|
46
|
+
end
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Here `customer_support` is the role, `primary` is the connection, and
|
|
50
|
+
`openrouter` is the adapter. You can move the role to another model or provider
|
|
51
|
+
without editing the Agent.
|
|
52
|
+
|
|
53
|
+
Profile settings are defaults. An individual call can override them:
|
|
54
|
+
|
|
55
|
+
```ruby
|
|
56
|
+
run = CustomerSupportAgent.ask(
|
|
57
|
+
"Explain the refund decision.",
|
|
58
|
+
settings: {temperature: 0.0}
|
|
59
|
+
)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Build these settings in application code instead of passing request parameters
|
|
63
|
+
through unchanged. Settings can affect cost, latency, and model behavior.
|
|
64
|
+
|
|
65
|
+
## Configure connections in one place
|
|
66
|
+
|
|
67
|
+
LittleGhost includes adapters for OpenRouter, OpenAI-compatible APIs,
|
|
68
|
+
Anthropic, Gemini, Vertex AI, and Bedrock. Connections may live in an
|
|
69
|
+
initializer or in the conventional files under `config/little_ghost`.
|
|
70
|
+
|
|
71
|
+
Keep credentials in your application's secret manager. Agents refer to a role
|
|
72
|
+
or configured connection; they don't need to contain credentials. If your
|
|
73
|
+
application obtains short-lived credentials at runtime, configure a credential
|
|
74
|
+
resolver that returns them for the selected connection.
|
|
75
|
+
|
|
76
|
+
> **Safety note:** The selected provider may receive system instructions,
|
|
77
|
+
> caller input, conversation history, Tool results, schemas, and attachments.
|
|
78
|
+
> Choose a provider that is appropriate for that data, and keep credentials and
|
|
79
|
+
> provider endpoints under application control.
|
|
80
|
+
|
|
81
|
+
## Choose a role for each request
|
|
82
|
+
|
|
83
|
+
An Agent can select between configured roles using its `Invocation`:
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
class CustomerSupportAgent < LittleGhost::Agent
|
|
87
|
+
model do |invocation|
|
|
88
|
+
invocation.fetch(:premium_account, false) ? :premium_support : :customer_support
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Set `premium_account` from application state when creating the invocation. If
|
|
94
|
+
a public request offers a model choice, map that choice to one of your
|
|
95
|
+
configured roles rather than accepting an arbitrary provider target.
|
|
96
|
+
|
|
97
|
+
Trusted application code may also declare a selection inline:
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
class ResearchAgent < LittleGhost::Agent
|
|
101
|
+
model(
|
|
102
|
+
provider: "primary",
|
|
103
|
+
model: "openai/gpt-5.6-luna",
|
|
104
|
+
reasoning_effort: "high"
|
|
105
|
+
)
|
|
106
|
+
end
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`provider` still names a configured connection. The inline settings change the
|
|
110
|
+
selection; they don't create a new connection.
|
|
111
|
+
|
|
112
|
+
## Use model capabilities
|
|
113
|
+
|
|
114
|
+
`LittleGhost::ModelResolver` turns a role or target into an executable
|
|
115
|
+
`LittleGhost::Model`. Its catalog describes capabilities such as supported
|
|
116
|
+
input types, output limits, and structured results. LittleGhost uses that
|
|
117
|
+
information to reject unsupported attachments, constrain output limits, and
|
|
118
|
+
choose a structured-result strategy.
|
|
119
|
+
|
|
120
|
+
Provider capabilities can change. Handle failed Runs and provider errors even
|
|
121
|
+
when the catalog says a feature is supported.
|
|
122
|
+
|
|
123
|
+
Continue with [Prompts as Views](prompt_views.md) when an Agent's instructions
|
|
124
|
+
outgrow one string. See [Structured Results and Content](structured_outputs_and_content.md)
|
|
125
|
+
when you need checked result shapes, images, or documents.
|