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 の扱い)。両プロンプトで共有する。

<<~PROMPT.strip
  パスを指定するツールでは、必ず作業ディレクトリからの相対パス (例: workspace/foo.txt) を
  使ってください。/workspace や /home/user/workspace のような絶対パスは許可されません。
  run_command / bash / powershell の作業ディレクトリはプロジェクトルートです。
  成果物は必ず workspace/ 配下に作ってください。uv など出力先ディレクトリを取るコマンドは
  必ず --directory workspace/... のように workspace 配下を明示してください
  (例: run_command uv init --directory workspace/myproj)。
  最新情報や外部ドキュメントの参照が必要な場合は fetch_url ツールを使ってください。
PROMPT
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
  <<~ENV.strip
    実行環境:
    - 現在日時: #{Time.now.strftime('%Y-%m-%d %H:%M:%S %Z')}
    - OS: #{host_os_description}
    - シェル: PowerShell (pwsh) を優先して使うこと。
    コマンド実行が必要な場合は、原則として powershell ツール (または run_command) を使い、
    PowerShell の構文で書いてください。bash 固有の構文は避けてください。
  ENV
end

.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
  <<~PROMPT
    あなたはユーザー要求を達成するために、ツールを 1 つずつ順番に実行していくアシスタントです。
    これまでに完了したステップの一覧が与えられます。それを踏まえ、
    「次に実行すべきツールを 1 つだけ」選び、その引数を決めてください。

    ユーザー要求がすべて満たされ、もう実行すべきことが残っていない場合は、
    finish ツールを呼び出して完了を宣言してください (それ以外のツールは呼ばないこと)。
    まだ残作業がある場合は finish を呼ばず、次に必要なツールを 1 つだけ呼んでください。

    #{environment_prompt}

    #{WORKSPACE_RULES}
  PROMPT
end

.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
  <<~PROMPT
    あなたはツール実行を提案するアシスタントです。
    ユーザー要求を満たすために呼び出すべきツールを 1 つだけ選び、
    その引数を決めてください。ツールを使って応答してください。

    #{environment_prompt}

    #{WORKSPACE_RULES}
  PROMPT
end

Instance Method Details

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

戻り値: { "tool" => name, "arguments" => Hash } feedback: 初回〜前回までの全試行履歴 (再提案時に引数を変えさせるため / DESIGN §6)。 [{ attempt:, proposal:, error: }, ...] 形式。旧来の単一 Hash も許容する。



123
124
125
126
127
128
129
130
131
132
133
# File 'lib/chocomint/llm/primary_client.rb', line 123

def propose(request, feedback: nil, trace_id: nil)
  response = @client.create_message(
    system: self.class.system_prompt,
    tools: @tool_definitions,
    messages: 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) ⇒ Object

マルチステップ用: これまで完了したステップを踏まえ、次に実行すべき 1 ツールを提案する。 完了していれば tool="finish" を返す (Planner がループ終了に使う)。 戻り値: { "tool" => name, "arguments" => Hash } done_steps: [{ tool:, arguments:, result: }, ...] 形式の完了済みステップ列。



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

def propose_step(request, done_steps = [], trace_id: nil)
  response = @client.create_message(
    system: self.class.step_system_prompt,
    tools: @tool_definitions + [FINISH_TOOL_DEFINITION],
    messages: 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