Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
ruby: ["3.1", "3.2", "3.3", "3.4"]
ruby: ["3.2", "3.3", "3.4", "4.0"]

services:
postgres:
Expand Down
2 changes: 1 addition & 1 deletion .tool-versions
Original file line number Diff line number Diff line change
@@ -1 +1 @@
ruby 3.4.4
ruby 4
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -500,7 +500,7 @@ assistant.add_message_and_run!(content: "What's the latest news about AI?")

# Supply an image to the assistant
assistant.add_message_and_run!(
content: "Show me a picture of a cat",
content: "Describe this image.",
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"
)

Expand Down
2 changes: 1 addition & 1 deletion langchain.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Gem::Specification.new do |spec|

# optional dependencies
spec.add_development_dependency "ai21", "~> 0.2.1"
spec.add_development_dependency "ruby-anthropic", "~> 0.4"
spec.add_development_dependency "anthropic", "~> 1.10"
spec.add_development_dependency "aws-sdk-bedrockruntime", "~> 1.1"
spec.add_development_dependency "chroma-db", "~> 0.6.0"
spec.add_development_dependency "cohere-ruby", "~> 1.0.1"
Expand Down
2 changes: 1 addition & 1 deletion lib/langchain/assistant.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def initialize(
tool_execution_callback: nil,
&block
)
unless tools.is_a?(Array) && tools.all? { |tool| tool.class.singleton_class.included_modules.include?(Langchain::ToolDefinition) }
unless tools.is_a?(Array) && tools.all? { |tool| tool.class.singleton_class.include?(Langchain::ToolDefinition) }
raise ArgumentError, "Tools must be an array of objects extending Langchain::ToolDefinition"
end

Expand Down
6 changes: 3 additions & 3 deletions lib/langchain/assistant/llm/adapters/anthropic.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@ def build_message(role:, content: nil, image_url: nil, tool_calls: [], tool_call
# @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"}
# @return [Array] The tool call information
def extract_tool_call_args(tool_call:)
tool_call_id = tool_call.dig("id")
function_name = tool_call.dig("name")
tool_call_id = tool_call.dig(:id)
function_name = tool_call.dig(:name)
tool_name, method_name = function_name.split("__")
tool_arguments = tool_call.dig("input").transform_keys(&:to_sym)
tool_arguments = tool_call.dig(:input).transform_keys(&:to_sym)
[tool_call_id, tool_name, method_name, tool_arguments]
end

Expand Down
2 changes: 1 addition & 1 deletion lib/langchain/dependency_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class VersionError < ScriptError; end
def depends_on(gem_name, req: true)
gem(gem_name) # require the gem

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
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

gem_version = Gem.loaded_specs[gem_name].version
gem_requirement = Bundler.load.dependencies.find { |g| g.name == gem_name }&.requirement
Expand Down
57 changes: 24 additions & 33 deletions lib/langchain/llm/anthropic.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ module Langchain::LLM
# Wrapper around Anthropic APIs.
#
# Gem requirements:
# gem "anthropic", "~> 0.3.2"
# gem "anthropic", "~> 1.10.0"
#
# Usage:
# llm = Langchain::LLM::Anthropic.new(api_key: ENV["ANTHROPIC_API_KEY"])
Expand All @@ -14,7 +14,7 @@ class Anthropic < Base
DEFAULTS = {
temperature: 0.0,
completion_model: "claude-2.1",
chat_model: "claude-3-5-sonnet-20240620",
chat_model: "claude-sonnet-4-6",
max_tokens: 256
}.freeze

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

@client = ::Anthropic::Client.new(access_token: api_key, **llm_options)
@client = ::Anthropic::Client.new(api_key: api_key, **llm_options)
@defaults = DEFAULTS.merge(default_options)
chat_parameters.update(
model: {default: @defaults[:chat_model]},
temperature: {default: @defaults[:temperature]},
max_tokens: {default: @defaults[:max_tokens]},
metadata: {},
system: {},
thinking: {default: @defaults[:thinking]}
thinking: {default: @defaults[:thinking]},
request_options: {}
)
chat_parameters.ignore(:n, :user)
chat_parameters.remap(stop: :stop_sequences)
Expand Down Expand Up @@ -108,8 +104,6 @@ def complete(
# @option params [Float] :top_p Use nucleus sampling.
# @return [Langchain::LLM::Response::AnthropicResponse] The chat completion
def chat(params = {}, &block)
set_extra_headers! if params[:tools]

parameters = chat_parameters.to_params(params)

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

response = client.messages(parameters: parameters)
response = client.messages.create(parameters)

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

usage = @response_chunks.find { |chunk| chunk["type"] == "message_delta" }&.dig("usage")
stop_reason = @response_chunks.find { |chunk| chunk["type"] == "message_delta" }&.dig("delta", "stop_reason")
usage_chunk = @response_chunks.find { |chunk| chunk["type"] == "message_delta" }
usage = usage_chunk&.dig("usage")&.transform_keys(&:to_sym)
stop_reason = usage_chunk&.dig("delta", "stop_reason")

content = grouped_chunks.map do |_index, chunks|
text = chunks.map { |chunk| chunk.dig("delta", "text") }.join
if !text.nil? && !text.empty?
{"type" => "text", "text" => text}
{type: "text", text: text}
else
tool_calls_from_choice_chunks(chunks)
end
end.flatten

@response_chunks.first&.slice("id", "object", "created", "model")
&.merge!(
{
"content" => content,
"usage" => usage,
"role" => "assistant",
"stop_reason" => stop_reason
}
)
first_chunk = @response_chunks.first
{
id: first_chunk&.dig("id") || first_chunk&.dig("message", "id"),
model: first_chunk&.dig("model") || first_chunk&.dig("message", "model"),
content: content,
usage: usage,
role: "assistant",
stop_reason: stop_reason
}
end

def tool_calls_from_choice_chunks(chunks)
Expand All @@ -174,10 +169,10 @@ def tool_calls_from_choice_chunks(chunks)
input = chunks.select { |chunk| chunk.dig("delta", "partial_json") }
.map! { |chunk| chunk.dig("delta", "partial_json") }.join
{
"id" => first_block.dig("content_block", "id"),
"type" => "tool_use",
"name" => first_block.dig("content_block", "name"),
"input" => input.empty? ? nil : JSON.parse(input).transform_keys(&:to_sym)
id: first_block.dig("content_block", "id"),
type: "tool_use",
name: first_block.dig("content_block", "name"),
input: input.empty? ? nil : JSON.parse(input).transform_keys(&:to_sym)
}
end.compact
end
Expand All @@ -187,9 +182,5 @@ def tool_calls_from_choice_chunks(chunks)
def reset_response_chunks
@response_chunks = []
end

def set_extra_headers!
::Anthropic.configuration.extra_headers = {"anthropic-beta": "tools-2024-05-16"}
end
end
end
4 changes: 2 additions & 2 deletions lib/langchain/llm/aws_bedrock.rb
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ def compose_embedding_parameters(params)

def parse_response(response, model_id)
if provider_name(model_id) == :anthropic
Langchain::LLM::Response::AnthropicResponse.new(JSON.parse(response.body.string))
Langchain::LLM::Response::AwsBedrockAnthropicResponse.new(JSON.parse(response.body.string))
elsif provider_name(model_id) == :cohere
Langchain::LLM::Response::CohereResponse.new(JSON.parse(response.body.string))
elsif provider_name(model_id) == :ai21
Expand Down Expand Up @@ -317,7 +317,7 @@ def response_from_chunks(chunks)
end
end

Langchain::LLM::Response::AnthropicResponse.new(raw_response)
Langchain::LLM::Response::AwsBedrockAnthropicResponse.new(raw_response)
end
end
end
28 changes: 14 additions & 14 deletions lib/langchain/llm/response/anthropic_response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,57 +3,57 @@
module Langchain::LLM::Response
class AnthropicResponse < BaseResponse
def model
raw_response.dig("model")
raw_response[:model]
end

def completion
completions.first
end

def chat_completion
chat_completion = chat_completions.find { |h| h["type"] == "text" }
chat_completion&.dig("text")
chat_completion = chat_completions&.find { |h| h[:type].to_s == "text" }
chat_completion && chat_completion[:text]
end

def tool_calls
tool_call = chat_completions.find { |h| h["type"] == "tool_use" }
tool_call ? [tool_call] : []
tool_call = chat_completions&.find { |h| h[:type].to_s == "tool_use" }
tool_call ? [tool_call.to_h] : []
end

def chat_completions
raw_response.dig("content")
raw_response[:content]
end

def completions
[raw_response.dig("completion")]
[raw_response[:completion]]
end

def stop_reason
raw_response.dig("stop_reason")
raw_response[:stop_reason]
end

def stop
raw_response.dig("stop")
def stop_sequence
raw_response[:stop_sequence]
end

def log_id
raw_response.dig("log_id")
raw_response[:id]
end

def prompt_tokens
raw_response.dig("usage", "input_tokens").to_i
raw_response[:usage][:input_tokens].to_i
end

def completion_tokens
raw_response.dig("usage", "output_tokens").to_i
raw_response[:usage][:output_tokens].to_i
end

def total_tokens
prompt_tokens + completion_tokens
end

def role
raw_response.dig("role")
raw_response[:role].to_s
end
end
end
59 changes: 59 additions & 0 deletions lib/langchain/llm/response/aws_bedrock_anthropic_response.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# frozen_string_literal: true

module Langchain::LLM::Response
class AwsBedrockAnthropicResponse < BaseResponse
def model
raw_response.dig("model")
end

def completion
completions.first
end

def chat_completion
chat_completion = chat_completions.find { |h| h["type"] == "text" }
chat_completion&.dig("text")
end

def tool_calls
tool_call = chat_completions.find { |h| h["type"] == "tool_use" }
tool_call ? [tool_call] : []
end

def chat_completions
raw_response.dig("content")
end

def completions
[raw_response.dig("completion")]
end

def stop_reason
raw_response.dig("stop_reason")
end

def stop
raw_response.dig("stop")
end

def log_id
raw_response.dig("log_id")
end

def prompt_tokens
raw_response.dig("usage", "input_tokens").to_i
end

def completion_tokens
raw_response.dig("usage", "output_tokens").to_i
end

def total_tokens
prompt_tokens + completion_tokens
end

def role
raw_response.dig("role")
end
end
end
2 changes: 2 additions & 0 deletions mise.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[tools]
ruby = "latest"
Loading
Loading