takt/docs/implements/structured-output.ja.md
2026-02-13 06:11:06 +09:00

5.3 KiB
Raw Permalink Blame History

Structured Output — Phase 3 ステータス判定

概要

Phase 3ステータス判定において、エージェントの出力を structured outputJSON スキーマ)で取得し、ルールマッチングの精度と信頼性を向上させる。

プロバイダ別の挙動

プロバイダ メソッド 仕組み
Claude structured_output SDK が StructuredOutput ツールを自動追加。エージェントがツール経由で { step, reason } を返す
Codex structured_output TurnOptions.outputSchema で API レベルの JSON 制約。テキストが JSON になる
OpenCode structured_output プロンプト末尾に JSON スキーマ付き出力指示を注入。テキストレスポンスから parseStructuredOutput() で JSON を抽出

フォールバックチェーン

judgeStatus() は3段階の独立した LLM 呼び出しでルールをマッチする。

Stage 1: structured_output  — outputSchema 付き LLM 呼び出し → structuredOutput.step1-based integer
Stage 2: phase3_tag         — outputSchema なし LLM 呼び出し → content 内の [MOVEMENT:N] タグ検出
Stage 3: ai_judge           — evaluateCondition() による AI 条件評価

各ステージは専用のインストラクションで LLM に問い合わせる。Stage 1 は「ルール番号を JSON で返せ」、Stage 2 は「タグを1行で出力せよ」と聞き方が異なる。

セッションログには toJudgmentMatchMethod() で変換された値が記録される。

内部メソッド セッションログ
structured_output structured_output
phase3_tag / phase1_tag tag_fallback
ai_judge / ai_judge_fallback ai_judge

インストラクション分岐

Phase 3 テンプレート(perform_phase3_message)は structuredOutput フラグで2つのモードを持つ。

Structured Output モード(structuredOutput: true

主要指示: ルール番号1-basedと理由を返せ。 フォールバック指示: structured output が使えない場合はタグを出力せよ。

タグモード(structuredOutput: false

従来の指示: 対応するタグを1行で出力せよ。

現在、Phase 3 は常に structuredOutput: true で実行される。

アーキテクチャ

StatusJudgmentBuilder
  └─ structuredOutput: true
      ├─ criteriaTable: ルール条件テーブル(常に含む)
      ├─ outputList: タグ一覧(フォールバック用に含む)
      └─ テンプレート: "ルール番号と理由を返せ + タグはフォールバック"

runStatusJudgmentPhase()
  └─ judgeStatus() → JudgeStatusResult { ruleIndex, method }
      └─ StatusJudgmentPhaseResult { tag, ruleIndex, method }

MovementExecutor
  ├─ Phase 3 あり → judgeStatus の結果を直接使用method 伝搬)
  └─ Phase 3 なし → detectMatchedRule() で Phase 1 コンテンツから検出

JSON スキーマ

judgment.jsonjudgeStatus 用)

{
  "type": "object",
  "properties": {
    "step": { "type": "integer", "description": "Matched rule number (1-based)" },
    "reason": { "type": "string", "description": "Brief justification" }
  },
  "required": ["step", "reason"],
  "additionalProperties": false
}

evaluation.jsonevaluateCondition 用)

{
  "type": "object",
  "properties": {
    "matched_index": { "type": "integer" },
    "reason": { "type": "string" }
  },
  "required": ["matched_index", "reason"],
  "additionalProperties": false
}

parseStructuredOutput() — JSON 抽出

Codex と OpenCode はテキストレスポンスから JSON を抽出する。3段階のフォールバック戦略を持つ。

1. Direct parse      — テキスト全体が `{` で始まる JSON オブジェクト
2. Code block        — ```json ... ``` または ``` ... ``` 内の JSON
3. Brace extraction  — テキスト内の最初の `{` から最後の `}` までを切り出し

OpenCode 固有の仕組み

OpenCode SDK は outputFormat を型定義でサポートしていない。代わりにプロンプト末尾に JSON 出力指示を注入する。

---
IMPORTANT: You MUST respond with ONLY a valid JSON object matching this schema. No other text, no markdown code blocks, no explanation.
```json
{ "type": "object", ... }

エージェントが返すテキストを `parseStructuredOutput()` でパースし、`AgentResponse.structuredOutput` に格納する。

## 注意事項

- OpenAI APICodexは `required` に全プロパティを含めないとエラーになる(`additionalProperties: false` 時)
- Codex SDK の `TurnCompletedEvent` には `finalResponse` フィールドがない。structured output は `AgentMessageItem.text` の JSON テキストから `parseStructuredOutput()` でパースする
- Claude SDK は `StructuredOutput` ツール方式のため、インストラクションでタグ出力を強調しすぎるとエージェントがツールを呼ばずタグを出力してしまう
- OpenCode のプロンプト注入方式はモデルの指示従順性に依存する。JSON 以外のテキストが混在する場合は `parseStructuredOutput()` の code block / brace extraction で回収する