0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-14 13:58:53 +08:00
discourse/plugins/discourse-ai/lib/completions/endpoints/anthropic_shared.rb
Sam 3b13aaa003
DEV: update preset model versions (#39342)
Refresh several AI model presets to newer provider versions and adjust
pricing/capacity metadata accordingly. Also add support for the new
`xhigh` effort level across Anthropic and Bedrock request handling and
update the LLM admin UI/specs to match the renamed presets.
2026-04-17 17:55:24 +10:00

146 lines
4.9 KiB
Ruby
Vendored

# frozen_string_literal: true
module DiscourseAi
module Completions
module Endpoints
module AnthropicShared
EFFORT_VALUES = %w[low medium high xhigh max].freeze
def normalize_model_params(model_params)
model_params = model_params.dup
thinking_enabled =
llm_model.lookup_custom_param("adaptive_thinking") ||
llm_model.lookup_custom_param("enable_reasoning")
if thinking_enabled
model_params.delete(:temperature)
model_params.delete(:top_p)
else
model_params.delete(:top_p) if llm_model.lookup_custom_param("disable_top_p")
if llm_model.lookup_custom_param("disable_temperature")
model_params.delete(:temperature)
end
end
model_params
end
def provider_id
AiApiAuditLog::Provider::Anthropic
end
def xml_tags_to_strip(dialect)
if dialect.prompt.has_tools?
%w[thinking search_quality_reflection search_quality_score]
else
[]
end
end
private
def prompt_size(prompt)
tokenizer.size(prompt.system_prompt.to_s + " " + prompt.messages.to_s)
end
def xml_tools_enabled?
!@native_tool_support
end
def supports_native_structured_output?
!llm_model.lookup_custom_param("disable_native_structured_output")
end
def decode(response_data)
processor.process_message(response_data)
end
def claude_processor
@processor ||=
DiscourseAi::Completions::AnthropicMessageProcessor.new(
streaming_mode: @streaming_mode,
partial_tool_calls: partial_tool_calls,
output_thinking: output_thinking,
)
end
def update_log_from_claude_processor(log)
log.request_tokens = processor.input_tokens if processor.input_tokens
log.response_tokens = processor.output_tokens if processor.output_tokens
log.cache_read_tokens =
processor.cache_read_input_tokens if processor.cache_read_input_tokens
log.cache_write_tokens =
processor.cache_creation_input_tokens if processor.cache_creation_input_tokens
end
def prepare_claude_payload(prompt, model_params, dialect)
@native_tool_support = dialect.native_tool_support?
payload =
default_options(dialect).merge(model_params.except(:response_format)).merge(
messages: prompt.messages,
)
if prompt.has_tools?
payload[:tools] = prompt.tools
apply_tool_choice(payload, dialect, prompt)
end
apply_anthropic_cache_control!(payload, prompt) if should_apply_prompt_caching?(prompt)
payload[:system] = prompt.system_prompt if prompt.system_prompt.present? &&
!payload[:system]
if model_params[:response_format].present?
response_format = model_params[:response_format].deep_symbolize_keys
if supports_native_structured_output?
json_schema = response_format.dig(:json_schema, :schema)
if json_schema.present?
payload[:output_config] ||= {}
payload[:output_config][:format] = { type: "json_schema", schema: json_schema }
end
else
payload[:messages] << { role: "assistant", content: "{" }
@forced_json_through_prefill = true
end
end
payload
end
def apply_tool_choice(payload, dialect, prompt)
return if dialect.tool_choice.blank?
if dialect.tool_choice == :none
payload[:tool_choice] = { type: "none" }
elsif thinking_enabled?(payload)
inject_force_tool_guidance(payload, prompt.tool_choice)
else
payload[:tool_choice] = { type: "tool", name: prompt.tool_choice }
end
end
def thinking_enabled?(payload)
thinking = payload[:thinking]
thinking.present? && %w[enabled adaptive].include?(thinking[:type].to_s)
end
def inject_force_tool_guidance(payload, tool_name)
guidance =
"Important: You must respond by calling the '#{tool_name}' tool immediately. " \
"Do not respond with text."
last_msg = payload[:messages]&.last
if last_msg && last_msg[:role] == "user"
if last_msg[:content].is_a?(String)
last_msg[:content] = last_msg[:content] + "\n\n#{guidance}"
elsif last_msg[:content].is_a?(Array)
last_msg[:content] << { type: "text", text: guidance }
end
else
payload[:messages] << { role: "user", content: guidance }
end
end
end
end
end
end