Module: SmartPrompt::OpenAIChatShaping

Included in:
SenseNovaAdapter, SiliconFlowAdapter, ZhipuAIAdapter
Defined in:
lib/smart_prompt/concerns/openai_chat_shaping.rb

Overview

Shared shaping of Net::HTTP chat responses into the OpenAI completion / stream shape that the rest of SmartPrompt (Engine#@stream_proc, Conversation) expects.

Reasoning models expose a thinking trace under a provider-specific field —surfaced here uniformly as ‘reasoning_content`. Adapters override one hook:

reasoning_field_name  — the source field on message/delta (default
  "reasoning_content"; SenseNova uses "reasoning"). Its value is remapped to
  reasoning_content so Engine#@stream_proc needs no per-provider logic.

extra_top_level_fields(raw) — extra top-level keys to copy onto the shaped
  response/chunk (default {}; SenseNova adds system_fingerprint).

Instance Method Summary collapse

Instance Method Details

#build_completion_response(raw) ⇒ Object



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/smart_prompt/concerns/openai_chat_shaping.rb', line 15

def build_completion_response(raw)
  msg = raw.dig("choices", 0, "message") || {}
  message = { "role" => msg["role"] || "assistant" }
  message["content"] = msg["content"]
  reasoning = msg[reasoning_field_name]
  message["reasoning_content"] = reasoning if reasoning
  message["tool_calls"] = msg["tool_calls"] if msg["tool_calls"]

  response = {
    "id"      => raw["id"],
    "object"  => raw["object"] || "chat.completion",
    "created" => raw["created"],
    "model"   => raw["model"],
    "choices" => [{
      "index"         => 0,
      "message"       => message,
      "finish_reason" => raw.dig("choices", 0, "finish_reason"),
    }],
  }
  response["usage"] = raw["usage"] if raw["usage"]
  merge_extra_top_level(response, raw)
  response
end

#build_stream_chunk(data) ⇒ Object



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
# File 'lib/smart_prompt/concerns/openai_chat_shaping.rb', line 39

def build_stream_chunk(data)
  chunk = {
    "id"      => data["id"],
    "object"  => data["object"],
    "created" => data["created"],
    "model"   => data["model"],
  }
  chunk["usage"] = data["usage"] if data["usage"]
  merge_extra_top_level(chunk, data)

  choices = data["choices"] || []
  if choices.any?
    delta = choices[0]["delta"] || {}
    new_delta = {}
    new_delta["role"]              = delta["role"]        if delta["role"]
    new_delta["content"]           = delta["content"]     if delta["content"]
    reasoning = delta[reasoning_field_name]
    new_delta["reasoning_content"] = reasoning if reasoning
    new_delta["tool_calls"]        = delta["tool_calls"]  if delta["tool_calls"]
    chunk["choices"] = [{
      "index"         => choices[0]["index"] || 0,
      "delta"         => new_delta,
      "finish_reason" => choices[0]["finish_reason"],
    }]
  else
    chunk["choices"] = []
  end
  chunk
end

#extra_top_level_fields(_raw) ⇒ Object



75
76
77
# File 'lib/smart_prompt/concerns/openai_chat_shaping.rb', line 75

def extra_top_level_fields(_raw)
  {}
end

#reasoning_field_nameObject

—- hooks (override in adapter) —————————————–



71
72
73
# File 'lib/smart_prompt/concerns/openai_chat_shaping.rb', line 71

def reasoning_field_name
  "reasoning_content"
end