diff --git a/pages/src/content/docs/en/configuration.md b/pages/src/content/docs/en/configuration.md index b8443cb..256711f 100644 --- a/pages/src/content/docs/en/configuration.md +++ b/pages/src/content/docs/en/configuration.md @@ -72,6 +72,47 @@ ocr config set custom_providers.my-gateway.model llama-3-70b ocr config set custom_providers.my-gateway.api_key "$MY_API_KEY" ``` +A local model served by Ollama is just a custom provider pointing at the +local OpenAI-compatible endpoint: + +```bash +ocr config set provider ollama +ocr config set custom_providers.ollama.url http://127.0.0.1:11434/v1 +ocr config set custom_providers.ollama.protocol openai +ocr config set custom_providers.ollama.model qwen3:32b +ocr config set custom_providers.ollama.api_key ollama +``` + +Ollama ignores the API key, but custom providers require a non-empty +`api_key` (there is no environment-variable fallback for them), so set +any placeholder value. The model itself must support native tool +calling — see +["No tool calls parsed" (local models / Ollama)](../faq/#no-tool-calls-parsed-local-models-ollama) +in the FAQ before picking one. + +### Timeouts + +Each LLM request has an HTTP timeout, defaulting to **300 seconds**. +Slow local models (or large files) can need more. Three knobs, in +increasing scope: + +- `providers..timeout_sec` / `custom_providers..timeout_sec` + — per-provider, in seconds. +- `llm.timeout_sec` — for the legacy `llm` section, in seconds. +- `OCR_LLM_TIMEOUT` environment variable — integer seconds; overrides + the config-file value for every resolution path. + +The `timeout_sec` keys are not supported by `ocr config set` — edit +`~/.opencodereview/config.json` directly: + +```json +{ + "custom_providers": { + "ollama": { "url": "http://127.0.0.1:11434/v1", "protocol": "openai", "timeout_sec": 900 } + } +} +``` + ### Verify connectivity ```bash diff --git a/pages/src/content/docs/en/faq.md b/pages/src/content/docs/en/faq.md index dbded44..72e0c23 100644 --- a/pages/src/content/docs/en/faq.md +++ b/pages/src/content/docs/en/faq.md @@ -53,6 +53,42 @@ OpenAI use different auth headers and different URL shapes — make sure against the current directory. If you're not inside a Git working tree, it exits early. Either `cd` into a repo, or pass `--repo /path/to/repo`. +### "No tool calls parsed" (local models / Ollama) + +``` +[ocr] No tool calls parsed for src/foo.go, retrying... +[ocr] Max tool requests reached for src/foo.go. +``` + +If every review loops through `No tool calls parsed` retries and ends +with "Max tool requests reached" and zero comments, the model — not the +config — is the problem. OCR drives the review entirely through tool +calls, so **the model must support native tool calling (function +calling)**. A model that merely *narrates* tool calls in its text +output (or inside `` blocks) can never work with OCR, no matter +how the prompt is tuned — `deepseek-r1` is a common example. Models +with native tool support, such as `qwen3`, work fine. For Ollama, pick +from the models tagged with tools support: +. + +Verify a local model directly, without OCR in the loop: + +```bash +curl http://127.0.0.1:11434/v1/chat/completions -H "Content-Type: application/json" -d '{ + "model": "qwen3:32b", + "messages": [{"role": "user", "content": "The code below has a bug, use the report_bug tool to report it.\n\nfunc add(a, b int) int {\n return a - b\n}"}], + "tools": [{"type": "function", "function": {"name": "report_bug", "description": "Report a bug in the code", + "parameters": {"type": "object", "properties": {"line": {"type": "integer"}, "description": {"type": "string"}}, "required": ["description"]}}}] +}' +``` + +Pass: the response contains a structured `tool_calls` array naming +`report_bug`. Fail: the "call" appears as text inside `content`. + +If the model *does* support tools but responses are slow on local +hardware, raise the LLM timeout instead — see +[Timeouts](../configuration/#timeouts). + ## Filtering & rules ### My file isn't being reviewed @@ -173,6 +209,9 @@ usually one of: `--max-tools 40` for more, `--max-tools 15` for fewer). Values 1–9 are clamped up to 10; `0` (the default) uses the template default of 30. +- The model does not support native tool calling at all (common with + local models) — see + ["No tool calls parsed" (local models / Ollama)](#no-tool-calls-parsed-local-models-ollama). ### Some sub-agents fail; the run still exits 0 diff --git a/pages/src/content/docs/ja/configuration.md b/pages/src/content/docs/ja/configuration.md index f60f569..f2729ed 100644 --- a/pages/src/content/docs/ja/configuration.md +++ b/pages/src/content/docs/ja/configuration.md @@ -70,6 +70,47 @@ ocr config set custom_providers.my-gateway.model llama-3-70b ocr config set custom_providers.my-gateway.api_key "$MY_API_KEY" ``` +Ollama で動かすローカルモデルは、ローカルの OpenAI 互換エンドポイントを +指すカスタム provider にすぎません。 + +```bash +ocr config set provider ollama +ocr config set custom_providers.ollama.url http://127.0.0.1:11434/v1 +ocr config set custom_providers.ollama.protocol openai +ocr config set custom_providers.ollama.model qwen3:32b +ocr config set custom_providers.ollama.api_key ollama +``` + +Ollama は API key を無視しますが、カスタム provider は空でない `api_key` を +必要とします(カスタム provider には環境変数のフォールバックがありません)。 +そのため任意のプレースホルダー値を設定してください。モデル自体はネイティブな +ツール呼び出しをサポートしている必要があります——選ぶ前に FAQ の +["No tool calls parsed"(ローカルモデル / Ollama)](../faq/#no-tool-calls-parsed-ollama)を +参照してください。 + +### タイムアウト(Timeouts) + +各 LLM リクエストには HTTP タイムアウトがあり、デフォルトは **300 秒**です。 +遅いローカルモデル(あるいは大きなファイル)では、それ以上の時間が必要になることがあります。 +スコープの狭い順に、3 つの設定があります。 + +- `providers..timeout_sec` / `custom_providers..timeout_sec` + ——provider ごと、秒単位。 +- `llm.timeout_sec`——レガシーな `llm` セクション用、秒単位。 +- `OCR_LLM_TIMEOUT` 環境変数——整数(秒単位)。すべての解決パスで設定ファイルの + 値を上書きします。 + +`timeout_sec` key は `ocr config set` ではサポートされていません—— +`~/.opencodereview/config.json` を直接編集してください。 + +```json +{ + "custom_providers": { + "ollama": { "url": "http://127.0.0.1:11434/v1", "protocol": "openai", "timeout_sec": 900 } + } +} +``` + ### 接続性を検証する ```bash diff --git a/pages/src/content/docs/ja/faq.md b/pages/src/content/docs/ja/faq.md index b2d541a..985d0f6 100644 --- a/pages/src/content/docs/ja/faq.md +++ b/pages/src/content/docs/ja/faq.md @@ -53,6 +53,39 @@ OpenAI は異なる auth header と URL フォーマットを使います——` `git ls-files`)を実行します。Git ワークツリー内にいない場合は、早期に終了します。リポジトリに `cd` するか、`--repo /path/to/repo` を渡してください。 +### "No tool calls parsed"(ローカルモデル / Ollama) + +``` +[ocr] No tool calls parsed for src/foo.go, retrying... +[ocr] Max tool requests reached for src/foo.go. +``` + +すべてのレビューが `No tool calls parsed` のリトライをループし、"Max tool requests +reached" とコメント 0 件で終わる場合、問題は設定ではなくモデルにあります。OCR はレビュー全体を +ツール呼び出しで駆動するため、**モデルはネイティブなツール呼び出し(function calling)を +サポートしている必要があります**。ツール呼び出しをテキスト出力(あるいは `` ブロック内)で +*語るだけ*のモデルは、prompt をどう調整しても OCR では決して動作しません——`deepseek-r1` は +よくある例です。`qwen3` のようなネイティブなツールサポートを持つモデルは問題なく動作します。 +Ollama の場合は、tools サポートのタグが付いたモデルから選んでください: +。 + +OCR を介さずに、ローカルモデルを直接検証するには: + +```bash +curl http://127.0.0.1:11434/v1/chat/completions -H "Content-Type: application/json" -d '{ + "model": "qwen3:32b", + "messages": [{"role": "user", "content": "The code below has a bug, use the report_bug tool to report it.\n\nfunc add(a, b int) int {\n return a - b\n}"}], + "tools": [{"type": "function", "function": {"name": "report_bug", "description": "Report a bug in the code", + "parameters": {"type": "object", "properties": {"line": {"type": "integer"}, "description": {"type": "string"}}, "required": ["description"]}}}] +}' +``` + +合格: 応答に `report_bug` を指す構造化された `tool_calls` 配列が含まれる。不合格: 「呼び出し」が +`content` 内のテキストとして現れる。 + +モデルがツールを*サポートしている*のに、ローカルハードウェアで応答が遅い場合は、代わりに +LLM タイムアウトを引き上げてください——[タイムアウト](../configuration/#timeouts)を参照。 + ## フィルタリングとルール ### ファイルがレビューされない @@ -163,6 +196,9 @@ JSON モードでは `warnings` にも表示されます。 - ファイルが本当に大きい、あるいはコンテキストが重く、30 回では足りない。`--max-tools ` で 上げるか下げるか調整してください(例: `--max-tools 40` でより多く、`--max-tools 15` でより少なく)。 1〜9 は 10 に引き上げられます。`0`(デフォルト)はテンプレートのデフォルト 30 を使います。 +- モデルがネイティブなツール呼び出しを全くサポートしていない(ローカルモデルでよくある)—— + ["No tool calls parsed"(ローカルモデル / Ollama)](#no-tool-calls-parsed-ollama)を + 参照してください。 ### 一部のサブエージェントが失敗しても、実行は 0 で終了する diff --git a/pages/src/content/docs/zh/configuration.md b/pages/src/content/docs/zh/configuration.md index 51ad3b1..796ec0c 100644 --- a/pages/src/content/docs/zh/configuration.md +++ b/pages/src/content/docs/zh/configuration.md @@ -68,6 +68,43 @@ ocr config set custom_providers.my-gateway.model llama-3-70b ocr config set custom_providers.my-gateway.api_key "$MY_API_KEY" ``` +用 Ollama 跑本地模型,就是一个指向本地 OpenAI 兼容端点的自定义 provider: + +```bash +ocr config set provider ollama +ocr config set custom_providers.ollama.url http://127.0.0.1:11434/v1 +ocr config set custom_providers.ollama.protocol openai +ocr config set custom_providers.ollama.model qwen3:32b +ocr config set custom_providers.ollama.api_key ollama +``` + +Ollama 会忽略 API key,但自定义 provider 要求非空的 `api_key`(自定义 +provider 没有环境变量回退),所以设任意占位值即可。模型本身必须支持原生 +工具调用——选型前请先看 FAQ 中的 +["No tool calls parsed"(本地模型 / Ollama)](../faq/#no-tool-calls-parsed-本地模型-ollama)。 + +### 超时 + +每个 LLM 请求都有 HTTP 超时,默认 **300 秒**。慢的本地模型(或大文件)可能 +需要更长的时间。三个配置项,作用域递增: + +- `providers..timeout_sec` / `custom_providers..timeout_sec` + ——per-provider,单位秒。 +- `llm.timeout_sec`——用于旧版 `llm` 配置段,单位秒。 +- `OCR_LLM_TIMEOUT` 环境变量——整数秒;对每条解析路径都覆盖配置文件里 + 的值。 + +`ocr config set` 不支持 `timeout_sec` key——直接编辑 +`~/.opencodereview/config.json`: + +```json +{ + "custom_providers": { + "ollama": { "url": "http://127.0.0.1:11434/v1", "protocol": "openai", "timeout_sec": 900 } + } +} +``` + ### 验证连通性 ```bash diff --git a/pages/src/content/docs/zh/faq.md b/pages/src/content/docs/zh/faq.md index 838c8b7..38b35ef 100644 --- a/pages/src/content/docs/zh/faq.md +++ b/pages/src/content/docs/zh/faq.md @@ -48,6 +48,38 @@ URL 格式——确保 `llm.use_anthropic` 与你指向的 URL 相匹配: `ocr review` 对当前目录运行 `git diff`(以及对 untracked 文件的 `git ls-files`)。 若你不在 Git 工作树内,它会提前退出。要么 `cd` 进仓库,要么传 `--repo /path/to/repo`。 +### "No tool calls parsed"(本地模型 / Ollama) + +``` +[ocr] No tool calls parsed for src/foo.go, retrying... +[ocr] Max tool requests reached for src/foo.go. +``` + +若每次评审都在 `No tool calls parsed` 重试中循环,最终以 "Max tool requests +reached" 结束且没有任何评论,问题出在模型——而非配置。OCR 完全通过工具调用驱动评审, +因此**模型必须支持原生工具调用(function calling)**。只在文本输出(或 +`` 块内)*叙述*工具调用的模型,无论怎么调 prompt 都永远无法与 OCR +配合使用——`deepseek-r1` 是常见例子。具备原生工具支持的模型(如 `qwen3`)则工作 +正常。对 Ollama,请从带 tools 标签的模型中挑选: +。 + +绕开 OCR、直接验证本地模型: + +```bash +curl http://127.0.0.1:11434/v1/chat/completions -H "Content-Type: application/json" -d '{ + "model": "qwen3:32b", + "messages": [{"role": "user", "content": "The code below has a bug, use the report_bug tool to report it.\n\nfunc add(a, b int) int {\n return a - b\n}"}], + "tools": [{"type": "function", "function": {"name": "report_bug", "description": "Report a bug in the code", + "parameters": {"type": "object", "properties": {"line": {"type": "integer"}, "description": {"type": "string"}}, "required": ["description"]}}}] +}' +``` + +通过:响应包含指向 `report_bug` 的结构化 `tool_calls` 数组。失败:“调用”以 +文本形式出现在 `content` 里。 + +若模型*确实*支持工具,只是在本地硬件上响应缓慢,请改为调高 LLM 超时——见 +[超时](../configuration/#超时)。 + ## 过滤与规则 ### 我的文件没被评审 @@ -150,6 +182,8 @@ diff 能从 plan 中受益。要为单次评审跳过它,用更小 diff 运行 - 文件确实大或上下文重,30 轮不够。用 `--max-tools ` 调高或调低 (如 `--max-tools 40` 更多,`--max-tools 15` 更少)。1–9 会被上调到 10; `0`(默认)用模板默认 30。 +- 模型完全不支持原生工具调用(本地模型常见)——见 + ["No tool calls parsed"(本地模型 / Ollama)](#no-tool-calls-parsed-本地模型-ollama)。 ### 一些子 agent 失败;运行仍以 0 退出 diff --git a/pages/src/pages/DocsPage.tsx b/pages/src/pages/DocsPage.tsx index fd1657b..626bf07 100644 --- a/pages/src/pages/DocsPage.tsx +++ b/pages/src/pages/DocsPage.tsx @@ -11,6 +11,16 @@ import docContentsIcon from '../assets/icons/doc-contents.svg'; import searchIcon from '../assets/icons/icon-search.svg'; import '../styles/docs-markdown.css'; +// marked percent-encodes non-ASCII hrefs; heading ids are raw text from +// generateHeadingId, so fragments must be decoded before lookup. +function decodeFragment(fragment: string): string { + try { + return decodeURIComponent(fragment); + } catch { + return fragment; + } +} + /* ─── Sidebar tree data ─── */ interface SidebarItem { id: string; @@ -198,7 +208,7 @@ const DocsPage: React.FC = () => { // Skip pure anchors (same-page scroll) if (href.startsWith('#')) { e.preventDefault(); - const id = href.slice(1); + const id = decodeFragment(href.slice(1)); const el = document.getElementById(id); if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' }); return; @@ -217,7 +227,8 @@ const DocsPage: React.FC = () => { e.preventDefault(); navigateToDoc(slug); // Handle anchor scroll after navigation with reliable retry - const anchor2 = href.split('#')[1]; + const anchor2raw = href.split('#')[1]; + const anchor2 = anchor2raw ? decodeFragment(anchor2raw) : undefined; if (anchor2) { const tryScroll = (attempts: number) => { const el = document.getElementById(anchor2);