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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9d1e3f9099b547fc6fa0e3373f0975cb6b3f8204d3684b9adac7d4c66610c265
4
- data.tar.gz: a323962c8263652345699fd1ecdf7355e307328cbb196c2918206d0747a41fda
3
+ metadata.gz: b17fd27c8efed9b5925460bc0237bfc1b7513295782c7fb2da59f536b587fca5
4
+ data.tar.gz: aa1173ecce93936a742fcee83951f660b0ff3fe2737f332754a4876a5d9d558b
5
5
  SHA512:
6
- metadata.gz: 6c623acb99a898b831280cb7fc02eaaa91382e752516e2763b6acf0ffad5c7dfc4dc5e7ac492798c13f21c830662ffb4cf6ab02350cf20ada2cfc237cdacdaad
7
- data.tar.gz: 33acd6c6f4b1c1e717b24fe8fb768844679145991c6fcbde5f7543cd0f52c94f8963de99943591b1b4665647d5c2f35dcd3cd930f2227c83cca19fe83d9faf72
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 **Llm-powered slot filling and tool orchestration**.
4
- Mount an endpoint, register tools (JSON Schema + handler), and let an Llm collect missing fields and call your Ruby code safely.
3
+ **Rails engine for LLM‑powered agents — slot filling, tool orchestration, and safe backend execution.**
4
+ Turn chat into validated function calls using JSONSchema, 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", github: "yourname/llm-agent-rails" # until published
28
+ gem "llm-agent-rails", "~> 0.1"
11
29
  ```
12
30
 
13
- Bundle:
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
- Run the installer:
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:install
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
- ## Quick test (cURL)
46
+ Configure your API key:
27
47
 
28
48
  ```bash
29
- curl -X POST http://localhost:3000/llm/agent/step -H "Content-Type: application/json" -d '{
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
- ## Register a tool (example)
52
+ Run the app:
36
53
 
37
- Create `app/llm_tools/tickets.rb`:
38
- ```ruby
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
- Register it in `config/initializers/llm_agent.rb`:
75
- ```ruby
76
- # After Llm::Agent::Rails.configure block
77
- require Rails.root.join("app/llm_tools/tickets")
78
- LlmTools.register!(Llm::Agent::Rails.config[:registry])
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
- ## How it works
74
+ - `thread_id` keeps tool state & idempotency consistent across turns.
75
+ - `messages` is the chat transcript (array of `{role, content}`).
82
76
 
83
- - **Registry**: Define tools (name/version/schema/description/handler).
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
- ## Routes
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
- The engine exposes:
88
+ Errors look like:
89
+ ```json
90
+ { "error": "BadRequest", "message": "messages is required (array of {role, content})" }
93
91
  ```
94
- POST /llm/agent/step
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
- ## Config
114
+ If you **haven’t registered a tool**, you’ll receive `{"type":"assistant","text":"..."}` — a helpful structured summary.
99
115
 
100
- Edit `config/initializers/llm_agent.rb`:
101
- ```ruby
102
- Llm::Agent::Rails.configure do |c|
103
- c[:model] = "gpt-4o-mini"
104
- c[:temperature] = 0
105
- c[:store] = Llm::Agent::Rails::Store::Memory.new
106
- c[:registry] = Llm::Agent::Rails::Registry.new
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
@@ -2,7 +2,7 @@
2
2
  module Llm
3
3
  module Agent
4
4
  module Rails
5
- VERSION = "0.1.0"
5
+ VERSION = "0.1.2"
6
6
  end
7
7
  end
8
8
  end
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.0
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/yourname/llm-agent-rails
84
+ source_code_uri: https://github.com/pnvang/llm-agent-rails
85
85
  post_install_message:
86
86
  rdoc_options: []
87
87
  require_paths: