Class: Legion::Extensions::Llm::Anthropic::Translator
- Inherits:
-
Object
- Object
- Legion::Extensions::Llm::Anthropic::Translator
- Includes:
- Logging::Helper
- Defined in:
- lib/legion/extensions/llm/anthropic/translator.rb
Overview
Canonical provider translator for Anthropic Messages API. Implements the provider-boundary contract: canonical to Anthropic wire format. Extracted from Provider render_format/parse methods - behaviour preserved, not rewritten.
Constant Summary collapse
- CAPABILITIES =
Anthropic-specific capabilities per the Phase 3 design.
{ provider: 'anthropic', # Thinking lifecycle: open thinking -> delta -> signature_delta -> close. # Signature required alongside thinking content on Anthropic. thinking: :signature_lifecycle, # Anthropic supports assistant prefill (sending partial assistant message # to bias completion direction) - used in mid-stream failover (G6). assistant_prefill: true, # Streaming support. streaming: true, # Tool calls are first-class (tool_use content blocks). tool_calls: :native, # System prompt as array of content blocks. system_content_blocks: true, # Supported params (G18). Unsupported params dropped with debug log. supported_params: %i[ max_tokens temperature stop_sequences seed response_format ].freeze }.freeze
Instance Method Summary collapse
- #capabilities ⇒ Object
- #config ⇒ Object
-
#initialize(config = {}) ⇒ Translator
constructor
A new instance of Translator.
-
#parse_chunk(raw) ⇒ Object
Parse chunk: raw streaming event to Canonical::Chunk.
-
#parse_response(wire) ⇒ Object
Parse: Anthropic wire Hash to Canonical::Response.
-
#render_request(canonical_request) ⇒ Object
Render: Canonical::Request to Anthropic wire Hash.
Constructor Details
#initialize(config = {}) ⇒ Translator
Returns a new instance of Translator.
39 |
# File 'lib/legion/extensions/llm/anthropic/translator.rb', line 39 def initialize(config = {}) = @config = config |
Instance Method Details
#capabilities ⇒ Object
37 |
# File 'lib/legion/extensions/llm/anthropic/translator.rb', line 37 def capabilities = CAPABILITIES |
#config ⇒ Object
38 |
# File 'lib/legion/extensions/llm/anthropic/translator.rb', line 38 def config = @config || {} |
#parse_chunk(raw) ⇒ Object
Parse chunk: raw streaming event to Canonical::Chunk.
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
# File 'lib/legion/extensions/llm/anthropic/translator.rb', line 110 def parse_chunk(raw) return nil unless raw.is_a?(Hash) && (raw.key?(:type) || raw.key?('type')) type = raw[:type] || raw['type'] case type when 'text_delta', :text_delta Canonical::Chunk.text_delta( delta: extract_delta(raw, 'text_delta'), request_id: raw[:request_id], block_index: raw[:block_index] ) when 'thinking_delta', :thinking_delta delta_obj = raw[:delta] || raw['delta'] sig_from_delta = (delta_obj[:signature] || delta_obj['signature'] if delta_obj.is_a?(Hash)) Canonical::Chunk.thinking_delta( delta: extract_delta(raw, 'thinking_delta'), request_id: raw[:request_id], block_index: raw[:block_index], signature: raw[:signature] || raw['signature'] || sig_from_delta ) when 'tool_call_delta', :tool_call_delta tc = extract_tool_call_from_chunk(raw) return nil unless tc Canonical::Chunk.tool_call_delta( tool_call: tc, request_id: raw[:request_id], block_index: raw[:block_index] ) when 'error', :error Canonical::Chunk.error_chunk( error: raw[:error] || raw['error'] || 'unknown', request_id: raw[:request_id] || '', metadata: raw[:metadata] || raw['metadata'] || {} ) when 'done', :done usage = (Canonical::Usage.from_hash(raw[:usage] || raw['usage'] || {}) if raw[:usage] || raw['usage']) Canonical::Chunk.done( request_id: raw[:request_id] || '', usage: usage, stop_reason: map_stop_reason(raw[:stop_reason] || raw['stop_reason']) ) else # Per G20d: ignore unknown chunk types on consume log.debug("[anthropic translator] ignoring unknown chunk type: #{type.inspect}") nil end rescue StandardError => e handle_exception(e, level: :debug, handled: true, operation: 'anthropic.translator.parse_chunk') Canonical::Chunk.error_chunk( error: "#{e.class}: #{e.}", request_id: raw[:request_id] || '' ) end |
#parse_response(wire) ⇒ Object
Parse: Anthropic wire Hash to Canonical::Response. Accepts both Anthropic wire format (content: array of blocks) and canonical form (text: string) for conformance kit compatibility.
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/legion/extensions/llm/anthropic/translator.rb', line 83 def parse_response(wire) # If the wire has canonical 'text' key, pass through using Canonical::Response factory. return Canonical::Response.from_hash(wire) if wire.key?(:text) || wire.key('text') content = Array(wire[:content] || wire['content'] || []) usage = wire[:usage] || wire['usage'] || {} raw_usage = parse_usage(usage) text = extract_text(content) thinking = extract_thinking(content) tool_calls = extract_tool_calls(content) stop_reason = map_stop_reason(wire[:stop_reason] || wire['stop_reason']) model = wire[:model] || wire['model'] Canonical::Response.build( text: text, thinking: thinking, tool_calls: tool_calls, usage: raw_usage, stop_reason: stop_reason, model: model, routing: {}, metadata: wire.except(:content, :usage, :stop_reason, :model).compact ) end |
#render_request(canonical_request) ⇒ Object
Render: Canonical::Request to Anthropic wire Hash.
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/legion/extensions/llm/anthropic/translator.rb', line 42 def render_request(canonical_request) msgs = canonical_request. || [] , = msgs.partition { |msg| msg.role == :system } system_parts = if canonical_request.system render_system_string(canonical_request.system) else render_system_content() end = (, thinking: thinking_enabled?(canonical_request)) tools = render_tools(canonical_request.tools) tool_choice = render_tool_choice(canonical_request.tool_choice) model_id = canonical_request.&.dig(:model) || 'claude-sonnet-4' base = { model: model_id, messages: , stream: canonical_request.stream, system: system_parts, temperature: canonical_request.params&.temperature }.compact params = canonical_request.params if params base[:max_tokens] = params.max_tokens base[:stop_sequences] = params.stop_sequences base[:seed] = params.seed drop_unsupported_params(params) base[:response_format] = render_response_format(params.response_format) end base[:thinking] = render_thinking_config(canonical_request) if thinking_enabled?(canonical_request) base[:tools] = tools if tools && !tools.empty? base[:tool_choice] = tool_choice if tool_choice base[:max_tokens] ||= canonical_request.&.dig(:default_max_tokens) || settings_default_max_tokens base.compact end |