ruby_llm-ai_sdk 0.1.0 → 0.2.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/CHANGELOG.md +5 -0
- data/README.md +7 -1
- data/lib/ruby_llm/ai_sdk/request.rb +17 -0
- data/lib/ruby_llm/ai_sdk/stream.rb +3 -3
- data/lib/ruby_llm/ai_sdk/version.rb +1 -1
- data/lib/ruby_llm/ai_sdk.rb +2 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 95427bfb5aaa6e2a7f396d47095e84b104886203850cdd88d5e5d2a0b75f9ef6
|
|
4
|
+
data.tar.gz: a6191fc53db9e82dd82d84eb0bd2ad2b76fbf2e1fd71ab820db459671ee36151
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2ed760fa36e55304ee7c96cba1c0e3751a493384704e56c934adea23c8b4e527c84955386414886cc2ecbfe247e36f0a33d746d4a5b18c4ed69f2aabd8e8ec83
|
|
7
|
+
data.tar.gz: d4646943ad0bca03f949273ef523b66389e14bf8f8d5c58e355f2ba5dee0085037680a2a160b7e0bb343196d0a331b2a80195f4cfe0c8ffe767a6e24df454c99
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
- Files attached to a `useChat` message reach the chat: hosted files by URL, data URLs decoded into attachments.
|
|
6
|
+
- `examples/` holds the Rails and Next.js pair the gem was verified with end to end.
|
|
7
|
+
|
|
3
8
|
## 0.1.0
|
|
4
9
|
|
|
5
10
|
- `RubyLLM::AiSdk::Stream` writes a chat turn as AI SDK UI message stream events: text, reasoning, tool calls, tool results, and approval requests.
|
data/README.md
CHANGED
|
@@ -39,7 +39,9 @@ const { messages, sendMessage, addToolApprovalResponse } = useChat({
|
|
|
39
39
|
});
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
`stream_chat` reads the body `useChat` posts. When the last message is from the user, it asks it. When the last assistant message carries approval decisions, it records them with `chat.approve` or `chat.deny` and continues the parked turn.
|
|
42
|
+
`stream_chat` reads the body `useChat` posts. When the last message is from the user, it asks it, with any attached files passed to `chat.ask` as attachments. When the last assistant message carries approval decisions, it records them with `chat.approve` or `chat.deny` and continues the parked turn.
|
|
43
|
+
|
|
44
|
+
[`examples/`](examples/) holds a complete pair, a Rails API app and a Next.js page, that exercises text, tools, approval and denial through a browser.
|
|
43
45
|
|
|
44
46
|
## Usage anywhere else
|
|
45
47
|
|
|
@@ -89,6 +91,10 @@ Declare a tool with `requires_approval` and the turn stops before it runs. The c
|
|
|
89
91
|
- **Tool results come from the transcript, not the callback.** RubyLLM's `after_tool_result` does not carry the tool call id, so results are read from the tool messages appended to the chat. That also keeps concurrent tool execution correct.
|
|
90
92
|
- **Steps follow model responses.** Each assistant message closes a step; a text or reasoning chunk that arrives with no step open opens one.
|
|
91
93
|
|
|
94
|
+
## Files
|
|
95
|
+
|
|
96
|
+
`useChat` sends file parts as data URLs by default, or as hosted URLs when you upload them yourself. Both reach `chat.ask` through `with:`: hosted files as their URL, data URLs decoded into a `RubyLLM::Attachment` that keeps the filename, so RubyLLM detects the type and sends it the way the provider expects.
|
|
97
|
+
|
|
92
98
|
## Development
|
|
93
99
|
|
|
94
100
|
```bash
|
|
@@ -12,6 +12,8 @@ module RubyLLM
|
|
|
12
12
|
# request.approvals # => []
|
|
13
13
|
#
|
|
14
14
|
class Request
|
|
15
|
+
DATA_URL = %r{\Adata:[^,]*;base64,(?<data>.*)\z}m # :nodoc:
|
|
16
|
+
|
|
15
17
|
# One decision on a tool call that required approval.
|
|
16
18
|
Approval = Struct.new(:tool_call_id, :approved, keyword_init: true) do
|
|
17
19
|
# Returns whether the person approved the tool call.
|
|
@@ -49,6 +51,15 @@ module RubyLLM
|
|
|
49
51
|
parts_of_type('text').map { |part| part['text'] }.join("\n")
|
|
50
52
|
end
|
|
51
53
|
|
|
54
|
+
# Returns the files attached to the last user message as sources
|
|
55
|
+
# Chat#ask accepts with +with:+. Hosted files come through as their
|
|
56
|
+
# URL; data URLs are decoded into an Attachment with the file's name.
|
|
57
|
+
def files
|
|
58
|
+
return [] unless last_message&.fetch('role', nil) == 'user'
|
|
59
|
+
|
|
60
|
+
parts_of_type('file').map { |part| file_source(part) }
|
|
61
|
+
end
|
|
62
|
+
|
|
52
63
|
# Returns the Approval decisions recorded on the last assistant
|
|
53
64
|
# message, an empty array when there are none.
|
|
54
65
|
def approvals
|
|
@@ -60,6 +71,12 @@ module RubyLLM
|
|
|
60
71
|
end
|
|
61
72
|
|
|
62
73
|
private
|
|
74
|
+
def file_source(part)
|
|
75
|
+
match = DATA_URL.match(part['url'].to_s)
|
|
76
|
+
return part['url'] unless match
|
|
77
|
+
|
|
78
|
+
RubyLLM::Attachment.new(StringIO.new(Base64.decode64(match[:data])), filename: part['filename'])
|
|
79
|
+
end
|
|
63
80
|
|
|
64
81
|
def last_message
|
|
65
82
|
messages.last
|
|
@@ -30,12 +30,12 @@ module RubyLLM
|
|
|
30
30
|
|
|
31
31
|
# Handles one +useChat+ request end to end. Records the approval
|
|
32
32
|
# decisions when the request carries them and continues the parked
|
|
33
|
-
# turn, otherwise asks the last user message
|
|
34
|
-
# Message.
|
|
33
|
+
# turn, otherwise asks the last user message with its files attached.
|
|
34
|
+
# Returns the final Message.
|
|
35
35
|
def serve(request)
|
|
36
36
|
request = Request.parse(request) if request.is_a?(String)
|
|
37
37
|
request = Request.new(request) if request.is_a?(Hash)
|
|
38
|
-
return ask(request.text) if request.approvals.empty?
|
|
38
|
+
return ask(request.text, with: request.files) if request.approvals.empty?
|
|
39
39
|
|
|
40
40
|
request.approvals.each { |approval| decide(approval) }
|
|
41
41
|
complete(message_id: request.message_id)
|
data/lib/ruby_llm/ai_sdk.rb
CHANGED