Class: Chocomint::LLM::BaseClient

Inherits:
Object
  • Object
show all
Defined in:
lib/chocomint/llm/base_client.rb

Overview

Anthropic Messages API 互換クライアント (DESIGN §10)。 base_url を差し替えるだけで Ollama / Claude / OpenRouter などに対応する。 リトライは orchestrator の責務なので、ここは単発呼び出しに徹する。

Instance Method Summary collapse

Constructor Details

#initialize(base_url:, model:, api_key: nil, max_tokens: 1024, timeout: 60, payload_logger: nil, role: nil) ⇒ BaseClient

payload_logger: LLM 送信内容を記録するロガー (record_llm_payload を持つ / 任意)。 role: ペイロードの識別子 ("primary" / "verifier")。ログ表示で区別するため。



14
15
16
17
18
19
20
21
22
23
# File 'lib/chocomint/llm/base_client.rb', line 14

def initialize(base_url:, model:, api_key: nil, max_tokens: 1024, timeout: 60,
               payload_logger: nil, role: nil)
  @base_url = base_url.to_s.chomp("/")
  @model = model
  @api_key = api_key
  @max_tokens = max_tokens
  @timeout = timeout
  @payload_logger = payload_logger
  @role = role
end

Instance Method Details

#create_message(messages:, tools: nil, system: nil, trace_id: nil) ⇒ Object

messages: Anthropic 形式の配列。tools / system は任意。 trace_id: 監査ログと突き合わせるための識別子 (任意)。 戻り値は生のレスポンス Hash (content 配列を含む)。



28
29
30
31
32
33
34
35
36
37
38
39
40
41
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
# File 'lib/chocomint/llm/base_client.rb', line 28

def create_message(messages:, tools: nil, system: nil, trace_id: nil)
  body = {
    "model" => @model,
    "max_tokens" => @max_tokens,
    "messages" => messages
  }
  body["tools"] = tools if tools
  body["system"] = system if system

  headers = {
    "content-type" => "application/json",
    "anthropic-version" => "2023-06-01"
  }
  headers["x-api-key"] = @api_key if @api_key && !@api_key.empty?

  # 送信直前に、LLM へ渡す全内容 (ボディ + ヘッダ) をそのまま記録する。
  # 戻り値の行 id にレスポンスを後から追記する。
  payload_id = log_payload(body, headers, trace_id)

  response = begin
    connection.post("#{@base_url}/messages") do |req|
      headers.each { |k, v| req.headers[k] = v }
      req.body = JSON.generate(body)
    end
  rescue Faraday::ConnectionFailed => e
    # Ollama 等のローカル LLM サーバーが起動していない場合にここに来る。
    # 生の接続拒否メッセージは分かりにくいため、原因が伝わる日本語メッセージに置き換える。
    log_response(payload_id, nil, e.message)
    raise Chocomint::Error,
          "LLM サーバーに接続できません (#{@base_url})。Ollama 等の LLM サーバーが" \
          "起動しているか確認してください。"
  rescue Faraday::Error => e
    # その他の接続失敗・タイムアウト等。orchestrator の RETRY 扱いに乗せるため
    # Chocomint::Error でラップする (素通りすると server.rb の例外処理網から漏れる)。
    log_response(payload_id, nil, e.message)
    raise Chocomint::Error, "LLM request failed: #{e.message}"
  end

  # 成功・失敗を問わず LLM レスポンス (ステータス + 生ボディ) を記録する。
  log_response(payload_id, response.status, response.body)

  unless response.success?
    raise Chocomint::Error, "LLM HTTP #{response.status}: #{response.body}"
  end

  JSON.parse(response.body)
rescue JSON::ParserError => e
  raise Chocomint::Error, "LLM response is not valid JSON: #{e.message}"
end