llm-agent-rails 0.1.0 → 0.1.2
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 +122 -75
- data/lib/llm/agent/rails/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b17fd27c8efed9b5925460bc0237bfc1b7513295782c7fb2da59f536b587fca5
|
|
4
|
+
data.tar.gz: aa1173ecce93936a742fcee83951f660b0ff3fe2737f332754a4876a5d9d558b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f1db5d7833ab8b3d4a31fed0ec4eefd11f43df217bee17666c13e34ff59ac8ada56d8ff3fe9fb395edaf32085c75baddbb7fba2c5d6817b84196d037613faae2
|
|
7
|
+
data.tar.gz: ec7ceb50c3c3da4912bb8173e414be5a11771dfaf1d734f3270aa30c1ec5deda3ba80b8ffffe11efbc3fe790acefb54fdc8854eb2ce019231d35985bd1c252ed
|
data/README.md
CHANGED
|
@@ -1,111 +1,158 @@
|
|
|
1
1
|
# llm-agent-rails
|
|
2
2
|
|
|
3
|
-
Rails engine for
|
|
4
|
-
|
|
3
|
+
**Rails engine for LLM‑powered agents — slot filling, tool orchestration, and safe backend execution.**
|
|
4
|
+
Turn chat into validated function calls using JSON‑Schema, then run your Ruby handlers with idempotency.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## What this gem gives you
|
|
9
|
+
|
|
10
|
+
- A mountable endpoint: `POST /llm/agent/step` that talks to an LLM.
|
|
11
|
+
- **Slot filling**: the model asks for missing fields until a tool call is ready.
|
|
12
|
+
- **Tool registry**: register functions (name+version+schema+handler) the LLM can call.
|
|
13
|
+
- **Validation**: arguments are validated with `json_schemer` before your handler runs.
|
|
14
|
+
- **Idempotency**: unique per‑thread keys for safe “create” operations.
|
|
15
|
+
- **Adapter**: OpenAI adapter using the `openai ~> 0.21` Ruby SDK.
|
|
16
|
+
- **Memory store**: persists tool results per `thread_id` (swap for Redis in prod).
|
|
17
|
+
- **Generators**:
|
|
18
|
+
- `rails g llm:agent:install` (initializer + mount routes)
|
|
19
|
+
- `rails g llm:agent:tickets_demo` (optional demo tool + migration)
|
|
20
|
+
|
|
21
|
+
---
|
|
5
22
|
|
|
6
23
|
## Install
|
|
7
24
|
|
|
8
25
|
Add to your Gemfile:
|
|
26
|
+
|
|
9
27
|
```ruby
|
|
10
|
-
gem "llm-agent-rails",
|
|
28
|
+
gem "llm-agent-rails", "~> 0.1"
|
|
11
29
|
```
|
|
12
30
|
|
|
13
|
-
|
|
31
|
+
Then:
|
|
32
|
+
|
|
14
33
|
```bash
|
|
15
34
|
bundle install
|
|
35
|
+
bin/rails g llm:agent:install
|
|
36
|
+
# mounts the engine at /llm/agent and creates config/initializers/llm_agent.rb
|
|
16
37
|
```
|
|
17
38
|
|
|
18
|
-
|
|
39
|
+
Optional demo (creates a Ticket model/tool so you can see tool-calling end‑to‑end):
|
|
40
|
+
|
|
19
41
|
```bash
|
|
20
|
-
rails g llm:agent:
|
|
42
|
+
bin/rails g llm:agent:tickets_demo
|
|
43
|
+
bin/rails db:migrate
|
|
21
44
|
```
|
|
22
|
-
This will:
|
|
23
|
-
- Create `config/initializers/llm_agent.rb`
|
|
24
|
-
- Mount the engine at `/llm/agent`
|
|
25
45
|
|
|
26
|
-
|
|
46
|
+
Configure your API key:
|
|
27
47
|
|
|
28
48
|
```bash
|
|
29
|
-
|
|
30
|
-
"thread_id":"demo-thread",
|
|
31
|
-
"messages":[{"role":"user","content":"Open a ticket: Apple Pay checkout keeps failing on mobile."}]
|
|
32
|
-
}'
|
|
49
|
+
export OPENAI_API_KEY=sk-...
|
|
33
50
|
```
|
|
34
51
|
|
|
35
|
-
|
|
52
|
+
Run the app:
|
|
36
53
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
module LlmTools
|
|
40
|
-
CREATE_TICKET_V1 = {
|
|
41
|
-
type: "object", additionalProperties: false,
|
|
42
|
-
properties: {
|
|
43
|
-
title: { type: "string" },
|
|
44
|
-
description: { type: "string" },
|
|
45
|
-
priority: { type: "string", enum: %w[low medium high] },
|
|
46
|
-
assignee_id: { type: "string" }
|
|
47
|
-
},
|
|
48
|
-
required: %w[title description priority]
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
def self.register!(registry)
|
|
52
|
-
registry.register!(
|
|
53
|
-
name: "create_ticket", version: "v1",
|
|
54
|
-
schema: CREATE_TICKET_V1,
|
|
55
|
-
description: "Create a support ticket.",
|
|
56
|
-
handler: ->(args, ctx) {
|
|
57
|
-
key = Llm::Agent::Rails::Idempotency.generate(thread_id: ctx[:thread_id])
|
|
58
|
-
ticket = Ticket.create!(
|
|
59
|
-
org_id: ctx[:tenant_id],
|
|
60
|
-
user_id: ctx[:actor_id],
|
|
61
|
-
idempotency_key: key,
|
|
62
|
-
title: args["title"],
|
|
63
|
-
description: args["description"],
|
|
64
|
-
priority: args["priority"],
|
|
65
|
-
assignee_id: args["assignee_id"]
|
|
66
|
-
)
|
|
67
|
-
{ id: ticket.id, title: ticket.title, priority: ticket.priority, key: key }
|
|
68
|
-
}
|
|
69
|
-
)
|
|
70
|
-
end
|
|
71
|
-
end
|
|
54
|
+
```bash
|
|
55
|
+
bin/rails s
|
|
72
56
|
```
|
|
73
57
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## API — talk to the agent
|
|
61
|
+
|
|
62
|
+
`POST /llm/agent/step`
|
|
63
|
+
|
|
64
|
+
**Body**
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"thread_id": "demo-123",
|
|
68
|
+
"messages": [
|
|
69
|
+
{ "role": "user", "content": "Open a ticket for checkout failing on Apple Pay" }
|
|
70
|
+
]
|
|
71
|
+
}
|
|
79
72
|
```
|
|
80
73
|
|
|
81
|
-
|
|
74
|
+
- `thread_id` keeps tool state & idempotency consistent across turns.
|
|
75
|
+
- `messages` is the chat transcript (array of `{role, content}`).
|
|
82
76
|
|
|
83
|
-
|
|
84
|
-
- **Validators**: JSON Schema validation (`json_schemer`) before calling handlers.
|
|
85
|
-
- **Idempotency**: Generate a per-thread key to prevent duplicate creates.
|
|
86
|
-
- **Orchestrator**: Coordinates conversation, asks for missing fields, executes tools.
|
|
87
|
-
- **Adapter**: OpenAI adapter (supports `openai ~> 0.21`, `chat.completions.create`).
|
|
88
|
-
- **Store**: Memory store for tool-result messages (swap for Redis in prod).
|
|
77
|
+
**Response**
|
|
89
78
|
|
|
90
|
-
|
|
79
|
+
One of:
|
|
80
|
+
```json
|
|
81
|
+
{ "type": "assistant", "text": "a clarifying question or confirmation..." }
|
|
82
|
+
```
|
|
83
|
+
or
|
|
84
|
+
```json
|
|
85
|
+
{ "type": "tool_ran", "tool_name": "create_ticket", "result": { "id": 42, "title": "..." } }
|
|
86
|
+
```
|
|
91
87
|
|
|
92
|
-
|
|
88
|
+
Errors look like:
|
|
89
|
+
```json
|
|
90
|
+
{ "error": "BadRequest", "message": "messages is required (array of {role, content})" }
|
|
93
91
|
```
|
|
94
|
-
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Example conversations
|
|
96
|
+
|
|
97
|
+
### A. Client-managed transcript (simple)
|
|
98
|
+
|
|
99
|
+
Send the **entire transcript each POST** with the same `thread_id`.
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
curl -X POST http://localhost:3000/llm/agent/step -H "Content-Type: application/json" -d '{
|
|
103
|
+
"thread_id": "demo-123",
|
|
104
|
+
"messages": [
|
|
105
|
+
{ "role": "user", "content": "Open a ticket for checkout failing on Apple Pay" },
|
|
106
|
+
{ "role": "assistant", "content": "I can help with that! What is the description?" },
|
|
107
|
+
{ "role": "user", "content": "Description: iOS 17 checkout fails with tokenization error. Priority high." },
|
|
108
|
+
{ "role": "assistant", "content": "Ill create a ticket with the following details:\n\n- **Title**: Checkout failing on Apple Pay\n- **Description**: iOS 17 checkout fails with tokenization error.\n- **Priority**: High\n\nIs there a specific category or team this ticket should be assigned to?"},
|
|
109
|
+
{ "role": "user", "content": "Assign to the mobile team." }
|
|
110
|
+
]
|
|
111
|
+
}'
|
|
95
112
|
```
|
|
96
|
-
(If you change the mount point, this path changes accordingly.)
|
|
97
113
|
|
|
98
|
-
|
|
114
|
+
If you **haven’t registered a tool**, you’ll receive `{"type":"assistant","text":"..."}` — a helpful structured summary.
|
|
99
115
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
end
|
|
116
|
+
### B. With a tool registered (end‑to‑end create)
|
|
117
|
+
|
|
118
|
+
1) Use the demo generator:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
bin/rails g llm:agent:tickets_demo
|
|
122
|
+
bin/rails db:migrate
|
|
108
123
|
```
|
|
109
124
|
|
|
125
|
+
This adds `app/llm_tools/tickets.rb` and registers a `create_ticket_v1` tool with JSON Schema validation and idempotency.
|
|
126
|
+
|
|
127
|
+
2) Try again. Once all required fields are collected, the response includes the tool result:
|
|
128
|
+
|
|
129
|
+
```json
|
|
130
|
+
{
|
|
131
|
+
"type": "tool_ran",
|
|
132
|
+
"tool_name": "create_ticket",
|
|
133
|
+
"result": { "id": 123, "title": "Checkout failing on Apple Pay", "priority": "high", "key": "chat-demo-123-7a3c9e" }
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## How it works
|
|
140
|
+
|
|
141
|
+
- **Orchestrator**: supplies tools to the model, loops until enough data is collected, then chooses exactly one tool to run.
|
|
142
|
+
- **Registry**: your functions (name+version+schema+handler).
|
|
143
|
+
- **Validators**: rejects bad inputs before your code runs.
|
|
144
|
+
- **Idempotency**: generates `chat-<thread>-<hex>`, pass to your creates.
|
|
145
|
+
- **Store**: remembers prior tool results by `thread_id` (swap to Redis).
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Environment
|
|
150
|
+
|
|
151
|
+
- `OPENAI_API_KEY` must be set.
|
|
152
|
+
- Ruby ≥ 3.1, Rails ≥ 7.0.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
110
156
|
## License
|
|
157
|
+
|
|
111
158
|
MIT
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: llm-agent-rails
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Phia Vang
|
|
@@ -81,7 +81,7 @@ licenses:
|
|
|
81
81
|
- MIT
|
|
82
82
|
metadata:
|
|
83
83
|
allowed_push_host: https://rubygems.org
|
|
84
|
-
source_code_uri: https://github.com/
|
|
84
|
+
source_code_uri: https://github.com/pnvang/llm-agent-rails
|
|
85
85
|
post_install_message:
|
|
86
86
|
rdoc_options: []
|
|
87
87
|
require_paths:
|