scout-ai 1.2.3 → 2.0.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/.vimproject +138 -50
- data/README.md +171 -290
- data/Rakefile +17 -1
- data/VERSION +1 -1
- data/doc/Improvements.md +325 -0
- data/doc/StartHere.md +110 -0
- data/doc/developer/Architecture.md +126 -0
- data/doc/developer/Backends.md +199 -0
- data/doc/developer/ChatLifecycle.md +183 -0
- data/doc/developer/DelegationInternals.md +295 -0
- data/doc/developer/DesignPrinciples.md +245 -0
- data/doc/developer/PromptProcessing.md +292 -0
- data/doc/developer/Provenance.md +317 -0
- data/doc/user/BuildingAgents.md +345 -0
- data/doc/user/Cookbook.md +333 -0
- data/doc/user/CoreConcepts.md +181 -0
- data/doc/user/Delegation.md +191 -0
- data/doc/user/GettingStarted.md +159 -0
- data/doc/user/ManagingContext.md +163 -0
- data/doc/user/MultiAgentWorkflows.md +256 -0
- data/doc/user/Python.md +159 -0
- data/doc/user/RunningInference.md +200 -0
- data/doc/user/ToolCalling.md +193 -0
- data/doc/user/WritingChats.md +197 -0
- data/lib/scout/llm/agent/chat.rb +61 -11
- data/lib/scout/llm/agent/delegate.rb +274 -65
- data/lib/scout/llm/agent/iterate.rb +2 -2
- data/lib/scout/llm/agent/save.rb +273 -0
- data/lib/scout/llm/agent/workflow.rb +164 -0
- data/lib/scout/llm/agent.rb +86 -61
- data/lib/scout/llm/ask.rb +62 -17
- data/lib/scout/llm/backends/anthropic.rb +9 -2
- data/lib/scout/llm/backends/bedrock.rb +15 -3
- data/lib/scout/llm/backends/default.rb +183 -99
- data/lib/scout/llm/backends/glm.rb +58 -0
- data/lib/scout/llm/backends/huggingface.rb +196 -26
- data/lib/scout/llm/backends/ollama.rb +13 -1
- data/lib/scout/llm/backends/openai.rb +0 -2
- data/lib/scout/llm/backends/openwebui.rb +20 -13
- data/lib/scout/llm/backends/relay.rb +22 -22
- data/lib/scout/llm/backends/responses.rb +1 -1
- data/lib/scout/llm/chat/agent_meta.rb +264 -0
- data/lib/scout/llm/chat/annotation.rb +39 -10
- data/lib/scout/llm/chat/parse.rb +28 -6
- data/lib/scout/llm/chat/persist.rb +25 -0
- data/lib/scout/llm/chat/process/clear.rb +41 -6
- data/lib/scout/llm/chat/process/files.rb +21 -6
- data/lib/scout/llm/chat/process/meta.rb +421 -34
- data/lib/scout/llm/chat/process/options.rb +21 -1
- data/lib/scout/llm/chat/process/tools.rb +56 -15
- data/lib/scout/llm/chat/process.rb +4 -0
- data/lib/scout/llm/chat/prompt/shorten_tools.rb +125 -0
- data/lib/scout/llm/chat/prompt/shorten_tools_epoch.rb +365 -0
- data/lib/scout/llm/chat/prompt.rb +48 -0
- data/lib/scout/llm/chat/provenance.rb +775 -0
- data/lib/scout/llm/chat/tool_calls.rb +76 -0
- data/lib/scout/llm/chat.rb +18 -2
- data/lib/scout/llm/embed.rb +11 -3
- data/lib/scout/llm/image.rb +86 -0
- data/lib/scout/llm/mcp.rb +10 -2
- data/lib/scout/llm/rag.rb +3 -3
- data/lib/scout/llm/tools/call.rb +160 -11
- data/lib/scout/llm/tools/knowledge_base.rb +1 -1
- data/lib/scout/llm/tools/workflow.rb +32 -16
- data/lib/scout/model/python/huggingface/causal.rb +23 -5
- data/lib/scout/model/python/huggingface.rb +2 -1
- data/lib/scout-ai.rb +1 -0
- data/python/README.md +197 -14
- data/python/scout_ai/huggingface/eval.py +245 -34
- data/python/tests/test_huggingface_eval.py +58 -0
- data/research/ChatAnalyst-required-changes.md +167 -0
- data/research/agent-delegation-analysis.md +810 -0
- data/research/agent-meta-provenance-integration-plan.md +622 -0
- data/research/agent-workflow-analysis.md +1120 -0
- data/research/backends-analysis.md +836 -0
- data/research/chat-core-analysis.md +946 -0
- data/research/chatanalyst-provenance/00-baseline.md +30 -0
- data/research/chatanalyst-provenance/01-repo-map.md +60 -0
- data/research/chatanalyst-provenance/02-event-reconstruction.md +55 -0
- data/research/chatanalyst-provenance/03-duplication-evidence.md +45 -0
- data/research/chatanalyst-provenance/04-tooling-root-cause.md +57 -0
- data/research/chatanalyst-provenance/05-fix-plan.md +46 -0
- data/research/chatanalyst-provenance/07-critic-review.md +25 -0
- data/research/chatanalyst-provenance/final-report.md +45 -0
- data/research/chatanalyst-provenance/resumption.md +37 -0
- data/research/coding-philosophy-analysis.md +928 -0
- data/research/commands-analysis.md +947 -0
- data/research/multi-agent-patterns-analysis.md +853 -0
- data/research/prompt-strategies-analysis.md +630 -0
- data/research/prov-verbosity-fix-notes.md +77 -0
- data/research/provenance-analysis.md +469 -0
- data/research/provenance-navigation-design.md +640 -0
- data/research/synthesis-report.md +487 -0
- data/research/tools-system-analysis.md +779 -0
- data/scout-ai.gemspec +100 -11
- data/scout_commands/agent/ask +13 -3
- data/scout_commands/agent/kb +2 -0
- data/scout_commands/llm/ask +11 -4
- data/scout_commands/llm/md +76 -0
- data/scout_commands/llm/process_queries +48 -0
- data/scout_commands/llm/prov +602 -0
- data/scout_commands/llm/word +71 -0
- data/scout_commands/workflow/mcp +43 -0
- data/share/word/reference.docx +0 -0
- data/test/etc/AI/mock.yaml +11 -0
- data/test/fixtures/backends/anthropic.json +19 -0
- data/test/fixtures/backends/anthropic_tool_use.json +24 -0
- data/test/fixtures/backends/bedrock.json +8 -0
- data/test/fixtures/backends/bedrock_embedding.json +3 -0
- data/test/fixtures/backends/bedrock_tool_use.json +17 -0
- data/test/fixtures/backends/ollama.json +16 -0
- data/test/fixtures/backends/ollama_tool_call.json +27 -0
- data/test/fixtures/backends/openai_chat.json +21 -0
- data/test/fixtures/backends/openai_chat_tool_call.json +31 -0
- data/test/fixtures/backends/responses.json +33 -0
- data/test/fixtures/backends/responses_tool_call.json +28 -0
- data/test/integration/README.md +32 -0
- data/test/integration/scout/llm/backends/test_endpoints.rb +34 -0
- data/test/integration/scout/llm/backends/test_openwebui.rb +61 -0
- data/test/integration/scout/llm/backends/test_relay.rb +52 -0
- data/test/integration/scout/llm/test_infrastructure.rb +74 -0
- data/test/{scout → integration/scout}/llm/test_mcp.rb +1 -1
- data/test/integration/scout/llm/tools/test_mcp.rb +42 -0
- data/test/integration/scout/model/test_base.rb +91 -0
- data/test/scout/llm/agent/test_chat.rb +8 -2
- data/test/scout/llm/agent/test_save.rb +413 -0
- data/test/scout/llm/agent/test_workflow.rb +110 -0
- data/test/scout/llm/backends/test_anthropic.rb +93 -10
- data/test/scout/llm/backends/test_bedrock.rb +118 -2
- data/test/scout/llm/backends/test_huggingface.rb +137 -42
- data/test/scout/llm/backends/test_ollama.rb +70 -20
- data/test/scout/llm/backends/test_openwebui.rb +42 -40
- data/test/scout/llm/backends/test_relay.rb +4 -2
- data/test/scout/llm/chat/agent_meta_fixtures.rb +131 -0
- data/test/scout/llm/chat/process/test_meta.rb +518 -0
- data/test/scout/llm/chat/process/test_normalize_usage.rb +183 -0
- data/test/scout/llm/chat/test_agent_meta.rb +357 -0
- data/test/scout/llm/chat/test_agent_meta_provenance.rb +467 -0
- data/test/scout/llm/chat/test_agent_meta_tokens.rb +594 -0
- data/test/scout/llm/chat/test_parse.rb +70 -15
- data/test/scout/llm/chat/test_prov_cli.rb +274 -0
- data/test/scout/llm/chat/test_provenance.rb +240 -0
- data/test/scout/llm/chat/test_tool_calls.rb +38 -0
- data/test/scout/llm/test_agent.rb +13 -36
- data/test/scout/llm/test_ask.rb +75 -52
- data/test/scout/llm/test_chat.rb +107 -13
- data/test/scout/llm/test_embed.rb +48 -0
- data/test/scout/llm/test_rag.rb +23 -16
- data/test/scout/llm/test_tools.rb +12 -1
- data/test/scout/llm/tools/test_knowledge_base.rb +0 -1
- data/test/scout/llm/tools/test_mcp.rb +5 -3
- data/test/scout/llm/tools/test_workflow.rb +23 -2
- data/test/scout/model/python/huggingface/causal/test_next_token.rb +11 -5
- data/test/scout/model/python/huggingface/test_causal.rb +9 -3
- data/test/scout/model/python/huggingface/test_classification.rb +11 -2
- data/test/scout/model/python/test_torch.rb +2 -0
- data/test/scout/model/python/torch/test_helpers.rb +4 -0
- data/test/scout/model/test_base.rb +4 -2
- data/test/support/availability.rb +231 -0
- data/test/support/fake_clients.rb +138 -0
- data/test/support/fixtures.rb +21 -0
- data/test/support/infrastructure_probes.rb +136 -0
- data/test/support/mock_backend.rb +215 -0
- data/test/test_helper.rb +32 -2
- metadata +99 -10
- data/doc/Agent.md +0 -327
- data/doc/Chat.md +0 -458
- data/doc/LLM.md +0 -340
- data/doc/RAG.md +0 -129
- data/scout_commands/documenter +0 -148
- data/test/scout/llm/backends/test_openai.rb +0 -192
- data/test/scout/llm/backends/test_responses.rb +0 -238
- data/test/scout/llm/test_parse.rb +0 -98
data/doc/LLM.md
DELETED
|
@@ -1,340 +0,0 @@
|
|
|
1
|
-
# LLM
|
|
2
|
-
|
|
3
|
-
This package provides a compact layer to talk to multiple LLM backends while keeping:
|
|
4
|
-
|
|
5
|
-
- **configuration reproducible** (named endpoints)
|
|
6
|
-
- **prompts reproducible** (chat files)
|
|
7
|
-
- **tool calling first-class** (Workflow tasks, KnowledgeBase databases, MCP tools)
|
|
8
|
-
|
|
9
|
-
The core entry points are:
|
|
10
|
-
|
|
11
|
-
- `LLM.ask(question, options={}, &block)` — ask a model (optionally with tools)
|
|
12
|
-
- `LLM.chat(...)` — compile/expand a chat file or message array into final messages
|
|
13
|
-
- `LLM.options(messages)` — extract per-chat options (`endpoint`, `model`, `format`, …)
|
|
14
|
-
- `LLM.print(messages)` — serialize messages back to chat-file format
|
|
15
|
-
- `LLM.embed(text, options={})` — embeddings (not the focus of this document)
|
|
16
|
-
|
|
17
|
-
For stateful multi-turn conversations, see `doc/Agent.md`.
|
|
18
|
-
|
|
19
|
-
For the full chat-file role reference, see `doc/Chat.md`.
|
|
20
|
-
|
|
21
|
-
---
|
|
22
|
-
|
|
23
|
-
## 1. Endpoints: configuring inference points (`~/.scout/etc/AI/<endpoint>`)
|
|
24
|
-
|
|
25
|
-
Scout-AI uses **named endpoints** so colleagues can agree on short names like `nano` or `deep`.
|
|
26
|
-
|
|
27
|
-
At runtime, `LLM.ask(..., endpoint: :nano)` will load:
|
|
28
|
-
|
|
29
|
-
```text
|
|
30
|
-
~/.scout/etc/AI/nano
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
This file is YAML.
|
|
34
|
-
|
|
35
|
-
Minimum useful keys:
|
|
36
|
-
|
|
37
|
-
- `backend`: which backend to use (`responses`, `openai`, `anthropic`, `ollama`, `vllm`, `openwebui`, `bedrock`, `relay`)
|
|
38
|
-
- `url`: server URL (only for backends that need one, e.g. `ollama`, `openwebui`)
|
|
39
|
-
- `model`: backend-specific model id
|
|
40
|
-
|
|
41
|
-
Any **additional keys are passed to the backend** and may be interpreted there.
|
|
42
|
-
|
|
43
|
-
Examples:
|
|
44
|
-
|
|
45
|
-
`~/.scout/etc/AI/nano`
|
|
46
|
-
|
|
47
|
-
```yaml
|
|
48
|
-
backend: responses
|
|
49
|
-
model: gpt-5-nano
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
`~/.scout/etc/AI/deep`
|
|
53
|
-
|
|
54
|
-
```yaml
|
|
55
|
-
backend: responses
|
|
56
|
-
model: gpt-5
|
|
57
|
-
reasoning_effort: high
|
|
58
|
-
text_verbosity: high
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
`~/.scout/etc/AI/ollama`
|
|
62
|
-
|
|
63
|
-
```yaml
|
|
64
|
-
backend: ollama
|
|
65
|
-
url: http://localhost:11434
|
|
66
|
-
model: llama3.1
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
How they are used:
|
|
70
|
-
|
|
71
|
-
```ruby
|
|
72
|
-
LLM.ask "Say hi", endpoint: :nano
|
|
73
|
-
LLM.ask "Think harder", endpoint: :deep
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
From the CLI:
|
|
77
|
-
|
|
78
|
-
```bash
|
|
79
|
-
scout-ai llm ask -e nano "Say hi"
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
---
|
|
83
|
-
|
|
84
|
-
## 2. `LLM.ask`: the core call
|
|
85
|
-
|
|
86
|
-
### 2.1 Input types
|
|
87
|
-
|
|
88
|
-
The `question` argument can be:
|
|
89
|
-
|
|
90
|
-
- a String (free text)
|
|
91
|
-
- a **path to a chat file** (if the string looks like a filename and exists)
|
|
92
|
-
- an Array of `{role:, content:}` hashes
|
|
93
|
-
- a `Chat` (which is an annotated array)
|
|
94
|
-
|
|
95
|
-
Internally `LLM.ask` always compiles input via `LLM.chat` first.
|
|
96
|
-
|
|
97
|
-
### 2.2 Option resolution order
|
|
98
|
-
|
|
99
|
-
Options come from multiple places:
|
|
100
|
-
|
|
101
|
-
1) options extracted from the compiled messages (`LLM.options(messages)`)
|
|
102
|
-
2) options passed explicitly to `LLM.ask(question, options)`
|
|
103
|
-
3) endpoint YAML (`~/.scout/etc/AI/<endpoint>`) is merged in (as defaults)
|
|
104
|
-
|
|
105
|
-
### 2.3 Caching (`persist`)
|
|
106
|
-
|
|
107
|
-
`LLM.ask` caches responses by default via `Persist.persist`.
|
|
108
|
-
|
|
109
|
-
- default: `persist: true`
|
|
110
|
-
- set `persist: false` to disable caching
|
|
111
|
-
|
|
112
|
-
The cache key includes the endpoint name, messages, and most options.
|
|
113
|
-
|
|
114
|
-
### 2.4 Return value
|
|
115
|
-
|
|
116
|
-
- default: returns the assistant content string
|
|
117
|
-
- if `return_messages: true`: returns the full message trace (including tool calls)
|
|
118
|
-
|
|
119
|
-
---
|
|
120
|
-
|
|
121
|
-
## 3. Chat compilation (`LLM.chat`) and why it matters
|
|
122
|
-
|
|
123
|
-
`LLM.chat` is what makes chat files powerful.
|
|
124
|
-
|
|
125
|
-
It expands special roles **before** calling the backend:
|
|
126
|
-
|
|
127
|
-
- imports: `import`, `continue`, `last`
|
|
128
|
-
- pruning: `clear`, `skip`
|
|
129
|
-
- workflow execution: `task`, `inline_task`, `exec_task`, and then `job`, `inline_job`
|
|
130
|
-
- file inclusion: `file`, `directory`, `image`, `pdf`
|
|
131
|
-
|
|
132
|
-
See `doc/Chat.md` for exact behavior and role syntax.
|
|
133
|
-
|
|
134
|
-
---
|
|
135
|
-
|
|
136
|
-
## 4. Backends (and what is “passed through”)
|
|
137
|
-
|
|
138
|
-
Backends are selected by `backend:` (from endpoint YAML, env/config, or explicit options).
|
|
139
|
-
|
|
140
|
-
Currently supported backends (see `lib/scout/llm/backends/`):
|
|
141
|
-
|
|
142
|
-
- `responses` — OpenAI Responses API style (default). Supports:
|
|
143
|
-
- JSON Schema responses (`format:` as schema)
|
|
144
|
-
- multimodal (`image:`, `pdf:` roles)
|
|
145
|
-
- session continuation (`previous_response_id`)
|
|
146
|
-
- optional web search tool (`websearch`)
|
|
147
|
-
- `openai` — OpenAI Chat Completions style
|
|
148
|
-
- `anthropic` — Anthropic Messages API style
|
|
149
|
-
- `ollama` — local Ollama server
|
|
150
|
-
- `vllm` — vLLM OpenAI-compatible server
|
|
151
|
-
- `openwebui` — OpenWebUI-compatible chat/completions
|
|
152
|
-
- `bedrock` — AWS Bedrock runtime
|
|
153
|
-
- `relay` — simple SCP/poll relay
|
|
154
|
-
|
|
155
|
-
### 4.1 Common backend options
|
|
156
|
-
|
|
157
|
-
Many backends accept the same high-level keys:
|
|
158
|
-
|
|
159
|
-
- `model`, `url`, `key`
|
|
160
|
-
- `request_timeout`
|
|
161
|
-
- `log_errors`
|
|
162
|
-
- `tools`
|
|
163
|
-
|
|
164
|
-
And then backend-specific keys are forwarded (for example: reasoning/text options for `responses`).
|
|
165
|
-
|
|
166
|
-
### 4.2 Responses session continuation (`previous_response_id`)
|
|
167
|
-
|
|
168
|
-
If you use the Responses backend and provide a `previous_response_id`, Scout-AI will:
|
|
169
|
-
|
|
170
|
-
- keep only the messages after the most recent `previous_response_id` marker (so you do not resend the entire conversation)
|
|
171
|
-
- pass `previous_response_id` to the backend
|
|
172
|
-
|
|
173
|
-
If you set `previous_response: false`, session continuation is disabled.
|
|
174
|
-
|
|
175
|
-
---
|
|
176
|
-
|
|
177
|
-
## 5. Tools and function calling
|
|
178
|
-
|
|
179
|
-
Scout-AI represents tools as a **hash**:
|
|
180
|
-
|
|
181
|
-
```ruby
|
|
182
|
-
tools = {
|
|
183
|
-
"tool_name" => [handler, tool_definition]
|
|
184
|
-
}
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
Where:
|
|
188
|
-
|
|
189
|
-
- `handler` can be:
|
|
190
|
-
- a `Workflow`
|
|
191
|
-
- a `KnowledgeBase`
|
|
192
|
-
- a `Proc` (custom tool implementation)
|
|
193
|
-
- `tool_definition` is an OpenAI-style function schema (adapted per backend)
|
|
194
|
-
|
|
195
|
-
### 5.1 Providing tools
|
|
196
|
-
|
|
197
|
-
You can provide tools in three main ways:
|
|
198
|
-
|
|
199
|
-
1) programmatically: `LLM.ask(..., tools: {...})`
|
|
200
|
-
2) via chat roles: `tool:`, `kb:`, `association:`, `mcp:`
|
|
201
|
-
3) via an Agent: `LLM::Agent` automatically exports a workflow/KB as tools
|
|
202
|
-
|
|
203
|
-
### 5.2 Workflow tools
|
|
204
|
-
|
|
205
|
-
```ruby
|
|
206
|
-
workflow_tools = LLM.workflow_tools(Baking)
|
|
207
|
-
LLM.ask "Bake muffins", tools: workflow_tools
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
Or in a chat file:
|
|
211
|
-
|
|
212
|
-
```text
|
|
213
|
-
tool: Baking bake_muffin_tray
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
### 5.3 KnowledgeBase tools
|
|
217
|
-
|
|
218
|
-
```ruby
|
|
219
|
-
kb_tools = LLM.knowledge_base_tool_definition(kb)
|
|
220
|
-
LLM.ask "Query relationships", tools: kb_tools
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
Or in a chat file:
|
|
224
|
-
|
|
225
|
-
```text
|
|
226
|
-
association: brothers test/data/person/brothers undirected=true
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
### 5.4 Tool execution and output limits
|
|
230
|
-
|
|
231
|
-
Tool calls are executed by `LLM.process_calls`.
|
|
232
|
-
|
|
233
|
-
To protect the context window, tool outputs are capped (config key `:max_content_length` under `:llm_tools`).
|
|
234
|
-
|
|
235
|
-
If a tool returns more than the max length, the content is replaced by an exception-like JSON string.
|
|
236
|
-
|
|
237
|
-
---
|
|
238
|
-
|
|
239
|
-
## 6. Convenience helpers
|
|
240
|
-
|
|
241
|
-
### 6.1 `LLM.workflow_ask`
|
|
242
|
-
|
|
243
|
-
```ruby
|
|
244
|
-
LLM.workflow_ask(Baking, "Bake muffins", endpoint: :nano)
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
### 6.2 `LLM.knowledge_base_ask`
|
|
248
|
-
|
|
249
|
-
```ruby
|
|
250
|
-
LLM.knowledge_base_ask(kb, "Who is X's brother in law?", endpoint: :nano)
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
---
|
|
254
|
-
|
|
255
|
-
## 7. CLI usage
|
|
256
|
-
|
|
257
|
-
### 7.1 `scout-ai llm ask`
|
|
258
|
-
|
|
259
|
-
The main “stateless” CLI.
|
|
260
|
-
|
|
261
|
-
```bash
|
|
262
|
-
scout-ai llm ask -e nano "Say hi"
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
For convenience prompt does not need quotes.
|
|
266
|
-
|
|
267
|
-
```bash
|
|
268
|
-
scout-ai llm ask -e nano Say hi
|
|
269
|
-
```
|
|
270
|
-
|
|
271
|
-
Useful flags:
|
|
272
|
-
|
|
273
|
-
- `-e/--endpoint <name>`
|
|
274
|
-
- `-m/--model <model_id>`
|
|
275
|
-
- `-b/--backend <backend>`
|
|
276
|
-
- `-c/--chat <file>` — read/append to a chat file
|
|
277
|
-
- `-i/--imports <a,b,c>` — import other chat files
|
|
278
|
-
- `-t/--template <name_or_path>` — templates with optional `???` placeholder
|
|
279
|
-
- `-f/--file <path>` — inline a file into the prompt
|
|
280
|
-
- `-d/--dry_run` — print the compiled conversation (no model call)
|
|
281
|
-
|
|
282
|
-
The configuration files under etc/config can be used to setup default
|
|
283
|
-
endpoints or models.
|
|
284
|
-
|
|
285
|
-
```config
|
|
286
|
-
# ~/.scout/etc/config
|
|
287
|
-
endpoint nano ask
|
|
288
|
-
```
|
|
289
|
-
|
|
290
|
-
### 7.2 `scout-ai agent ask`
|
|
291
|
-
|
|
292
|
-
The “agent-aware” CLI.
|
|
293
|
-
|
|
294
|
-
```bash
|
|
295
|
-
scout-ai agent ask Baking Bake a tray of muffins
|
|
296
|
-
```
|
|
297
|
-
|
|
298
|
-
It loads the agent (workflow + knowledge base + start_chat) and runs a stateful conversation.
|
|
299
|
-
|
|
300
|
-
See `doc/Agent.md`.
|
|
301
|
-
|
|
302
|
-
---
|
|
303
|
-
|
|
304
|
-
## 8. Programmatic use (control loops)
|
|
305
|
-
|
|
306
|
-
For loops, branching, delegation, and structured iteration, use `LLM::Agent`.
|
|
307
|
-
|
|
308
|
-
See `doc/Agent.md` for the full API and examples.
|
|
309
|
-
|
|
310
|
-
---
|
|
311
|
-
|
|
312
|
-
## 9. Minimal end-to-end example
|
|
313
|
-
|
|
314
|
-
1) Create an endpoint file:
|
|
315
|
-
|
|
316
|
-
```yaml
|
|
317
|
-
# ~/.scout/etc/AI/nano
|
|
318
|
-
backend: responses
|
|
319
|
-
model: gpt-5-nano
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
2) Ask from the CLI:
|
|
323
|
-
|
|
324
|
-
```bash
|
|
325
|
-
scout-ai llm ask -e nano Say hi
|
|
326
|
-
```
|
|
327
|
-
|
|
328
|
-
3) Use a chat file with a workflow tool:
|
|
329
|
-
|
|
330
|
-
```text
|
|
331
|
-
user:
|
|
332
|
-
|
|
333
|
-
tool: Baking bake_muffin_tray
|
|
334
|
-
|
|
335
|
-
Bake muffins using the tool.
|
|
336
|
-
```
|
|
337
|
-
|
|
338
|
-
```bash
|
|
339
|
-
scout-ai llm ask -c baking.chat -e nano
|
|
340
|
-
```
|
data/doc/RAG.md
DELETED
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
# RAG (Retrieval-Augmented Generation) module
|
|
2
|
-
|
|
3
|
-
This document explains how to use the RAG helper provided in Scout (lib/scout/llm/rag.rb).
|
|
4
|
-
|
|
5
|
-
Audience: AI agents and developers integrating retrieval-augmented flows into other applications.
|
|
6
|
-
|
|
7
|
-
Overview
|
|
8
|
-
--------
|
|
9
|
-
LLM::RAG provides a thin helper to build a nearest-neighbor index over embedding vectors using the hnswlib library. It expects an array of fixed-size numeric vectors (Float arrays) and returns an HNSW index that can be queried with another vector to find the nearest neighbors.
|
|
10
|
-
|
|
11
|
-
The RAG.index method is intentionally small and focused:
|
|
12
|
-
|
|
13
|
-
- It requires the `hnswlib` Ruby gem at runtime (loaded inside the method).
|
|
14
|
-
- It uses L2 (Euclidean) distance by default.
|
|
15
|
-
- It sets the index dimension to the length of the first vector and initializes the HNSW index with the number of elements supplied.
|
|
16
|
-
- Each vector is added in order; the integer ID stored in the index is the zero-based position in the input array.
|
|
17
|
-
|
|
18
|
-
Prerequisites
|
|
19
|
-
-------------
|
|
20
|
-
- Ruby environment with the Scout gem code available.
|
|
21
|
-
- The `hnswlib` Ruby gem installed (the method requires it dynamically):
|
|
22
|
-
|
|
23
|
-
gem install hnswlib
|
|
24
|
-
|
|
25
|
-
- An embedding function that produces fixed-length numeric vectors. Scout exposes LLM.embed(...) which delegates to configured backends (OpenAI, Ollama, etc.). Ensure your embedding backend is configured and working.
|
|
26
|
-
|
|
27
|
-
Basic usage
|
|
28
|
-
-----------
|
|
29
|
-
The common RAG flow is:
|
|
30
|
-
|
|
31
|
-
1. Prepare a corpus (array of documents or chunks).
|
|
32
|
-
2. Compute embeddings for each document.
|
|
33
|
-
3. Build an HNSW index from those embeddings using LLM::RAG.index.
|
|
34
|
-
4. For a query, compute its embedding and run a nearest-neighbor search on the index.
|
|
35
|
-
5. Map matched neighbor indices back to the original documents.
|
|
36
|
-
|
|
37
|
-
Example (Ruby)
|
|
38
|
-
---------------
|
|
39
|
-
This example shows a minimal end-to-end flow using Scout's LLM.embed helper to compute embeddings and LLM::RAG to build and query an index.
|
|
40
|
-
|
|
41
|
-
```ruby
|
|
42
|
-
# `documents` is an array of strings (documents/chunks).
|
|
43
|
-
documents = [
|
|
44
|
-
"How to make espresso at home",
|
|
45
|
-
"Machine learning: an introduction",
|
|
46
|
-
"Ruby concurrency primitives and patterns",
|
|
47
|
-
"Cooking guide: baking sourdough"
|
|
48
|
-
]
|
|
49
|
-
|
|
50
|
-
# 1) Compute embeddings for each document.
|
|
51
|
-
# Use whatever embed model/backend you have configured. Pass model: if needed.
|
|
52
|
-
embeddings = documents.map do |doc|
|
|
53
|
-
# returns an Array<Float> of fixed length
|
|
54
|
-
LLM.embed(doc, model: 'mxbai-embed-large')
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
# 2) Build the HNSW index
|
|
58
|
-
index = LLM::RAG.index(embeddings)
|
|
59
|
-
|
|
60
|
-
# 3) For a query, compute its embedding
|
|
61
|
-
query = "best way to brew espresso"
|
|
62
|
-
query_vec = LLM.embed(query, model: 'mxbai-embed-large')
|
|
63
|
-
|
|
64
|
-
# 4) Run nearest-neighbor search
|
|
65
|
-
# search_knn returns two arrays: node indices and distances/scores
|
|
66
|
-
k = 3
|
|
67
|
-
nodes, scores = index.search_knn(query_vec, k)
|
|
68
|
-
|
|
69
|
-
# 5) Map indices back to original documents
|
|
70
|
-
results = nodes.map { |i| documents[i] }
|
|
71
|
-
|
|
72
|
-
puts "Top #{k} results:"
|
|
73
|
-
results.each_with_index do |doc, idx|
|
|
74
|
-
puts "#{idx + 1}. #{doc} (score=#{scores[idx]})"
|
|
75
|
-
end
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
Notes and best practices
|
|
79
|
-
------------------------
|
|
80
|
-
- Vector dimensionality: All vectors passed to LLM::RAG.index must have identical length. The code inspects `data.first.length` to determine the index dimension.
|
|
81
|
-
- Index IDs: The HNSW index stores integer IDs equal to the input array index. Keep a mapping from those indices to your document IDs/metadata (for instance, an array of document IDs parallel to the embeddings array).
|
|
82
|
-
- Persistence: The RAG helper code only constructs and populates the index in memory. The underlying `hnswlib` gem typically offers persistence APIs (save/load). To persist or reload an index, consult the `hnswlib` gem documentation for the correct methods and usage patterns.
|
|
83
|
-
- Memory and performance: HNSW indexes keep data in memory and can be large for many vectors. Choose your chunking strategy and max dataset size accordingly.
|
|
84
|
-
- Distance metric: The current implementation uses the `'l2'` (Euclidean) space. If your application needs cosine similarity, either normalize vectors before indexing (common practice) or check whether the hnswlib Ruby binding supports a cosine space and adapt accordingly.
|
|
85
|
-
|
|
86
|
-
Example: utility wrapper
|
|
87
|
-
------------------------
|
|
88
|
-
Here is a small utility that wraps the typical pattern and returns the top-k documents and scores for a query.
|
|
89
|
-
|
|
90
|
-
```ruby
|
|
91
|
-
# documents: Array of items (strings or objects). If objects, provide a `to_embedding_source` or pass a block to extract text.
|
|
92
|
-
# embed_opts: options forwarded to LLM.embed (e.g. model: ...)
|
|
93
|
-
def build_rag_index(documents, embed_opts = {})
|
|
94
|
-
# compute embeddings in order
|
|
95
|
-
embeddings = documents.map { |d| LLM.embed(d, embed_opts) }
|
|
96
|
-
index = LLM::RAG.index(embeddings)
|
|
97
|
-
[index, embeddings]
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
def rag_query(index, documents, query, k = 5, embed_opts = {})
|
|
101
|
-
qvec = LLM.embed(query, embed_opts)
|
|
102
|
-
nodes, scores = index.search_knn(qvec, k)
|
|
103
|
-
results = nodes.map { |i| { doc: documents[i], score: scores[nodes.index(i)] } }
|
|
104
|
-
results
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
# Usage:
|
|
108
|
-
# index, embs = build_rag_index(documents, model: 'mxbai-embed-large')
|
|
109
|
-
# top = rag_query(index, documents, 'how to make coffee', 3, model: 'mxbai-embed-large')
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
Troubleshooting
|
|
113
|
-
---------------
|
|
114
|
-
- "NoMethodError" or "uninitialized constant Hnswlib": ensure the `hnswlib` gem is installed and available to your Ruby runtime.
|
|
115
|
-
- Inconsistent dimensions: If you see errors related to dimension mismatch, confirm every embedding vector has the same length and is numeric.
|
|
116
|
-
- Mapping errors: Remember the index IDs correspond to the zero-based position in the `data` array passed to LLM::RAG.index. Keep a parallel array or map to metadata (IDs, titles, etc.).
|
|
117
|
-
|
|
118
|
-
Further integration
|
|
119
|
-
-------------------
|
|
120
|
-
- Use chunking for long documents: split long documents into smaller passages, embed each passage, and keep a mapping from passage index to parent document.
|
|
121
|
-
- Use result reranking: after retrieval, you can rerank retrieved documents with more expensive cross-encoders or scoring functions.
|
|
122
|
-
- Combine with generative models: feed retrieved passages into an LLM prompt to produce answers grounded in retrieved content.
|
|
123
|
-
|
|
124
|
-
References
|
|
125
|
-
----------
|
|
126
|
-
- lib/scout/llm/rag.rb (implementation)
|
|
127
|
-
- hnswlib Ruby gem (install and persistence documentation)
|
|
128
|
-
- Scout LLM embedding helpers (lib/scout/llm/embed.rb)
|
|
129
|
-
|
data/scout_commands/documenter
DELETED
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
|
|
3
|
-
require 'scout'
|
|
4
|
-
|
|
5
|
-
$0 = "scout-ai #{$previous_commands.any? ? $previous_commands*" " + " " : "" }#{ File.basename(__FILE__) }" if $previous_commands
|
|
6
|
-
|
|
7
|
-
options = SOPT.setup <<EOF
|
|
8
|
-
Scout documenter tool
|
|
9
|
-
|
|
10
|
-
$ #{$0} [<options>] <topic>
|
|
11
|
-
|
|
12
|
-
Generates technical, example-driven documentation for a given Scout topic by
|
|
13
|
-
analyzing Ruby source and corresponding test files. For a specified topic, it
|
|
14
|
-
locates main and subtopic files, invokes an LLM agent to synthesize markdown
|
|
15
|
-
documentation using real test behavior and examples, then outputs comprehensive
|
|
16
|
-
topic and subtopic documentation files.
|
|
17
|
-
|
|
18
|
-
-h--help Print this help
|
|
19
|
-
EOF
|
|
20
|
-
if options[:help]
|
|
21
|
-
if defined? scout_usage
|
|
22
|
-
scout_usage
|
|
23
|
-
else
|
|
24
|
-
puts SOPT.doc
|
|
25
|
-
end
|
|
26
|
-
exit 0
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
require 'scout-ai'
|
|
30
|
-
|
|
31
|
-
topic = ARGV.first
|
|
32
|
-
|
|
33
|
-
raise MissingParameterException if topic.nil?
|
|
34
|
-
|
|
35
|
-
src_files = Scout.lib.scout.glob("#{topic}*/**/*") + Scout.lib.scout.glob("#{topic}.rb")
|
|
36
|
-
src_files.collect! do |file|
|
|
37
|
-
file.sub(Dir.pwd, '.')
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
bin_files = Scout.scout_commands.glob("**/*").select{|file| file.include?(topic) && ! file.directory? }
|
|
41
|
-
|
|
42
|
-
main = src_files.select{|f| f.split("/").length == 4}.first
|
|
43
|
-
subtopics = src_files.select{|f| f.split("/").length == 5}.collect{|f| File.basename(f).sub(".rb",'') }.uniq
|
|
44
|
-
|
|
45
|
-
def source_to_test(file)
|
|
46
|
-
file.sub(%r{\A./lib/}, './test/').sub(%r{([^/]+)\.rb\z}, 'test_\1.rb')
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
documenter = LLM::Agent.new
|
|
50
|
-
|
|
51
|
-
documenter.start_chat.system <<-EOF
|
|
52
|
-
You are a world-class Ruby documentation author. For each (source, test) file
|
|
53
|
-
pair given, produce technically precise module- and file-level documentation,
|
|
54
|
-
incorporating specific code usage and behavior from the test file as worked
|
|
55
|
-
examples, code idioms, and edge-case handling.
|
|
56
|
-
|
|
57
|
-
Never insert your own example code: always use live content from the tests as examples.
|
|
58
|
-
|
|
59
|
-
Integrate documentation and test-derived examples smoothly.
|
|
60
|
-
|
|
61
|
-
You will be given first the main topic documentation for the main file and
|
|
62
|
-
test_file, then you will be asked to produce documentation for a subtopic.
|
|
63
|
-
|
|
64
|
-
Finally you will be ask to aggregate all the documentation portions into
|
|
65
|
-
a final topic documentation file
|
|
66
|
-
|
|
67
|
-
User markdown
|
|
68
|
-
|
|
69
|
-
Avoid initial and final comments like: Certainly! I'll do this and that
|
|
70
|
-
EOF
|
|
71
|
-
|
|
72
|
-
documenter.start_chat.file main
|
|
73
|
-
documenter.start_chat.file source_to_test(main)
|
|
74
|
-
|
|
75
|
-
documenter.start_chat.user <<-EOF
|
|
76
|
-
This is the basic topic file. Write the markdown documentation for it.
|
|
77
|
-
EOF
|
|
78
|
-
|
|
79
|
-
docs = {}
|
|
80
|
-
subtopics.each do |subtopic|
|
|
81
|
-
src = src_files.select{|f| f.include? subtopic}.select{|f| f.end_with?(".rb") }
|
|
82
|
-
test = src.collect{|f| source_to_test(f) }.select{|f| Open.exists? f }.select{|f| f.end_with?(".rb")}
|
|
83
|
-
|
|
84
|
-
documenter.start
|
|
85
|
-
(src + test + bin_files).each do |file|
|
|
86
|
-
documenter.file file
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
documenter.start_chat.user <<-EOF
|
|
90
|
-
Write documentation for topic #{topic} subtopic #{subtopic}
|
|
91
|
-
EOF
|
|
92
|
-
docs[subtopic] = documenter.respond
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
documenter.start
|
|
96
|
-
|
|
97
|
-
docs.each do |subtopic, documentation|
|
|
98
|
-
documenter.user <<-EOF
|
|
99
|
-
Please construct a comprehensive documentation on topic #{topic}. For each
|
|
100
|
-
subtopic reproduce all the most important from the original documentation
|
|
101
|
-
files. The subtopic documentation files will not be available anymore, so
|
|
102
|
-
don't leave anything imporant out
|
|
103
|
-
|
|
104
|
-
<file subtopic=#{subtopic}>
|
|
105
|
-
#{documentation}
|
|
106
|
-
<file>
|
|
107
|
-
EOF
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
main_documentation = documenter.chat
|
|
111
|
-
|
|
112
|
-
documenter.start_chat.user <<-EOF
|
|
113
|
-
This is the revise documentation for the topic:
|
|
114
|
-
|
|
115
|
-
---
|
|
116
|
-
|
|
117
|
-
#{main_documentation}
|
|
118
|
-
|
|
119
|
-
---
|
|
120
|
-
EOF
|
|
121
|
-
|
|
122
|
-
revised_subtopics = {}
|
|
123
|
-
docs.each do |subtopic, documentation|
|
|
124
|
-
documenter.start
|
|
125
|
-
|
|
126
|
-
src = src_files.select{|f| f.include? subtopic}.select{|f| f.end_with?(".rb") }
|
|
127
|
-
test = src.collect{|f| source_to_test(f) }.select{|f| Open.exists? f }.select{|f| f.end_with?(".rb")}
|
|
128
|
-
|
|
129
|
-
documenter.start
|
|
130
|
-
(src + test + bin_files).each do |file|
|
|
131
|
-
documenter.file file
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
documenter.user <<-EOF
|
|
135
|
-
Please revise the subtopic documentation in light of the revised main_documentation
|
|
136
|
-
|
|
137
|
-
<file subtopic=#{subtopic}>
|
|
138
|
-
#{documentation}
|
|
139
|
-
<file>
|
|
140
|
-
EOF
|
|
141
|
-
revised_subtopics[subtopic] = documenter.respond
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
Open.write Scout.doc.lib.scout[topic + '.md'].find(:current), main_documentation
|
|
146
|
-
revised_subtopics.each do |subtopic,documentation|
|
|
147
|
-
Open.write Scout.doc.lib.scout[topic][subtopic + '.md'].find(:current), documentation
|
|
148
|
-
end
|