Module: RubyLLM::Protocols::Cohere::Tools

Defined in:
lib/ruby_llm/protocols/cohere/tools.rb

Overview

Tools methods of the Cohere v2 API integration

Constant Summary collapse

EMPTY_PARAMETERS_SCHEMA =
{
  'type' => 'object',
  'properties' => {},
  'required' => []
}.freeze

Class Method Summary collapse

Class Method Details

.build_tool_choice(choice) ⇒ Object

Cohere only forces tool use on or off; it cannot pin a named tool.



47
48
49
50
51
52
53
54
55
56
# File 'lib/ruby_llm/protocols/cohere/tools.rb', line 47

def build_tool_choice(choice)
  case choice
  when nil, :auto then nil
  when :none then 'NONE'
  when :required then 'REQUIRED'
  else
    raise ArgumentError,
          "Cohere tool choice accepts :auto, :required, or :none, got #{choice.inspect}"
  end
end

.document_blocks(search_results) ⇒ Object



95
96
97
98
99
100
101
102
103
104
105
# File 'lib/ruby_llm/protocols/cohere/tools.rb', line 95

def document_blocks(search_results)
  search_results.results.map.with_index do |result, index|
    {
      type: 'document',
      document: {
        id: result[:url] || "tool:#{index}",
        data: { title: result[:title], text: result[:text], url: result[:url] }.compact
      }
    }
  end
end

.format_tool_calls(tool_calls) ⇒ Object



58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/ruby_llm/protocols/cohere/tools.rb', line 58

def format_tool_calls(tool_calls)
  tool_calls.map do |_, tool_call|
    {
      id: tool_call.id,
      type: 'function',
      function: {
        name: tool_call.name,
        arguments: JSON.generate(tool_call.arguments)
      }
    }
  end
end

.format_tool_result(msg) ⇒ Object



71
72
73
74
75
76
77
# File 'lib/ruby_llm/protocols/cohere/tools.rb', line 71

def format_tool_result(msg)
  {
    role: 'tool',
    tool_call_id: msg.tool_call_id,
    content: format_tool_result_content(msg)
  }
end

.format_tool_result_content(msg) ⇒ Object

Search results become document content blocks, the shape Cohere cites tool output against. A tool that attaches files sends its text and each attachment as blocks, so the model reads what the tool fetched rather than only the sentence describing it.



83
84
85
86
87
88
89
90
91
92
93
# File 'lib/ruby_llm/protocols/cohere/tools.rb', line 83

def format_tool_result_content(msg)
  search_results = RubyLLM::SearchResults.from_content(msg.content)
  return document_blocks(search_results) if search_results
  return msg.content.to_s if msg.attachments.empty?

  [Media.format_text(msg.content.to_s)] + msg.attachments.map do |attachment|
    raise UnsupportedAttachmentError, attachment.mime_type unless attachment.type == :text

    Media.format_text(attachment.for_llm)
  end
end

.function_for(tool) ⇒ Object



18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/ruby_llm/protocols/cohere/tools.rb', line 18

def function_for(tool)
  definition = {
    type: 'function',
    function: {
      name: tool.name,
      description: tool.description,
      parameters: parameters_schema_for(tool)
    }.compact
  }

  return definition if tool.provider_options.empty?

  RubyLLM::Support::Utils.deep_merge(definition, tool.provider_options)
end

.parameters_schema_for(tool) ⇒ Object

Cohere takes plain JSON Schema here and controls strictness with the request's own strict_tools flag, so the OpenAI strict keyword goes.



35
36
37
38
39
40
41
42
43
44
# File 'lib/ruby_llm/protocols/cohere/tools.rb', line 35

def parameters_schema_for(tool)
  schema = tool.parameters_schema ||
           RubyLLM::Tool::SchemaDefinition.from_parameters(tool.declared_parameters)&.json_schema
  return EMPTY_PARAMETERS_SCHEMA unless schema

  schema = RubyLLM::Support::Utils.deep_dup(schema)
  schema.delete(:strict)
  schema.delete('strict')
  schema
end

.parse_arguments(arguments, response, finish_reason) ⇒ Object



122
123
124
125
126
127
128
# File 'lib/ruby_llm/protocols/cohere/tools.rb', line 122

def parse_arguments(arguments, response, finish_reason)
  return {} if arguments.nil? || arguments.empty?

  JSON.parse(arguments)
rescue JSON::ParserError => e
  raise ToolCallParseError.new(response: response, finish_reason: finish_reason), cause: e
end

.parse_tool_calls(tool_calls, response: nil, finish_reason: nil) ⇒ Object



107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/ruby_llm/protocols/cohere/tools.rb', line 107

def parse_tool_calls(tool_calls, response: nil, finish_reason: nil)
  return nil unless tool_calls&.any?

  tool_calls.to_h do |tool_call|
    [
      tool_call['id'],
      ToolCall.new(
        id: tool_call['id'],
        name: tool_call.dig('function', 'name'),
        arguments: parse_arguments(tool_call.dig('function', 'arguments'), response, finish_reason)
      )
    ]
  end
end