Skip to content

Commit c40c241

Browse files
Use new anthropic gem (#1035)
* Replace ruby-anthropic gem with newer anthropic sdk gem * Fix specs for new anthropic SDK gem migration Update response handling and specs to work with the new anthropic gem model objects which use symbol keys instead of string keys. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Update default Anthropic chat model to claude-sonnet-4-6 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix missing trailing newline in aws_bedrock_anthropic_response.rb Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix linter offenses: style, spacing, and empty line after include Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Update CI Ruby matrix: drop 3.1, add 4.0 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add Ruby 4.0 compatibility - Loosen anthropic gem constraint to ~> 1.10 (fixes CGI.parse removal) - Skip RubyCodeInterpreter specs on Ruby 4.0 (safe_ruby gem incompatible) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add mise.toml with ruby = latest Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix linter --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent fca4056 commit c40c241

18 files changed

Lines changed: 182 additions & 125 deletions

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
runs-on: ubuntu-latest
1616
strategy:
1717
matrix:
18-
ruby: ["3.1", "3.2", "3.3", "3.4"]
18+
ruby: ["3.2", "3.3", "3.4", "4.0"]
1919

2020
services:
2121
postgres:

.tool-versions

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
ruby 3.4.4
1+
ruby 4

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -500,7 +500,7 @@ assistant.add_message_and_run!(content: "What's the latest news about AI?")
500500

501501
# Supply an image to the assistant
502502
assistant.add_message_and_run!(
503-
content: "Show me a picture of a cat",
503+
content: "Describe this image.",
504504
image_url: "https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg"
505505
)
506506

langchain.gemspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ Gem::Specification.new do |spec|
4444

4545
# optional dependencies
4646
spec.add_development_dependency "ai21", "~> 0.2.1"
47-
spec.add_development_dependency "ruby-anthropic", "~> 0.4"
47+
spec.add_development_dependency "anthropic", "~> 1.10"
4848
spec.add_development_dependency "aws-sdk-bedrockruntime", "~> 1.1"
4949
spec.add_development_dependency "chroma-db", "~> 0.6.0"
5050
spec.add_development_dependency "cohere-ruby", "~> 1.0.1"

lib/langchain/assistant.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def initialize(
4949
tool_execution_callback: nil,
5050
&block
5151
)
52-
unless tools.is_a?(Array) && tools.all? { |tool| tool.class.singleton_class.included_modules.include?(Langchain::ToolDefinition) }
52+
unless tools.is_a?(Array) && tools.all? { |tool| tool.class.singleton_class.include?(Langchain::ToolDefinition) }
5353
raise ArgumentError, "Tools must be an array of objects extending Langchain::ToolDefinition"
5454
end
5555

lib/langchain/assistant/llm/adapters/anthropic.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,10 @@ def build_message(role:, content: nil, image_url: nil, tool_calls: [], tool_call
4646
# @param tool_call [Hash] The tool call hash, format: {"type"=>"tool_use", "id"=>"toolu_01TjusbFApEbwKPRWTRwzadR", "name"=>"news_retriever__get_top_headlines", "input"=>{"country"=>"us", "page_size"=>10}}], "stop_reason"=>"tool_use"}
4747
# @return [Array] The tool call information
4848
def extract_tool_call_args(tool_call:)
49-
tool_call_id = tool_call.dig("id")
50-
function_name = tool_call.dig("name")
49+
tool_call_id = tool_call.dig(:id)
50+
function_name = tool_call.dig(:name)
5151
tool_name, method_name = function_name.split("__")
52-
tool_arguments = tool_call.dig("input").transform_keys(&:to_sym)
52+
tool_arguments = tool_call.dig(:input).transform_keys(&:to_sym)
5353
[tool_call_id, tool_name, method_name, tool_arguments]
5454
end
5555

lib/langchain/dependency_helper.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class VersionError < ScriptError; end
1717
def depends_on(gem_name, req: true)
1818
gem(gem_name) # require the gem
1919

20-
return(true) unless defined?(Bundler) # If we're in a non-bundler environment, we're no longer able to determine if we'll meet requirements
20+
return true unless defined?(Bundler) # If we're in a non-bundler environment, we're no longer able to determine if we'll meet requirements
2121

2222
gem_version = Gem.loaded_specs[gem_name].version
2323
gem_requirement = Bundler.load.dependencies.find { |g| g.name == gem_name }&.requirement

lib/langchain/llm/anthropic.rb

Lines changed: 24 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ module Langchain::LLM
55
# Wrapper around Anthropic APIs.
66
#
77
# Gem requirements:
8-
# gem "anthropic", "~> 0.3.2"
8+
# gem "anthropic", "~> 1.10.0"
99
#
1010
# Usage:
1111
# llm = Langchain::LLM::Anthropic.new(api_key: ENV["ANTHROPIC_API_KEY"])
@@ -14,7 +14,7 @@ class Anthropic < Base
1414
DEFAULTS = {
1515
temperature: 0.0,
1616
completion_model: "claude-2.1",
17-
chat_model: "claude-3-5-sonnet-20240620",
17+
chat_model: "claude-sonnet-4-6",
1818
max_tokens: 256
1919
}.freeze
2020

@@ -25,22 +25,18 @@ class Anthropic < Base
2525
# @param default_options [Hash] Default options to use on every call to LLM, e.g.: { temperature:, completion_model:, chat_model:, max_tokens:, thinking: }
2626
# @return [Langchain::LLM::Anthropic] Langchain::LLM::Anthropic instance
2727
def initialize(api_key:, llm_options: {}, default_options: {})
28-
begin
29-
depends_on "ruby-anthropic", req: "anthropic"
30-
rescue Langchain::DependencyHelper::LoadError
31-
# Falls back to the older `anthropic` gem if `ruby-anthropic` gem cannot be loaded.
32-
depends_on "anthropic"
33-
end
28+
depends_on "anthropic"
3429

35-
@client = ::Anthropic::Client.new(access_token: api_key, **llm_options)
30+
@client = ::Anthropic::Client.new(api_key: api_key, **llm_options)
3631
@defaults = DEFAULTS.merge(default_options)
3732
chat_parameters.update(
3833
model: {default: @defaults[:chat_model]},
3934
temperature: {default: @defaults[:temperature]},
4035
max_tokens: {default: @defaults[:max_tokens]},
4136
metadata: {},
4237
system: {},
43-
thinking: {default: @defaults[:thinking]}
38+
thinking: {default: @defaults[:thinking]},
39+
request_options: {}
4440
)
4541
chat_parameters.ignore(:n, :user)
4642
chat_parameters.remap(stop: :stop_sequences)
@@ -108,8 +104,6 @@ def complete(
108104
# @option params [Float] :top_p Use nucleus sampling.
109105
# @return [Langchain::LLM::Response::AnthropicResponse] The chat completion
110106
def chat(params = {}, &block)
111-
set_extra_headers! if params[:tools]
112-
113107
parameters = chat_parameters.to_params(params)
114108

115109
raise ArgumentError.new("messages argument is required") if Array(parameters[:messages]).empty?
@@ -124,7 +118,7 @@ def chat(params = {}, &block)
124118
end
125119
end
126120

127-
response = client.messages(parameters: parameters)
121+
response = client.messages.create(parameters)
128122

129123
response = response_from_chunks if block
130124
reset_response_chunks
@@ -144,27 +138,28 @@ def with_api_error_handling
144138
def response_from_chunks
145139
grouped_chunks = @response_chunks.group_by { |chunk| chunk["index"] }.except(nil)
146140

147-
usage = @response_chunks.find { |chunk| chunk["type"] == "message_delta" }&.dig("usage")
148-
stop_reason = @response_chunks.find { |chunk| chunk["type"] == "message_delta" }&.dig("delta", "stop_reason")
141+
usage_chunk = @response_chunks.find { |chunk| chunk["type"] == "message_delta" }
142+
usage = usage_chunk&.dig("usage")&.transform_keys(&:to_sym)
143+
stop_reason = usage_chunk&.dig("delta", "stop_reason")
149144

150145
content = grouped_chunks.map do |_index, chunks|
151146
text = chunks.map { |chunk| chunk.dig("delta", "text") }.join
152147
if !text.nil? && !text.empty?
153-
{"type" => "text", "text" => text}
148+
{type: "text", text: text}
154149
else
155150
tool_calls_from_choice_chunks(chunks)
156151
end
157152
end.flatten
158153

159-
@response_chunks.first&.slice("id", "object", "created", "model")
160-
&.merge!(
161-
{
162-
"content" => content,
163-
"usage" => usage,
164-
"role" => "assistant",
165-
"stop_reason" => stop_reason
166-
}
167-
)
154+
first_chunk = @response_chunks.first
155+
{
156+
id: first_chunk&.dig("id") || first_chunk&.dig("message", "id"),
157+
model: first_chunk&.dig("model") || first_chunk&.dig("message", "model"),
158+
content: content,
159+
usage: usage,
160+
role: "assistant",
161+
stop_reason: stop_reason
162+
}
168163
end
169164

170165
def tool_calls_from_choice_chunks(chunks)
@@ -174,10 +169,10 @@ def tool_calls_from_choice_chunks(chunks)
174169
input = chunks.select { |chunk| chunk.dig("delta", "partial_json") }
175170
.map! { |chunk| chunk.dig("delta", "partial_json") }.join
176171
{
177-
"id" => first_block.dig("content_block", "id"),
178-
"type" => "tool_use",
179-
"name" => first_block.dig("content_block", "name"),
180-
"input" => input.empty? ? nil : JSON.parse(input).transform_keys(&:to_sym)
172+
id: first_block.dig("content_block", "id"),
173+
type: "tool_use",
174+
name: first_block.dig("content_block", "name"),
175+
input: input.empty? ? nil : JSON.parse(input).transform_keys(&:to_sym)
181176
}
182177
end.compact
183178
end
@@ -187,9 +182,5 @@ def tool_calls_from_choice_chunks(chunks)
187182
def reset_response_chunks
188183
@response_chunks = []
189184
end
190-
191-
def set_extra_headers!
192-
::Anthropic.configuration.extra_headers = {"anthropic-beta": "tools-2024-05-16"}
193-
end
194185
end
195186
end

lib/langchain/llm/aws_bedrock.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ def compose_embedding_parameters(params)
229229

230230
def parse_response(response, model_id)
231231
if provider_name(model_id) == :anthropic
232-
Langchain::LLM::Response::AnthropicResponse.new(JSON.parse(response.body.string))
232+
Langchain::LLM::Response::AwsBedrockAnthropicResponse.new(JSON.parse(response.body.string))
233233
elsif provider_name(model_id) == :cohere
234234
Langchain::LLM::Response::CohereResponse.new(JSON.parse(response.body.string))
235235
elsif provider_name(model_id) == :ai21
@@ -317,7 +317,7 @@ def response_from_chunks(chunks)
317317
end
318318
end
319319

320-
Langchain::LLM::Response::AnthropicResponse.new(raw_response)
320+
Langchain::LLM::Response::AwsBedrockAnthropicResponse.new(raw_response)
321321
end
322322
end
323323
end

lib/langchain/llm/response/anthropic_response.rb

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,57 +3,57 @@
33
module Langchain::LLM::Response
44
class AnthropicResponse < BaseResponse
55
def model
6-
raw_response.dig("model")
6+
raw_response[:model]
77
end
88

99
def completion
1010
completions.first
1111
end
1212

1313
def chat_completion
14-
chat_completion = chat_completions.find { |h| h["type"] == "text" }
15-
chat_completion&.dig("text")
14+
chat_completion = chat_completions&.find { |h| h[:type].to_s == "text" }
15+
chat_completion && chat_completion[:text]
1616
end
1717

1818
def tool_calls
19-
tool_call = chat_completions.find { |h| h["type"] == "tool_use" }
20-
tool_call ? [tool_call] : []
19+
tool_call = chat_completions&.find { |h| h[:type].to_s == "tool_use" }
20+
tool_call ? [tool_call.to_h] : []
2121
end
2222

2323
def chat_completions
24-
raw_response.dig("content")
24+
raw_response[:content]
2525
end
2626

2727
def completions
28-
[raw_response.dig("completion")]
28+
[raw_response[:completion]]
2929
end
3030

3131
def stop_reason
32-
raw_response.dig("stop_reason")
32+
raw_response[:stop_reason]
3333
end
3434

35-
def stop
36-
raw_response.dig("stop")
35+
def stop_sequence
36+
raw_response[:stop_sequence]
3737
end
3838

3939
def log_id
40-
raw_response.dig("log_id")
40+
raw_response[:id]
4141
end
4242

4343
def prompt_tokens
44-
raw_response.dig("usage", "input_tokens").to_i
44+
raw_response[:usage][:input_tokens].to_i
4545
end
4646

4747
def completion_tokens
48-
raw_response.dig("usage", "output_tokens").to_i
48+
raw_response[:usage][:output_tokens].to_i
4949
end
5050

5151
def total_tokens
5252
prompt_tokens + completion_tokens
5353
end
5454

5555
def role
56-
raw_response.dig("role")
56+
raw_response[:role].to_s
5757
end
5858
end
5959
end

0 commit comments

Comments
 (0)