Skip to content

Commit 9a80bc6

Browse files
Fix long JSON pre-processing
1 parent a60bb8a commit 9a80bc6

4 files changed

+391
-4
lines changed

lib/anthropic/http.rb

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -135,13 +135,16 @@ def preprocess_text(stack, delta, user_proc)
135135
def preprocess_json(stack, _delta, user_proc)
136136
if stack.strip.include?("}")
137137
matches = stack.match(/\{(?:[^{}]|\g<0>)*\}/)
138-
user_proc.call(JSON.parse(matches[0]))
139-
stack.clear
138+
if matches
139+
json_object = JSON.parse(matches[0])
140+
user_proc.call(json_object)
141+
# Remove the matched JSON object and any preceding/trailing brackets
142+
# or commas so that the next JSON object can be matched.
143+
stack.sub!(/^\[?,?\s*#{Regexp.escape(matches[0])},?\s*/, "")
144+
end
140145
end
141146
rescue StandardError => e
142147
log(e)
143-
ensure
144-
stack.clear if stack.strip.include?("}")
145148
end
146149

147150
def log(error)

spec/anthropic/client/messages/streaming_spec.rb

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,64 @@
169169
end
170170
end
171171

172+
context "streaming large preprocessed JSON" do
173+
let(:model) { "claude-3-haiku-20240307" }
174+
let(:messages) do
175+
[
176+
{
177+
role: "user",
178+
content: <<~TXT.strip
179+
Give me 10 cool, large tweets about Ruby. Follow this format:
180+
181+
[
182+
{
183+
"title": "put tweet title here",
184+
"tweet": "put tweet text here"
185+
}
186+
]
187+
188+
CRITICAL: Ensure JSON is valid. Escape all necessary characters.
189+
Don't output anything else, only JSON.
190+
TXT
191+
},
192+
{
193+
role: "assistant",
194+
content: ""
195+
}
196+
]
197+
end
198+
let(:max_tokens) { 4096 }
199+
let(:response_objects) { [] }
200+
201+
let(:stream) do
202+
proc do |json_object|
203+
response_objects << json_object
204+
end
205+
end
206+
207+
let(:response) do
208+
Anthropic::Client.new(access_token: ENV.fetch("ANTHROPIC_API_KEY", nil)).messages(
209+
parameters: {
210+
model: model,
211+
messages: messages,
212+
max_tokens: max_tokens,
213+
stream: stream,
214+
preprocess_stream: :json
215+
}
216+
)
217+
end
218+
219+
let(:cassette) { "#{model} streaming json #{messages[0][:content]}".downcase }
220+
221+
it "succeeds" do
222+
VCR.use_cassette(cassette) do
223+
expect(response["content"].empty?).to eq(false)
224+
expect(response_objects.count).to eq(10)
225+
expect(response_objects).to eq(fixture_json("preprocessed_long_json.json"))
226+
end
227+
end
228+
end
229+
172230
context "malformed streaming preprocessed JSON" do
173231
let(:model) { "claude-3-haiku-20240307" }
174232
let(:messages) do

0 commit comments

Comments
 (0)