fix(agent): Ollama の format に移植スキーマを渡し文法変換失敗を解消 - #422

Merged
yusuke0610 merged 1 commit into
mainfrom
fix/ollama-format-portable-schema
Jun 28, 2026
Merged

fix(agent): Ollama の format に移植スキーマを渡し文法変換失敗を解消#422
yusuke0610 merged 1 commit into
mainfrom
fix/ollama-format-portable-schema

Conversation

@yusuke0610

@yusuke0610yusuke0610 commented Jun 28, 2026

Copy link
Copy Markdown
Owner

概要

ローカル Ollama 経路(LLM_LOCAL_OLLAMA=1)で Agent チャットが 502 になっていた不具合を修正する。

原因

OllamaClient/api/chatformat生スキーマbuild_output_schema)を渡しており、その value.maxLength(2000) / suggestions.maxItems(4) を llama.cpp の JSON Schema → GBNF 文法変換が解釈できず、{"error":{"message":"Failed to initialize samplers: failed to parse grammar"}} の 400 を返していた。これが LLMError → 502 になっていた。

実機 curl で切り分け済み:

  • oneOf のみ → 200(無罪)
  • フルスキーマ → 400failed to parse grammar
  • maxLength/maxItems を除去(to_portable_schema 相当)→ 200

変更内容

  • ollama_client.py: formatto_portable_schema(output_schema, drop_additional_properties=False) を渡す(maxLength/maxItems 除去・oneOfenum 平坦化)。Gemini/OpenAI と同じ移植スキーマ経路に揃える。
  • output_schema.py: 「Ollama は maxLength を助言扱い/生スキーマをそのまま使う」という誤った前提のコメントを実態(数値制約で文法変換が壊れる)に修正。ロジックは無変更。
  • tests/test_llm_clients.py: 回帰テスト追加。Ollama に投げる formatoneOf/maxLength/maxItems が残らず、field が enum に畳まれることを検証。

影響範囲

  • 文字数上限の実強制は従来どおり chat_service._parse_response(二重防衛)が担うため品質は不変
  • Anthropic / Gemini / OpenAI 経路は無変更to_portable_schema の本体は触らず、Ollama を相乗りさせただけ)。

検証

  • make lint-backend: pass
  • make test-backend: pass(633 passed)
  • 実機: 移植スキーマ相当のペイロードが Ollama /api/chat で 200 を返すことを curl で確認

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved compatibility when sending structured responses to the AI service by converting schemas into a more portable format before requests are made.
    • Reduced failures caused by schema constraints that are not reliably supported by some model backends.
  • Tests

    • Added coverage to verify the request payload uses the portable schema format and preserves allowed field values.

ローカル Ollama(LLM_LOCAL_OLLAMA=1)経路で /api/chat が
"Failed to initialize samplers: failed to parse grammar" の 400 を返し、
Agent チャットが 502 になっていた。原因は format に渡す JSON Schema の
maxLength / maxItems を llama.cpp の GBNF 文法変換が解釈できないこと
(oneOf は無罪。実機 curl で maxLength/maxItems 除去後は 200 を確認)。
- ollama_client: format に to_portable_schema(..., drop_additional_properties=False)
を渡す(maxLength/maxItems 除去・oneOf→enum 平坦化)。Gemini/OpenAI と同じ移植経路に揃える
- output_schema: 「Ollama は maxLength を助言扱い/生スキーマを使う」誤コメントを実態へ修正
- test_llm_clients: format に oneOf/maxLength/maxItems が残らないことの回帰テストを追加
文字数上限の実強制は従来どおり _parse_response が担う(二重防衛)。
Anthropic/Gemini/OpenAI 経路は無変更。make lint-backend / make test-backend(633 passed) green。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Jun 28, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e66b3fd4-1b6a-4524-843f-878ad8ae8aa1

📥 Commits

Reviewing files that changed from the base of the PR and between 395b512 and fa75ff1.

📒 Files selected for processing (3)
  • backend/app/services/agent/llm/ollama_client.py
  • backend/app/services/agent/output_schema.py
  • backend/tests/test_llm_clients.py

📝 Walkthrough

Walkthrough

OllamaClient.generate now applies to_portable_schema to output_schema before placing it in the Ollama /api/chat request's format field, removing JSON Schema keywords (oneOf, maxLength, maxItems) that break llama.cpp grammar conversion. Docstrings in both files are updated, and a new test verifies the transformed payload.

Changes

Ollama Portable Schema

Layer / File(s)Summary
Schema transformation and docs
backend/app/services/agent/llm/ollama_client.py, backend/app/services/agent/output_schema.py
OllamaClient imports and applies to_portable_schema(output_schema, drop_additional_properties=False) for the format payload field. Docstrings in both files clarify which providers use the portable schema and that length enforcement is delegated to _parse_response.
Payload verification test
backend/tests/test_llm_clients.py
test_ollama_client_sends_portable_schema captures the outgoing request via a monkeypatched httpx.AsyncClient and asserts the format field contains no oneOf, maxLength, or maxItems, and that project.field is folded into an enum.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐇 Hop hop, the grammar's clear,
No maxLength to cause llama fear,
oneOf dropped, the schema neat,
Portable format, what a treat!
The rabbit cheers — constraints complete! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 66.67% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedOllama の format に移植スキーマを渡して grammar 変換失敗を解消する内容で、変更の主旨を適切に表しています。
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/ollama-format-portable-schema

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@yusuke0610
yusuke0610 merged commit 5c5e5df into mainJun 28, 2026
17 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@yusuke0610
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(agent): Ollama の format に移植スキーマを渡し文法変換失敗を解消 - #422

Merged
yusuke0610 merged 1 commit into
mainfrom
fix/ollama-format-portable-schema
Jun 28, 2026
Merged

fix(agent): Ollama の format に移植スキーマを渡し文法変換失敗を解消#422
yusuke0610 merged 1 commit into
mainfrom
fix/ollama-format-portable-schema

Conversation

@yusuke0610

@yusuke0610yusuke0610 commented Jun 28, 2026

Copy link
Copy Markdown
Owner

概要

ローカル Ollama 経路(LLM_LOCAL_OLLAMA=1)で Agent チャットが 502 になっていた不具合を修正する。

原因

OllamaClient/api/chatformat生スキーマbuild_output_schema)を渡しており、その value.maxLength(2000) / suggestions.maxItems(4) を llama.cpp の JSON Schema → GBNF 文法変換が解釈できず、{"error":{"message":"Failed to initialize samplers: failed to parse grammar"}} の 400 を返していた。これが LLMError → 502 になっていた。

実機 curl で切り分け済み:

  • oneOf のみ → 200(無罪)
  • フルスキーマ → 400failed to parse grammar
  • maxLength/maxItems を除去(to_portable_schema 相当)→ 200

変更内容

  • ollama_client.py: formatto_portable_schema(output_schema, drop_additional_properties=False) を渡す(maxLength/maxItems 除去・oneOfenum 平坦化)。Gemini/OpenAI と同じ移植スキーマ経路に揃える。
  • output_schema.py: 「Ollama は maxLength を助言扱い/生スキーマをそのまま使う」という誤った前提のコメントを実態(数値制約で文法変換が壊れる)に修正。ロジックは無変更。
  • tests/test_llm_clients.py: 回帰テスト追加。Ollama に投げる formatoneOf/maxLength/maxItems が残らず、field が enum に畳まれることを検証。

影響範囲

  • 文字数上限の実強制は従来どおり chat_service._parse_response(二重防衛)が担うため品質は不変
  • Anthropic / Gemini / OpenAI 経路は無変更to_portable_schema の本体は触らず、Ollama を相乗りさせただけ)。

検証

  • make lint-backend: pass
  • make test-backend: pass(633 passed)
  • 実機: 移植スキーマ相当のペイロードが Ollama /api/chat で 200 を返すことを curl で確認

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved compatibility when sending structured responses to the AI service by converting schemas into a more portable format before requests are made.
    • Reduced failures caused by schema constraints that are not reliably supported by some model backends.
  • Tests

    • Added coverage to verify the request payload uses the portable schema format and preserves allowed field values.

ローカル Ollama(LLM_LOCAL_OLLAMA=1)経路で /api/chat が
"Failed to initialize samplers: failed to parse grammar" の 400 を返し、
Agent チャットが 502 になっていた。原因は format に渡す JSON Schema の
maxLength / maxItems を llama.cpp の GBNF 文法変換が解釈できないこと
(oneOf は無罪。実機 curl で maxLength/maxItems 除去後は 200 を確認)。
- ollama_client: format に to_portable_schema(..., drop_additional_properties=False)
を渡す(maxLength/maxItems 除去・oneOf→enum 平坦化)。Gemini/OpenAI と同じ移植経路に揃える
- output_schema: 「Ollama は maxLength を助言扱い/生スキーマを使う」誤コメントを実態へ修正
- test_llm_clients: format に oneOf/maxLength/maxItems が残らないことの回帰テストを追加
文字数上限の実強制は従来どおり _parse_response が担う(二重防衛)。
Anthropic/Gemini/OpenAI 経路は無変更。make lint-backend / make test-backend(633 passed) green。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Jun 28, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e66b3fd4-1b6a-4524-843f-878ad8ae8aa1

📥 Commits

Reviewing files that changed from the base of the PR and between 395b512 and fa75ff1.

📒 Files selected for processing (3)
  • backend/app/services/agent/llm/ollama_client.py
  • backend/app/services/agent/output_schema.py
  • backend/tests/test_llm_clients.py

📝 Walkthrough

Walkthrough

OllamaClient.generate now applies to_portable_schema to output_schema before placing it in the Ollama /api/chat request's format field, removing JSON Schema keywords (oneOf, maxLength, maxItems) that break llama.cpp grammar conversion. Docstrings in both files are updated, and a new test verifies the transformed payload.

Changes

Ollama Portable Schema

Layer / File(s)Summary
Schema transformation and docs
backend/app/services/agent/llm/ollama_client.py, backend/app/services/agent/output_schema.py
OllamaClient imports and applies to_portable_schema(output_schema, drop_additional_properties=False) for the format payload field. Docstrings in both files clarify which providers use the portable schema and that length enforcement is delegated to _parse_response.
Payload verification test
backend/tests/test_llm_clients.py
test_ollama_client_sends_portable_schema captures the outgoing request via a monkeypatched httpx.AsyncClient and asserts the format field contains no oneOf, maxLength, or maxItems, and that project.field is folded into an enum.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐇 Hop hop, the grammar's clear,
No maxLength to cause llama fear,
oneOf dropped, the schema neat,
Portable format, what a treat!
The rabbit cheers — constraints complete! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 66.67% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedOllama の format に移植スキーマを渡して grammar 変換失敗を解消する内容で、変更の主旨を適切に表しています。
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/ollama-format-portable-schema

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@yusuke0610
yusuke0610 merged commit 5c5e5df into mainJun 28, 2026
17 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@yusuke0610
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(agent): Ollama の format に移植スキーマを渡し文法変換失敗を解消 - #422

Merged
yusuke0610 merged 1 commit into
mainfrom
fix/ollama-format-portable-schema
Jun 28, 2026
Merged

fix(agent): Ollama の format に移植スキーマを渡し文法変換失敗を解消#422
yusuke0610 merged 1 commit into
mainfrom
fix/ollama-format-portable-schema

Conversation

@yusuke0610

@yusuke0610yusuke0610 commented Jun 28, 2026

Copy link
Copy Markdown
Owner

概要

ローカル Ollama 経路(LLM_LOCAL_OLLAMA=1)で Agent チャットが 502 になっていた不具合を修正する。

原因

OllamaClient/api/chatformat生スキーマbuild_output_schema)を渡しており、その value.maxLength(2000) / suggestions.maxItems(4) を llama.cpp の JSON Schema → GBNF 文法変換が解釈できず、{"error":{"message":"Failed to initialize samplers: failed to parse grammar"}} の 400 を返していた。これが LLMError → 502 になっていた。

実機 curl で切り分け済み:

  • oneOf のみ → 200(無罪)
  • フルスキーマ → 400failed to parse grammar
  • maxLength/maxItems を除去(to_portable_schema 相当)→ 200

変更内容

  • ollama_client.py: formatto_portable_schema(output_schema, drop_additional_properties=False) を渡す(maxLength/maxItems 除去・oneOfenum 平坦化)。Gemini/OpenAI と同じ移植スキーマ経路に揃える。
  • output_schema.py: 「Ollama は maxLength を助言扱い/生スキーマをそのまま使う」という誤った前提のコメントを実態(数値制約で文法変換が壊れる)に修正。ロジックは無変更。
  • tests/test_llm_clients.py: 回帰テスト追加。Ollama に投げる formatoneOf/maxLength/maxItems が残らず、field が enum に畳まれることを検証。

影響範囲

  • 文字数上限の実強制は従来どおり chat_service._parse_response(二重防衛)が担うため品質は不変
  • Anthropic / Gemini / OpenAI 経路は無変更to_portable_schema の本体は触らず、Ollama を相乗りさせただけ)。

検証

  • make lint-backend: pass
  • make test-backend: pass(633 passed)
  • 実機: 移植スキーマ相当のペイロードが Ollama /api/chat で 200 を返すことを curl で確認

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved compatibility when sending structured responses to the AI service by converting schemas into a more portable format before requests are made.
    • Reduced failures caused by schema constraints that are not reliably supported by some model backends.
  • Tests

    • Added coverage to verify the request payload uses the portable schema format and preserves allowed field values.

ローカル Ollama(LLM_LOCAL_OLLAMA=1)経路で /api/chat が
"Failed to initialize samplers: failed to parse grammar" の 400 を返し、
Agent チャットが 502 になっていた。原因は format に渡す JSON Schema の
maxLength / maxItems を llama.cpp の GBNF 文法変換が解釈できないこと
(oneOf は無罪。実機 curl で maxLength/maxItems 除去後は 200 を確認)。
- ollama_client: format に to_portable_schema(..., drop_additional_properties=False)
を渡す(maxLength/maxItems 除去・oneOf→enum 平坦化)。Gemini/OpenAI と同じ移植経路に揃える
- output_schema: 「Ollama は maxLength を助言扱い/生スキーマを使う」誤コメントを実態へ修正
- test_llm_clients: format に oneOf/maxLength/maxItems が残らないことの回帰テストを追加
文字数上限の実強制は従来どおり _parse_response が担う(二重防衛)。
Anthropic/Gemini/OpenAI 経路は無変更。make lint-backend / make test-backend(633 passed) green。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Jun 28, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e66b3fd4-1b6a-4524-843f-878ad8ae8aa1

📥 Commits

Reviewing files that changed from the base of the PR and between 395b512 and fa75ff1.

📒 Files selected for processing (3)
  • backend/app/services/agent/llm/ollama_client.py
  • backend/app/services/agent/output_schema.py
  • backend/tests/test_llm_clients.py

📝 Walkthrough

Walkthrough

OllamaClient.generate now applies to_portable_schema to output_schema before placing it in the Ollama /api/chat request's format field, removing JSON Schema keywords (oneOf, maxLength, maxItems) that break llama.cpp grammar conversion. Docstrings in both files are updated, and a new test verifies the transformed payload.

Changes

Ollama Portable Schema

Layer / File(s)Summary
Schema transformation and docs
backend/app/services/agent/llm/ollama_client.py, backend/app/services/agent/output_schema.py
OllamaClient imports and applies to_portable_schema(output_schema, drop_additional_properties=False) for the format payload field. Docstrings in both files clarify which providers use the portable schema and that length enforcement is delegated to _parse_response.
Payload verification test
backend/tests/test_llm_clients.py
test_ollama_client_sends_portable_schema captures the outgoing request via a monkeypatched httpx.AsyncClient and asserts the format field contains no oneOf, maxLength, or maxItems, and that project.field is folded into an enum.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐇 Hop hop, the grammar's clear,
No maxLength to cause llama fear,
oneOf dropped, the schema neat,
Portable format, what a treat!
The rabbit cheers — constraints complete! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 66.67% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedOllama の format に移植スキーマを渡して grammar 変換失敗を解消する内容で、変更の主旨を適切に表しています。
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/ollama-format-portable-schema

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@yusuke0610
yusuke0610 merged commit 5c5e5df into mainJun 28, 2026
17 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@yusuke0610
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(agent): Ollama の format に移植スキーマを渡し文法変換失敗を解消 - #422

Merged
yusuke0610 merged 1 commit into
mainfrom
fix/ollama-format-portable-schema
Jun 28, 2026
Merged

fix(agent): Ollama の format に移植スキーマを渡し文法変換失敗を解消#422
yusuke0610 merged 1 commit into
mainfrom
fix/ollama-format-portable-schema

Conversation

@yusuke0610

@yusuke0610yusuke0610 commented Jun 28, 2026

Copy link
Copy Markdown
Owner

概要

ローカル Ollama 経路(LLM_LOCAL_OLLAMA=1)で Agent チャットが 502 になっていた不具合を修正する。

原因

OllamaClient/api/chatformat生スキーマbuild_output_schema)を渡しており、その value.maxLength(2000) / suggestions.maxItems(4) を llama.cpp の JSON Schema → GBNF 文法変換が解釈できず、{"error":{"message":"Failed to initialize samplers: failed to parse grammar"}} の 400 を返していた。これが LLMError → 502 になっていた。

実機 curl で切り分け済み:

  • oneOf のみ → 200(無罪)
  • フルスキーマ → 400failed to parse grammar
  • maxLength/maxItems を除去(to_portable_schema 相当)→ 200

変更内容

  • ollama_client.py: formatto_portable_schema(output_schema, drop_additional_properties=False) を渡す(maxLength/maxItems 除去・oneOfenum 平坦化)。Gemini/OpenAI と同じ移植スキーマ経路に揃える。
  • output_schema.py: 「Ollama は maxLength を助言扱い/生スキーマをそのまま使う」という誤った前提のコメントを実態(数値制約で文法変換が壊れる)に修正。ロジックは無変更。
  • tests/test_llm_clients.py: 回帰テスト追加。Ollama に投げる formatoneOf/maxLength/maxItems が残らず、field が enum に畳まれることを検証。

影響範囲

  • 文字数上限の実強制は従来どおり chat_service._parse_response(二重防衛)が担うため品質は不変
  • Anthropic / Gemini / OpenAI 経路は無変更to_portable_schema の本体は触らず、Ollama を相乗りさせただけ)。

検証

  • make lint-backend: pass
  • make test-backend: pass(633 passed)
  • 実機: 移植スキーマ相当のペイロードが Ollama /api/chat で 200 を返すことを curl で確認

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved compatibility when sending structured responses to the AI service by converting schemas into a more portable format before requests are made.
    • Reduced failures caused by schema constraints that are not reliably supported by some model backends.
  • Tests

    • Added coverage to verify the request payload uses the portable schema format and preserves allowed field values.

ローカル Ollama(LLM_LOCAL_OLLAMA=1)経路で /api/chat が
"Failed to initialize samplers: failed to parse grammar" の 400 を返し、
Agent チャットが 502 になっていた。原因は format に渡す JSON Schema の
maxLength / maxItems を llama.cpp の GBNF 文法変換が解釈できないこと
(oneOf は無罪。実機 curl で maxLength/maxItems 除去後は 200 を確認)。
- ollama_client: format に to_portable_schema(..., drop_additional_properties=False)
を渡す(maxLength/maxItems 除去・oneOf→enum 平坦化)。Gemini/OpenAI と同じ移植経路に揃える
- output_schema: 「Ollama は maxLength を助言扱い/生スキーマを使う」誤コメントを実態へ修正
- test_llm_clients: format に oneOf/maxLength/maxItems が残らないことの回帰テストを追加
文字数上限の実強制は従来どおり _parse_response が担う(二重防衛)。
Anthropic/Gemini/OpenAI 経路は無変更。make lint-backend / make test-backend(633 passed) green。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Jun 28, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e66b3fd4-1b6a-4524-843f-878ad8ae8aa1

📥 Commits

Reviewing files that changed from the base of the PR and between 395b512 and fa75ff1.

📒 Files selected for processing (3)
  • backend/app/services/agent/llm/ollama_client.py
  • backend/app/services/agent/output_schema.py
  • backend/tests/test_llm_clients.py

📝 Walkthrough

Walkthrough

OllamaClient.generate now applies to_portable_schema to output_schema before placing it in the Ollama /api/chat request's format field, removing JSON Schema keywords (oneOf, maxLength, maxItems) that break llama.cpp grammar conversion. Docstrings in both files are updated, and a new test verifies the transformed payload.

Changes

Ollama Portable Schema

Layer / File(s)Summary
Schema transformation and docs
backend/app/services/agent/llm/ollama_client.py, backend/app/services/agent/output_schema.py
OllamaClient imports and applies to_portable_schema(output_schema, drop_additional_properties=False) for the format payload field. Docstrings in both files clarify which providers use the portable schema and that length enforcement is delegated to _parse_response.
Payload verification test
backend/tests/test_llm_clients.py
test_ollama_client_sends_portable_schema captures the outgoing request via a monkeypatched httpx.AsyncClient and asserts the format field contains no oneOf, maxLength, or maxItems, and that project.field is folded into an enum.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐇 Hop hop, the grammar's clear,
No maxLength to cause llama fear,
oneOf dropped, the schema neat,
Portable format, what a treat!
The rabbit cheers — constraints complete! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 66.67% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedOllama の format に移植スキーマを渡して grammar 変換失敗を解消する内容で、変更の主旨を適切に表しています。
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/ollama-format-portable-schema

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@yusuke0610
yusuke0610 merged commit 5c5e5df into mainJun 28, 2026
17 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@yusuke0610
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(agent): Ollama の format に移植スキーマを渡し文法変換失敗を解消 - #422

Merged
yusuke0610 merged 1 commit into
mainfrom
fix/ollama-format-portable-schema
Jun 28, 2026
Merged

fix(agent): Ollama の format に移植スキーマを渡し文法変換失敗を解消#422
yusuke0610 merged 1 commit into
mainfrom
fix/ollama-format-portable-schema

Conversation

@yusuke0610

@yusuke0610yusuke0610 commented Jun 28, 2026

Copy link
Copy Markdown
Owner

概要

ローカル Ollama 経路(LLM_LOCAL_OLLAMA=1)で Agent チャットが 502 になっていた不具合を修正する。

原因

OllamaClient/api/chatformat生スキーマbuild_output_schema)を渡しており、その value.maxLength(2000) / suggestions.maxItems(4) を llama.cpp の JSON Schema → GBNF 文法変換が解釈できず、{"error":{"message":"Failed to initialize samplers: failed to parse grammar"}} の 400 を返していた。これが LLMError → 502 になっていた。

実機 curl で切り分け済み:

  • oneOf のみ → 200(無罪)
  • フルスキーマ → 400failed to parse grammar
  • maxLength/maxItems を除去(to_portable_schema 相当)→ 200

変更内容

  • ollama_client.py: formatto_portable_schema(output_schema, drop_additional_properties=False) を渡す(maxLength/maxItems 除去・oneOfenum 平坦化)。Gemini/OpenAI と同じ移植スキーマ経路に揃える。
  • output_schema.py: 「Ollama は maxLength を助言扱い/生スキーマをそのまま使う」という誤った前提のコメントを実態(数値制約で文法変換が壊れる)に修正。ロジックは無変更。
  • tests/test_llm_clients.py: 回帰テスト追加。Ollama に投げる formatoneOf/maxLength/maxItems が残らず、field が enum に畳まれることを検証。

影響範囲

  • 文字数上限の実強制は従来どおり chat_service._parse_response(二重防衛)が担うため品質は不変
  • Anthropic / Gemini / OpenAI 経路は無変更to_portable_schema の本体は触らず、Ollama を相乗りさせただけ)。

検証

  • make lint-backend: pass
  • make test-backend: pass(633 passed)
  • 実機: 移植スキーマ相当のペイロードが Ollama /api/chat で 200 を返すことを curl で確認

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved compatibility when sending structured responses to the AI service by converting schemas into a more portable format before requests are made.
    • Reduced failures caused by schema constraints that are not reliably supported by some model backends.
  • Tests

    • Added coverage to verify the request payload uses the portable schema format and preserves allowed field values.

ローカル Ollama(LLM_LOCAL_OLLAMA=1)経路で /api/chat が
"Failed to initialize samplers: failed to parse grammar" の 400 を返し、
Agent チャットが 502 になっていた。原因は format に渡す JSON Schema の
maxLength / maxItems を llama.cpp の GBNF 文法変換が解釈できないこと
(oneOf は無罪。実機 curl で maxLength/maxItems 除去後は 200 を確認)。
- ollama_client: format に to_portable_schema(..., drop_additional_properties=False)
を渡す(maxLength/maxItems 除去・oneOf→enum 平坦化)。Gemini/OpenAI と同じ移植経路に揃える
- output_schema: 「Ollama は maxLength を助言扱い/生スキーマを使う」誤コメントを実態へ修正
- test_llm_clients: format に oneOf/maxLength/maxItems が残らないことの回帰テストを追加
文字数上限の実強制は従来どおり _parse_response が担う(二重防衛)。
Anthropic/Gemini/OpenAI 経路は無変更。make lint-backend / make test-backend(633 passed) green。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Jun 28, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e66b3fd4-1b6a-4524-843f-878ad8ae8aa1

📥 Commits

Reviewing files that changed from the base of the PR and between 395b512 and fa75ff1.

📒 Files selected for processing (3)
  • backend/app/services/agent/llm/ollama_client.py
  • backend/app/services/agent/output_schema.py
  • backend/tests/test_llm_clients.py

📝 Walkthrough

Walkthrough

OllamaClient.generate now applies to_portable_schema to output_schema before placing it in the Ollama /api/chat request's format field, removing JSON Schema keywords (oneOf, maxLength, maxItems) that break llama.cpp grammar conversion. Docstrings in both files are updated, and a new test verifies the transformed payload.

Changes

Ollama Portable Schema

Layer / File(s)Summary
Schema transformation and docs
backend/app/services/agent/llm/ollama_client.py, backend/app/services/agent/output_schema.py
OllamaClient imports and applies to_portable_schema(output_schema, drop_additional_properties=False) for the format payload field. Docstrings in both files clarify which providers use the portable schema and that length enforcement is delegated to _parse_response.
Payload verification test
backend/tests/test_llm_clients.py
test_ollama_client_sends_portable_schema captures the outgoing request via a monkeypatched httpx.AsyncClient and asserts the format field contains no oneOf, maxLength, or maxItems, and that project.field is folded into an enum.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐇 Hop hop, the grammar's clear,
No maxLength to cause llama fear,
oneOf dropped, the schema neat,
Portable format, what a treat!
The rabbit cheers — constraints complete! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 66.67% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedOllama の format に移植スキーマを渡して grammar 変換失敗を解消する内容で、変更の主旨を適切に表しています。
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/ollama-format-portable-schema

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@yusuke0610
yusuke0610 merged commit 5c5e5df into mainJun 28, 2026
17 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@yusuke0610
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(agent): Ollama の format に移植スキーマを渡し文法変換失敗を解消 - #422

Merged
yusuke0610 merged 1 commit into
mainfrom
fix/ollama-format-portable-schema
Jun 28, 2026
Merged

fix(agent): Ollama の format に移植スキーマを渡し文法変換失敗を解消#422
yusuke0610 merged 1 commit into
mainfrom
fix/ollama-format-portable-schema

Conversation

@yusuke0610

@yusuke0610yusuke0610 commented Jun 28, 2026

Copy link
Copy Markdown
Owner

概要

ローカル Ollama 経路(LLM_LOCAL_OLLAMA=1)で Agent チャットが 502 になっていた不具合を修正する。

原因

OllamaClient/api/chatformat生スキーマbuild_output_schema)を渡しており、その value.maxLength(2000) / suggestions.maxItems(4) を llama.cpp の JSON Schema → GBNF 文法変換が解釈できず、{"error":{"message":"Failed to initialize samplers: failed to parse grammar"}} の 400 を返していた。これが LLMError → 502 になっていた。

実機 curl で切り分け済み:

  • oneOf のみ → 200(無罪)
  • フルスキーマ → 400failed to parse grammar
  • maxLength/maxItems を除去(to_portable_schema 相当)→ 200

変更内容

  • ollama_client.py: formatto_portable_schema(output_schema, drop_additional_properties=False) を渡す(maxLength/maxItems 除去・oneOfenum 平坦化)。Gemini/OpenAI と同じ移植スキーマ経路に揃える。
  • output_schema.py: 「Ollama は maxLength を助言扱い/生スキーマをそのまま使う」という誤った前提のコメントを実態(数値制約で文法変換が壊れる)に修正。ロジックは無変更。
  • tests/test_llm_clients.py: 回帰テスト追加。Ollama に投げる formatoneOf/maxLength/maxItems が残らず、field が enum に畳まれることを検証。

影響範囲

  • 文字数上限の実強制は従来どおり chat_service._parse_response(二重防衛)が担うため品質は不変
  • Anthropic / Gemini / OpenAI 経路は無変更to_portable_schema の本体は触らず、Ollama を相乗りさせただけ)。

検証

  • make lint-backend: pass
  • make test-backend: pass(633 passed)
  • 実機: 移植スキーマ相当のペイロードが Ollama /api/chat で 200 を返すことを curl で確認

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved compatibility when sending structured responses to the AI service by converting schemas into a more portable format before requests are made.
    • Reduced failures caused by schema constraints that are not reliably supported by some model backends.
  • Tests

    • Added coverage to verify the request payload uses the portable schema format and preserves allowed field values.

ローカル Ollama(LLM_LOCAL_OLLAMA=1)経路で /api/chat が
"Failed to initialize samplers: failed to parse grammar" の 400 を返し、
Agent チャットが 502 になっていた。原因は format に渡す JSON Schema の
maxLength / maxItems を llama.cpp の GBNF 文法変換が解釈できないこと
(oneOf は無罪。実機 curl で maxLength/maxItems 除去後は 200 を確認)。
- ollama_client: format に to_portable_schema(..., drop_additional_properties=False)
を渡す(maxLength/maxItems 除去・oneOf→enum 平坦化)。Gemini/OpenAI と同じ移植経路に揃える
- output_schema: 「Ollama は maxLength を助言扱い/生スキーマを使う」誤コメントを実態へ修正
- test_llm_clients: format に oneOf/maxLength/maxItems が残らないことの回帰テストを追加
文字数上限の実強制は従来どおり _parse_response が担う(二重防衛)。
Anthropic/Gemini/OpenAI 経路は無変更。make lint-backend / make test-backend(633 passed) green。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Jun 28, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e66b3fd4-1b6a-4524-843f-878ad8ae8aa1

📥 Commits

Reviewing files that changed from the base of the PR and between 395b512 and fa75ff1.

📒 Files selected for processing (3)
  • backend/app/services/agent/llm/ollama_client.py
  • backend/app/services/agent/output_schema.py
  • backend/tests/test_llm_clients.py

📝 Walkthrough

Walkthrough

OllamaClient.generate now applies to_portable_schema to output_schema before placing it in the Ollama /api/chat request's format field, removing JSON Schema keywords (oneOf, maxLength, maxItems) that break llama.cpp grammar conversion. Docstrings in both files are updated, and a new test verifies the transformed payload.

Changes

Ollama Portable Schema

Layer / File(s)Summary
Schema transformation and docs
backend/app/services/agent/llm/ollama_client.py, backend/app/services/agent/output_schema.py
OllamaClient imports and applies to_portable_schema(output_schema, drop_additional_properties=False) for the format payload field. Docstrings in both files clarify which providers use the portable schema and that length enforcement is delegated to _parse_response.
Payload verification test
backend/tests/test_llm_clients.py
test_ollama_client_sends_portable_schema captures the outgoing request via a monkeypatched httpx.AsyncClient and asserts the format field contains no oneOf, maxLength, or maxItems, and that project.field is folded into an enum.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐇 Hop hop, the grammar's clear,
No maxLength to cause llama fear,
oneOf dropped, the schema neat,
Portable format, what a treat!
The rabbit cheers — constraints complete! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 66.67% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedOllama の format に移植スキーマを渡して grammar 変換失敗を解消する内容で、変更の主旨を適切に表しています。
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/ollama-format-portable-schema

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@yusuke0610
yusuke0610 merged commit 5c5e5df into mainJun 28, 2026
17 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@yusuke0610
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(agent): Ollama の format に移植スキーマを渡し文法変換失敗を解消 - #422

Merged
yusuke0610 merged 1 commit into
mainfrom
fix/ollama-format-portable-schema
Jun 28, 2026
Merged

fix(agent): Ollama の format に移植スキーマを渡し文法変換失敗を解消#422
yusuke0610 merged 1 commit into
mainfrom
fix/ollama-format-portable-schema

Conversation

@yusuke0610

@yusuke0610yusuke0610 commented Jun 28, 2026

Copy link
Copy Markdown
Owner

概要

ローカル Ollama 経路(LLM_LOCAL_OLLAMA=1)で Agent チャットが 502 になっていた不具合を修正する。

原因

OllamaClient/api/chatformat生スキーマbuild_output_schema)を渡しており、その value.maxLength(2000) / suggestions.maxItems(4) を llama.cpp の JSON Schema → GBNF 文法変換が解釈できず、{"error":{"message":"Failed to initialize samplers: failed to parse grammar"}} の 400 を返していた。これが LLMError → 502 になっていた。

実機 curl で切り分け済み:

  • oneOf のみ → 200(無罪)
  • フルスキーマ → 400failed to parse grammar
  • maxLength/maxItems を除去(to_portable_schema 相当)→ 200

変更内容

  • ollama_client.py: formatto_portable_schema(output_schema, drop_additional_properties=False) を渡す(maxLength/maxItems 除去・oneOfenum 平坦化)。Gemini/OpenAI と同じ移植スキーマ経路に揃える。
  • output_schema.py: 「Ollama は maxLength を助言扱い/生スキーマをそのまま使う」という誤った前提のコメントを実態(数値制約で文法変換が壊れる)に修正。ロジックは無変更。
  • tests/test_llm_clients.py: 回帰テスト追加。Ollama に投げる formatoneOf/maxLength/maxItems が残らず、field が enum に畳まれることを検証。

影響範囲

  • 文字数上限の実強制は従来どおり chat_service._parse_response(二重防衛)が担うため品質は不変
  • Anthropic / Gemini / OpenAI 経路は無変更to_portable_schema の本体は触らず、Ollama を相乗りさせただけ)。

検証

  • make lint-backend: pass
  • make test-backend: pass(633 passed)
  • 実機: 移植スキーマ相当のペイロードが Ollama /api/chat で 200 を返すことを curl で確認

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved compatibility when sending structured responses to the AI service by converting schemas into a more portable format before requests are made.
    • Reduced failures caused by schema constraints that are not reliably supported by some model backends.
  • Tests

    • Added coverage to verify the request payload uses the portable schema format and preserves allowed field values.

ローカル Ollama(LLM_LOCAL_OLLAMA=1)経路で /api/chat が
"Failed to initialize samplers: failed to parse grammar" の 400 を返し、
Agent チャットが 502 になっていた。原因は format に渡す JSON Schema の
maxLength / maxItems を llama.cpp の GBNF 文法変換が解釈できないこと
(oneOf は無罪。実機 curl で maxLength/maxItems 除去後は 200 を確認)。
- ollama_client: format に to_portable_schema(..., drop_additional_properties=False)
を渡す(maxLength/maxItems 除去・oneOf→enum 平坦化)。Gemini/OpenAI と同じ移植経路に揃える
- output_schema: 「Ollama は maxLength を助言扱い/生スキーマを使う」誤コメントを実態へ修正
- test_llm_clients: format に oneOf/maxLength/maxItems が残らないことの回帰テストを追加
文字数上限の実強制は従来どおり _parse_response が担う(二重防衛)。
Anthropic/Gemini/OpenAI 経路は無変更。make lint-backend / make test-backend(633 passed) green。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Jun 28, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e66b3fd4-1b6a-4524-843f-878ad8ae8aa1

📥 Commits

Reviewing files that changed from the base of the PR and between 395b512 and fa75ff1.

📒 Files selected for processing (3)
  • backend/app/services/agent/llm/ollama_client.py
  • backend/app/services/agent/output_schema.py
  • backend/tests/test_llm_clients.py

📝 Walkthrough

Walkthrough

OllamaClient.generate now applies to_portable_schema to output_schema before placing it in the Ollama /api/chat request's format field, removing JSON Schema keywords (oneOf, maxLength, maxItems) that break llama.cpp grammar conversion. Docstrings in both files are updated, and a new test verifies the transformed payload.

Changes

Ollama Portable Schema

Layer / File(s)Summary
Schema transformation and docs
backend/app/services/agent/llm/ollama_client.py, backend/app/services/agent/output_schema.py
OllamaClient imports and applies to_portable_schema(output_schema, drop_additional_properties=False) for the format payload field. Docstrings in both files clarify which providers use the portable schema and that length enforcement is delegated to _parse_response.
Payload verification test
backend/tests/test_llm_clients.py
test_ollama_client_sends_portable_schema captures the outgoing request via a monkeypatched httpx.AsyncClient and asserts the format field contains no oneOf, maxLength, or maxItems, and that project.field is folded into an enum.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐇 Hop hop, the grammar's clear,
No maxLength to cause llama fear,
oneOf dropped, the schema neat,
Portable format, what a treat!
The rabbit cheers — constraints complete! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 66.67% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedOllama の format に移植スキーマを渡して grammar 変換失敗を解消する内容で、変更の主旨を適切に表しています。
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/ollama-format-portable-schema

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@yusuke0610
yusuke0610 merged commit 5c5e5df into mainJun 28, 2026
17 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@yusuke0610
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(agent): Ollama の format に移植スキーマを渡し文法変換失敗を解消 - #422

Merged
yusuke0610 merged 1 commit into
mainfrom
fix/ollama-format-portable-schema
Jun 28, 2026
Merged

fix(agent): Ollama の format に移植スキーマを渡し文法変換失敗を解消#422
yusuke0610 merged 1 commit into
mainfrom
fix/ollama-format-portable-schema

Conversation

@yusuke0610

@yusuke0610yusuke0610 commented Jun 28, 2026

Copy link
Copy Markdown
Owner

概要

ローカル Ollama 経路(LLM_LOCAL_OLLAMA=1)で Agent チャットが 502 になっていた不具合を修正する。

原因

OllamaClient/api/chatformat生スキーマbuild_output_schema)を渡しており、その value.maxLength(2000) / suggestions.maxItems(4) を llama.cpp の JSON Schema → GBNF 文法変換が解釈できず、{"error":{"message":"Failed to initialize samplers: failed to parse grammar"}} の 400 を返していた。これが LLMError → 502 になっていた。

実機 curl で切り分け済み:

  • oneOf のみ → 200(無罪)
  • フルスキーマ → 400failed to parse grammar
  • maxLength/maxItems を除去(to_portable_schema 相当)→ 200

変更内容

  • ollama_client.py: formatto_portable_schema(output_schema, drop_additional_properties=False) を渡す(maxLength/maxItems 除去・oneOfenum 平坦化)。Gemini/OpenAI と同じ移植スキーマ経路に揃える。
  • output_schema.py: 「Ollama は maxLength を助言扱い/生スキーマをそのまま使う」という誤った前提のコメントを実態(数値制約で文法変換が壊れる)に修正。ロジックは無変更。
  • tests/test_llm_clients.py: 回帰テスト追加。Ollama に投げる formatoneOf/maxLength/maxItems が残らず、field が enum に畳まれることを検証。

影響範囲

  • 文字数上限の実強制は従来どおり chat_service._parse_response(二重防衛)が担うため品質は不変
  • Anthropic / Gemini / OpenAI 経路は無変更to_portable_schema の本体は触らず、Ollama を相乗りさせただけ)。

検証

  • make lint-backend: pass
  • make test-backend: pass(633 passed)
  • 実機: 移植スキーマ相当のペイロードが Ollama /api/chat で 200 を返すことを curl で確認

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved compatibility when sending structured responses to the AI service by converting schemas into a more portable format before requests are made.
    • Reduced failures caused by schema constraints that are not reliably supported by some model backends.
  • Tests

    • Added coverage to verify the request payload uses the portable schema format and preserves allowed field values.

ローカル Ollama(LLM_LOCAL_OLLAMA=1)経路で /api/chat が
"Failed to initialize samplers: failed to parse grammar" の 400 を返し、
Agent チャットが 502 になっていた。原因は format に渡す JSON Schema の
maxLength / maxItems を llama.cpp の GBNF 文法変換が解釈できないこと
(oneOf は無罪。実機 curl で maxLength/maxItems 除去後は 200 を確認)。
- ollama_client: format に to_portable_schema(..., drop_additional_properties=False)
を渡す(maxLength/maxItems 除去・oneOf→enum 平坦化)。Gemini/OpenAI と同じ移植経路に揃える
- output_schema: 「Ollama は maxLength を助言扱い/生スキーマを使う」誤コメントを実態へ修正
- test_llm_clients: format に oneOf/maxLength/maxItems が残らないことの回帰テストを追加
文字数上限の実強制は従来どおり _parse_response が担う(二重防衛)。
Anthropic/Gemini/OpenAI 経路は無変更。make lint-backend / make test-backend(633 passed) green。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Jun 28, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e66b3fd4-1b6a-4524-843f-878ad8ae8aa1

📥 Commits

Reviewing files that changed from the base of the PR and between 395b512 and fa75ff1.

📒 Files selected for processing (3)
  • backend/app/services/agent/llm/ollama_client.py
  • backend/app/services/agent/output_schema.py
  • backend/tests/test_llm_clients.py

📝 Walkthrough

Walkthrough

OllamaClient.generate now applies to_portable_schema to output_schema before placing it in the Ollama /api/chat request's format field, removing JSON Schema keywords (oneOf, maxLength, maxItems) that break llama.cpp grammar conversion. Docstrings in both files are updated, and a new test verifies the transformed payload.

Changes

Ollama Portable Schema

Layer / File(s)Summary
Schema transformation and docs
backend/app/services/agent/llm/ollama_client.py, backend/app/services/agent/output_schema.py
OllamaClient imports and applies to_portable_schema(output_schema, drop_additional_properties=False) for the format payload field. Docstrings in both files clarify which providers use the portable schema and that length enforcement is delegated to _parse_response.
Payload verification test
backend/tests/test_llm_clients.py
test_ollama_client_sends_portable_schema captures the outgoing request via a monkeypatched httpx.AsyncClient and asserts the format field contains no oneOf, maxLength, or maxItems, and that project.field is folded into an enum.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐇 Hop hop, the grammar's clear,
No maxLength to cause llama fear,
oneOf dropped, the schema neat,
Portable format, what a treat!
The rabbit cheers — constraints complete! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 66.67% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedOllama の format に移植スキーマを渡して grammar 変換失敗を解消する内容で、変更の主旨を適切に表しています。
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/ollama-format-portable-schema

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@yusuke0610
yusuke0610 merged commit 5c5e5df into mainJun 28, 2026
17 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@yusuke0610