Class: Chocomint::LLM::PrimaryClient

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

Overview

主 LLM にツール提案をさせる (DESIGN §5 Step1)。 tool_use ブロックを優先し、無ければテキストから JSON を抽出する (LLM 出力形式に依存しない堅牢性: DESIGN Goals)。

Constant Summary collapse

WORKSPACE_RULES =

共通の作業ルール (パス・workspace の扱い)。両プロンプトで共有する。

"\u30D1\u30B9\u3092\u6307\u5B9A\u3059\u308B\u30C4\u30FC\u30EB\u3067\u306F\u3001\u5FC5\u305A\u4F5C\u696D\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u304B\u3089\u306E\u76F8\u5BFE\u30D1\u30B9 (\u4F8B: workspace/foo.txt) \u3092\n\u4F7F\u3063\u3066\u304F\u3060\u3055\u3044\u3002/workspace \u3084 /home/user/workspace \u306E\u3088\u3046\u306A\u7D76\u5BFE\u30D1\u30B9\u306F\u8A31\u53EF\u3055\u308C\u307E\u305B\u3093\u3002\nrun_command / bash / powershell \u306E\u4F5C\u696D\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306F\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u30EB\u30FC\u30C8\u3067\u3059\u3002\n\u6210\u679C\u7269\u306F\u5FC5\u305A workspace/ \u914D\u4E0B\u306B\u4F5C\u3063\u3066\u304F\u3060\u3055\u3044\u3002uv \u306A\u3069\u51FA\u529B\u5148\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u3092\u53D6\u308B\u30B3\u30DE\u30F3\u30C9\u306F\n\u5FC5\u305A --directory workspace/... \u306E\u3088\u3046\u306B workspace \u914D\u4E0B\u3092\u660E\u793A\u3057\u3066\u304F\u3060\u3055\u3044\n(\u4F8B: run_command uv init --directory workspace/myproj)\u3002\n\u6700\u65B0\u60C5\u5831\u3084\u5916\u90E8\u30C9\u30AD\u30E5\u30E1\u30F3\u30C8\u306E\u53C2\u7167\u304C\u5FC5\u8981\u306A\u5834\u5408\u306F fetch_url \u30C4\u30FC\u30EB\u3092\u4F7F\u3063\u3066\u304F\u3060\u3055\u3044\u3002\n".strip
FINISH_TOOL =

完了宣言用の疑似ツール。実際には実行されず、Planner がループ終了の合図として使う。

"finish"
FINISH_TOOL_DEFINITION =
{
  "name" => FINISH_TOOL,
  "description" => "ユーザー要求がすべて達成され、もう実行すべきツールが残っていないときに呼ぶ。",
  "input_schema" => {
    "type" => "object",
    "properties" => {
      "reason" => { "type" => "string", "description" => "完了と判断した理由 (任意)" },
      "message" => { "type" => "string", "description" => "ユーザーへ伝える最終メッセージ (任意)" }
    }
  }
}.freeze

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(base_client:, tool_definitions:) ⇒ PrimaryClient

Returns a new instance of PrimaryClient.



93
94
95
96
# File 'lib/chocomint/llm/primary_client.rb', line 93

def initialize(base_client:, tool_definitions:)
  @client = base_client
  @tool_definitions = tool_definitions
end

Class Method Details

.environment_promptObject

実行環境 (現在日時・OS・シェル) の説明。ローカルモデルが「今日の日付」や 「どの OS 上で動いているか」を誤解しないよう、システムプロンプトに毎回埋め込む。 シェルは PowerShell を優先させる (Windows ネイティブ環境のため)。



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

def self.environment_prompt
  "    \u5B9F\u884C\u74B0\u5883:\n    - \u73FE\u5728\u65E5\u6642: \#{Time.now.strftime('%Y-%m-%d %H:%M:%S %Z')}\n    - OS: \#{host_os_description}\n    - \u30B7\u30A7\u30EB: PowerShell (pwsh) \u3092\u512A\u5148\u3057\u3066\u4F7F\u3046\u3053\u3068\u3002\n    \u30B3\u30DE\u30F3\u30C9\u5B9F\u884C\u304C\u5FC5\u8981\u306A\u5834\u5408\u306F\u3001\u539F\u5247\u3068\u3057\u3066 powershell \u30C4\u30FC\u30EB (\u307E\u305F\u306F run_command) \u3092\u4F7F\u3044\u3001\n    PowerShell \u306E\u69CB\u6587\u3067\u66F8\u3044\u3066\u304F\u3060\u3055\u3044\u3002bash \u56FA\u6709\u306E\u69CB\u6587\u306F\u907F\u3051\u3066\u304F\u3060\u3055\u3044\u3002\n  ENV\nend\n".strip

.host_os_descriptionObject

RbConfig の host_os を人間可読な OS 名に整える。



27
28
29
30
31
32
33
34
35
# File 'lib/chocomint/llm/primary_client.rb', line 27

def self.host_os_description
  host = RbConfig::CONFIG["host_os"]
  case host
  when /mswin|mingw|cygwin/ then "Windows (#{host})"
  when /darwin/ then "macOS (#{host})"
  when /linux/ then "Linux (#{host})"
  else host.to_s
  end
end

.step_system_promptObject

マルチステップの逐次実行を促すシステムプロンプト。 1 回の応答で「次に実行すべき 1 ツール」だけを選ばせ、 やることが残っていなければ finish ツールで完了を宣言させる。



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/chocomint/llm/primary_client.rb', line 63

def self.step_system_prompt
  "    \u3042\u306A\u305F\u306F\u30E6\u30FC\u30B6\u30FC\u8981\u6C42\u3092\u9054\u6210\u3059\u308B\u305F\u3081\u306B\u3001\u30C4\u30FC\u30EB\u3092 1 \u3064\u305A\u3064\u9806\u756A\u306B\u5B9F\u884C\u3057\u3066\u3044\u304F\u30A2\u30B7\u30B9\u30BF\u30F3\u30C8\u3067\u3059\u3002\n    \u3053\u308C\u307E\u3067\u306B\u5B8C\u4E86\u3057\u305F\u30B9\u30C6\u30C3\u30D7\u306E\u4E00\u89A7\u304C\u4E0E\u3048\u3089\u308C\u307E\u3059\u3002\u305D\u308C\u3092\u8E0F\u307E\u3048\u3001\n    \u300C\u6B21\u306B\u5B9F\u884C\u3059\u3079\u304D\u30C4\u30FC\u30EB\u3092 1 \u3064\u3060\u3051\u300D\u9078\u3073\u3001\u305D\u306E\u5F15\u6570\u3092\u6C7A\u3081\u3066\u304F\u3060\u3055\u3044\u3002\n\n    \u30E6\u30FC\u30B6\u30FC\u8981\u6C42\u304C\u3059\u3079\u3066\u6E80\u305F\u3055\u308C\u3001\u3082\u3046\u5B9F\u884C\u3059\u3079\u304D\u3053\u3068\u304C\u6B8B\u3063\u3066\u3044\u306A\u3044\u5834\u5408\u306F\u3001\n    finish \u30C4\u30FC\u30EB\u3092\u547C\u3073\u51FA\u3057\u3066\u5B8C\u4E86\u3092\u5BA3\u8A00\u3057\u3066\u304F\u3060\u3055\u3044 (\u305D\u308C\u4EE5\u5916\u306E\u30C4\u30FC\u30EB\u306F\u547C\u3070\u306A\u3044\u3053\u3068)\u3002\n    \u307E\u3060\u6B8B\u4F5C\u696D\u304C\u3042\u308B\u5834\u5408\u306F finish \u3092\u547C\u3070\u305A\u3001\u6B21\u306B\u5FC5\u8981\u306A\u30C4\u30FC\u30EB\u3092 1 \u3064\u3060\u3051\u547C\u3093\u3067\u304F\u3060\u3055\u3044\u3002\n\n    \#{environment_prompt}\n\n    \#{WORKSPACE_RULES}\n  PROMPT\nend\n"

.system_promptObject



48
49
50
51
52
53
54
55
56
57
58
# File 'lib/chocomint/llm/primary_client.rb', line 48

def self.system_prompt
  "    \u3042\u306A\u305F\u306F\u30C4\u30FC\u30EB\u5B9F\u884C\u3092\u63D0\u6848\u3059\u308B\u30A2\u30B7\u30B9\u30BF\u30F3\u30C8\u3067\u3059\u3002\n    \u30E6\u30FC\u30B6\u30FC\u8981\u6C42\u3092\u6E80\u305F\u3059\u305F\u3081\u306B\u547C\u3073\u51FA\u3059\u3079\u304D\u30C4\u30FC\u30EB\u3092 1 \u3064\u3060\u3051\u9078\u3073\u3001\n    \u305D\u306E\u5F15\u6570\u3092\u6C7A\u3081\u3066\u304F\u3060\u3055\u3044\u3002\u30C4\u30FC\u30EB\u3092\u4F7F\u3063\u3066\u5FDC\u7B54\u3057\u3066\u304F\u3060\u3055\u3044\u3002\n\n    \#{environment_prompt}\n\n    \#{WORKSPACE_RULES}\n  PROMPT\nend\n"

Instance Method Details

#propose(request, feedback: nil, trace_id: nil, history: nil) ⇒ Object

戻り値: { "tool" => name, "arguments" => Hash } feedback: 初回〜前回までの全試行履歴 (再提案時に引数を変えさせるため / DESIGN §6)。 [{ attempt:, proposal:, error: }, ...] 形式。旧来の単一 Hash も許容する。 history: これまでの会話ターン (propose_step と同様。messages の先頭に積む)。



126
127
128
129
130
131
132
133
134
135
136
# File 'lib/chocomint/llm/primary_client.rb', line 126

def propose(request, feedback: nil, trace_id: nil, history: nil)
  response = @client.create_message(
    system: self.class.system_prompt,
    tools: @tool_definitions,
    messages: Array(history) + build_messages(request, feedback),
    trace_id: trace_id
  )

  from_tool_use(response) || from_text(response) ||
    raise(InvalidProposalError, "proposal contains no valid tool call")
end

#propose_step(request, done_steps = [], trace_id: nil, history: nil) ⇒ Object

マルチステップ用: これまで完了したステップを踏まえ、次に実行すべき 1 ツールを提案する。 完了していれば tool="finish" を返す (Planner がループ終了に使う)。 戻り値: { "tool" => name, "arguments" => Hash } done_steps: [{ tool:, arguments:, result: }, ...] 形式の完了済みステップ列。 history: これまでの会話ターン [{ "role" => "user"/"assistant", "content" => String }, ...] (request より前の文脈として messages の先頭に user/assistant のまま積む)。



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/chocomint/llm/primary_client.rb', line 104

def propose_step(request, done_steps = [], trace_id: nil, history: nil)
  response = @client.create_message(
    system: self.class.step_system_prompt,
    tools: @tool_definitions + [FINISH_TOOL_DEFINITION],
    messages: Array(history) + build_step_messages(request, done_steps),
    trace_id: trace_id
  )

  # ツール提案があればそれを優先。無ければモデルの自然文応答を「finish + メッセージ」
  # として扱う。天気の問い合わせで場所を聞き返すなど、モデルがツールではなく
  # テキストで応答するのは正当なので、これを失敗にせずユーザーへ返す (聞き返し対応)。
  # ここでは JSON 抽出の失敗を致命エラーにしない (strict: false)。自然文に壊れた
  # JSON 断片が混じっていても、聞き返し (from_text_reply) にフォールバックさせる。
  from_tool_use(response) || from_text(response, strict: false) ||
    from_text_reply(response) ||
    raise(InvalidProposalError, "step proposal contains no valid tool call")
end