Tencent WeKnora を Gemini で動かしたら、embedding で2回ハマった話 ―― 404とtruncate_prompt_tokens、そしてローカルOllamaでの回避

Tencent WeKnora を Gemini で動かしたら、embedding で2回ハマった話 ―― 404とtruncate_prompt_tokens、そしてローカルOllamaでの回避

Tencent WeKnora を Gemini で動かしたら、embedding で2回ハマった話 ―― 404とtruncate_prompt_tokens、そしてローカルOllamaでの回避

Tencent が公開しているOSSのナレッジ基盤「WeKnora」を、社内文書RAGの検証用に手元の Mac で動かしてみました。

セットアップ自体はサクサク進んだのですが、「埋め込み(embedding)に Gemini を使う」ところで立て続けにつまずきました。原因にたどり着くまでにけっこう寄り道したので、その過程をそのまま残しておきます。同じ構成で同じところに引っかかる人がいたら、参考になればと思います。気軽に読んでもらえたら。

なお、ここに書いた内容はすべて検証時点で手元で再現したものです。WeKnora は更新が速いので、仕様が変わっていそうなところは都度ことわりを入れています。

検証した環境

  • マシン:Apple Silicon の Mac / メモリ 8GB

  • 動かし方:Docker Compose(コア構成だけ。Neo4j や Langfuse まで入れるフル構成は、メモリの都合で今回は見送り)

  • WeKnora:検証時点の :latest イメージ(途中で v0.6.1 に更新)

  • 最初に使おうとしたモデル

    • 埋め込み:gemini-embedding-001(Gemini API の OpenAI 互換エンドポイント経由)

    • LLM:gemini-2.5-flash

メモリ 8GB ということもあって、最初は「LLM も埋め込みも全部クラウド(Gemini)に任せよう」と考えていました。……が、この方針が後々の伏線になります。

つまずき①:embedding が 404 になる(犯人は末尾の /

どうなったか

文書をアップロードすると、ステータスが 「Parsing failed」。あっさり失敗しました。

▲ アップロードした文書が軒並み「Parsing failed」。ここから原因の切り分けが始まりました

原因をどう絞ったか

まずは「解析(テキストを読み取る段階)」と「埋め込み(ベクトル化する段階)」のどっちでコケているのか、を切り分けました。

解析を担当する docreader のログを見ると、PDF も Markdown もテキスト抽出はちゃんと成功しています。


ということは、コケているのはその次の段階、app 側(チャンク分割と埋め込み)です。app のログを見たら、原因がはっきり書いてありました。


なぜ404だったか、どう直したか

404 は「そのURL、存在しませんよ」というサイン。Gemini の OpenAI 互換エンドポイントで embedding を叩くこと自体はできる(curl で直接叩くとちゃんと届く)のに、なぜか404。

犯人は、WeKnora に設定した Base URL の末尾に付けていた / でした。

  • 設定していた値(NG):https://generativelanguage.googleapis.com/v1beta/openai/

  • 直したあと(OK):https://generativelanguage.googleapis.com/v1beta/openai

末尾スラッシュを外したら、エラーが 404 から 400 に変わりました。「URLが見つからない」状態は抜けて、リクエストがエンドポイントまで届くようになった、ということです。

ちょっと補足(ここは推測です):末尾に / があると、WeKnora がURLを組み立てるときに .../openai//embeddings のようにスラッシュが二重になって 404 になっていたのかな、と思っています。内部の挙動までは追っていないので推測どまりですが、「末尾スラッシュを外したら404が消えた」のは事実です。

つまずき②:今度は truncate_prompt_tokens で 400

どうなったか

404 は消えたのに、入れ替わりで今度はこれが出るように。

EmbedBatch API error: Http Status 400 Bad Request
"Invalid JSON payload received. Unknown name \"truncate_prompt_tokens\"

なぜ起きるか

WeKnora の埋め込み処理が、リクエストに truncate_prompt_tokens というフィールドを乗せて送っていて、Gemini 側が「そんなフィールド知らない」とリクエストごと突き返していた、というのが原因です。

調べてみたら、これは WeKnora 側の既知の不具合で、リリースノートにこんな修正が載っていました。

fix(embedding): remove hardcoded TruncatePromptTokens in BatchEmbed

「BatchEmbed にハードコードされていた TruncatePromptTokens を取り除く」という修正です。コードに直接埋め込まれていた値なので、設定画面から無効化する、という逃げ方はできませんでした。

修正版に上げようとしたものの……

直っているはずの新しい版に上げようと、本体を更新しました。

git pull
docker compose pull
docker compose up -d

ソースは v0.6.1 まで上がったのですが、docker compose pull で取ってきた :latest イメージにはまだ修正が入っておらず、同じ 400 がそのまま再発しました。ここはちょっと拍子抜けでした。

補足(推測):公開イメージがソースの修正に追いついていなかったか、修正が当該タグにまだ含まれていなかったか、のどちらかだと思います。そこまでは確認できていません。確実なのは「更新後も :latest では同じエラーが出た」という結果だけです。ソースから自前でビルドする方法は、今回は試していません。

抜け道:埋め込みだけ、ローカルの Ollama に逃がす

Gemini 経由の埋め込みがバグで止まってしまうので、思い切って埋め込みだけをローカルの Ollama に切り替え、LLM はクラウドの Gemini のまま残すことにしました。埋め込みモデルは LLM に比べてずっと軽いので、メモリ 8GB でも「埋め込みだけなら」十分動かせる、という読みです。

やったことの要点

  1. Ollama を入れる(Homebrew 版はインストールでつまずきました。llama-server のバイナリが見つからず推論できなかったので、公式アプリ版に切り替え)

  2. 日本語に強い埋め込みモデルを取得

  3. WeKnora に Ollama 埋め込みモデルを登録

    • Source:Ollama (Local)

    • Model:bge-m3

    • Vector Dimension:1024

  4. 既存のナレッジベースは旧モデルで作っていたので、埋め込みを bge-m3 にして作り直し

▲ Source を「Ollama (Local)」にして bge-m3 を登録。次元は 1024

▲ ナレッジベースを作り直し、埋め込みに bge-m3、LLM に gemini-2.5-flash を指定

Docker からホストの Ollama に繋ぐときの小さな罠

WeKnora は Docker コンテナの中で動いているので、コンテナから見た localhost は「コンテナ自身」を指してしまいます。ホストの Mac で動いている Ollama に繋ぐには、localhost ではなく http://host.docker.internal:11434 を使います。ここは地味に間違えやすいポイントです。

補足:今回使った版では、Ollama のモデル名が登録フォームのドロップダウンに自動で出てきて、接続は特別な設定なしで通りました。版や環境によっては、Ollama 側を全インターフェース受け付け(OLLAMA_HOST=0.0.0.0)にする必要が出る場合もあります。

結果:日本語RAG、ちゃんと動きました

最終構成(埋め込み:bge-m3 / LLM:gemini-2.5-flash)で、架空の社内ハンドブック(FAQ形式のサンプル)を取り込んで、日本語で質問してみました。結果は、ぜんぶ文書どおりの答えが返ってきました。

<!-- ▼ 画像④:取り込み成功の画面(おすすめ:16_20_04 / 中身プレビューが出て Parsing failed が消えたスクショ) -->

▲ 埋め込みをローカル Ollama にしたら、今度はあっさり成功(中身のプレビューが表示され、エラーが消えた)

質問

回答

文書の記載

夏季休暇は何日とれる?

5日間

一致

リモートワークは週に何日まで?

週最大3日

一致

経費の締め日はいつ?

毎月25日

一致

健康診断はいつ実施される?

年1回・毎年10月

一致

在宅勤務手当はいくら?

月額3,000円

一致

ローカルの bge-m3 でも、日本語の意味をちゃんと拾って正しい答えを返してくれました。ここはシンプルに嬉しかったところです。

実際のやりとりを、順に貼っていきます。


▲ 最初の回答は中国語。「日本語で答えて」を添えると、以降は日本語で返ってくるように(夏季休暇は5日間)

▲ 「月額3,000円」も正確に回答

ひとつだけ注意:回答の言語

ちょっと面白かったのが、最初の回答が中国語で返ってきたこと(上の1枚目)。WeKnora は内部のプロンプトが中国語ベースなので、それに引っ張られたのだと思います(画面の表示言語の設定とは別物です)。質問に「日本語で答えて」と添えると日本語に切り替わり、その後は同じ会話の中ではずっと日本語のままでした。常に日本語で使いたいなら、システムプロンプト側で言語を指定しておくのがよさそうです。

まとめ・ふりかえり

  • WeKnora 自体は導入は簡単。ただ、Gemini を OpenAI 互換エンドポイントで使うなら、埋め込みまわりに2つ落とし穴がある

    • Base URL の末尾スラッシュで 404。

    • truncate_prompt_tokens の送信で 400(WeKnora 側の既知不具合。修正PRはあるが、検証時の :latest には入っていなかった)。

  • 詰まったら、ログを「docreader(解析)」と「app(埋め込み)」に分けて見ると早い。404 なのか 400 なのか、メッセージのフィールド名まで読むと一気に原因に近づけます。

  • どうにもならないときは、埋め込みをローカル Ollama に逃がすのが確実。LLM はクラウドのまま使えるので、「埋め込みローカル+LLMクラウド」のハイブリッドは、データを外に出さない社内文書RAGとしてもけっこう理にかなっています。

  • 回答の言語は中国語寄りがデフォルト。日本語で使うなら、プロンプトでの言語指定を前提にしておくと安心です。

この記事の数値やエラーは、検証時点で実際に確認したものです。WeKnora は更新が活発なので、truncate_prompt_tokens の不具合は新しいイメージではもう直っているかもしれません。最新版で試すときは、まず Gemini 構成のまま取り込みが通るか確かめて、ダメだったらこの記事のローカル Ollama 構成に切り替える、という順番がおすすめです。

Tencent WeKnora を Gemini で動かしたら、embedding で2回ハマった話 ―― 404とtruncate_prompt_tokens、そしてローカルOllamaでの回避

Tencent が公開しているOSSのナレッジ基盤「WeKnora」を、社内文書RAGの検証用に手元の Mac で動かしてみました。

セットアップ自体はサクサク進んだのですが、「埋め込み(embedding)に Gemini を使う」ところで立て続けにつまずきました。原因にたどり着くまでにけっこう寄り道したので、その過程をそのまま残しておきます。同じ構成で同じところに引っかかる人がいたら、参考になればと思います。気軽に読んでもらえたら。

なお、ここに書いた内容はすべて検証時点で手元で再現したものです。WeKnora は更新が速いので、仕様が変わっていそうなところは都度ことわりを入れています。

検証した環境

  • マシン:Apple Silicon の Mac / メモリ 8GB

  • 動かし方:Docker Compose(コア構成だけ。Neo4j や Langfuse まで入れるフル構成は、メモリの都合で今回は見送り)

  • WeKnora:検証時点の :latest イメージ(途中で v0.6.1 に更新)

  • 最初に使おうとしたモデル

    • 埋め込み:gemini-embedding-001(Gemini API の OpenAI 互換エンドポイント経由)

    • LLM:gemini-2.5-flash

メモリ 8GB ということもあって、最初は「LLM も埋め込みも全部クラウド(Gemini)に任せよう」と考えていました。……が、この方針が後々の伏線になります。

つまずき①:embedding が 404 になる(犯人は末尾の /

どうなったか

文書をアップロードすると、ステータスが 「Parsing failed」。あっさり失敗しました。

▲ アップロードした文書が軒並み「Parsing failed」。ここから原因の切り分けが始まりました

原因をどう絞ったか

まずは「解析(テキストを読み取る段階)」と「埋め込み(ベクトル化する段階)」のどっちでコケているのか、を切り分けました。

解析を担当する docreader のログを見ると、PDF も Markdown もテキスト抽出はちゃんと成功しています。


ということは、コケているのはその次の段階、app 側(チャンク分割と埋め込み)です。app のログを見たら、原因がはっきり書いてありました。


なぜ404だったか、どう直したか

404 は「そのURL、存在しませんよ」というサイン。Gemini の OpenAI 互換エンドポイントで embedding を叩くこと自体はできる(curl で直接叩くとちゃんと届く)のに、なぜか404。

犯人は、WeKnora に設定した Base URL の末尾に付けていた / でした。

  • 設定していた値(NG):https://generativelanguage.googleapis.com/v1beta/openai/

  • 直したあと(OK):https://generativelanguage.googleapis.com/v1beta/openai

末尾スラッシュを外したら、エラーが 404 から 400 に変わりました。「URLが見つからない」状態は抜けて、リクエストがエンドポイントまで届くようになった、ということです。

ちょっと補足(ここは推測です):末尾に / があると、WeKnora がURLを組み立てるときに .../openai//embeddings のようにスラッシュが二重になって 404 になっていたのかな、と思っています。内部の挙動までは追っていないので推測どまりですが、「末尾スラッシュを外したら404が消えた」のは事実です。

つまずき②:今度は truncate_prompt_tokens で 400

どうなったか

404 は消えたのに、入れ替わりで今度はこれが出るように。

EmbedBatch API error: Http Status 400 Bad Request
"Invalid JSON payload received. Unknown name \"truncate_prompt_tokens\"

なぜ起きるか

WeKnora の埋め込み処理が、リクエストに truncate_prompt_tokens というフィールドを乗せて送っていて、Gemini 側が「そんなフィールド知らない」とリクエストごと突き返していた、というのが原因です。

調べてみたら、これは WeKnora 側の既知の不具合で、リリースノートにこんな修正が載っていました。

fix(embedding): remove hardcoded TruncatePromptTokens in BatchEmbed

「BatchEmbed にハードコードされていた TruncatePromptTokens を取り除く」という修正です。コードに直接埋め込まれていた値なので、設定画面から無効化する、という逃げ方はできませんでした。

修正版に上げようとしたものの……

直っているはずの新しい版に上げようと、本体を更新しました。

git pull
docker compose pull
docker compose up -d

ソースは v0.6.1 まで上がったのですが、docker compose pull で取ってきた :latest イメージにはまだ修正が入っておらず、同じ 400 がそのまま再発しました。ここはちょっと拍子抜けでした。

補足(推測):公開イメージがソースの修正に追いついていなかったか、修正が当該タグにまだ含まれていなかったか、のどちらかだと思います。そこまでは確認できていません。確実なのは「更新後も :latest では同じエラーが出た」という結果だけです。ソースから自前でビルドする方法は、今回は試していません。

抜け道:埋め込みだけ、ローカルの Ollama に逃がす

Gemini 経由の埋め込みがバグで止まってしまうので、思い切って埋め込みだけをローカルの Ollama に切り替え、LLM はクラウドの Gemini のまま残すことにしました。埋め込みモデルは LLM に比べてずっと軽いので、メモリ 8GB でも「埋め込みだけなら」十分動かせる、という読みです。

やったことの要点

  1. Ollama を入れる(Homebrew 版はインストールでつまずきました。llama-server のバイナリが見つからず推論できなかったので、公式アプリ版に切り替え)

  2. 日本語に強い埋め込みモデルを取得

  3. WeKnora に Ollama 埋め込みモデルを登録

    • Source:Ollama (Local)

    • Model:bge-m3

    • Vector Dimension:1024

  4. 既存のナレッジベースは旧モデルで作っていたので、埋め込みを bge-m3 にして作り直し

▲ Source を「Ollama (Local)」にして bge-m3 を登録。次元は 1024

▲ ナレッジベースを作り直し、埋め込みに bge-m3、LLM に gemini-2.5-flash を指定

Docker からホストの Ollama に繋ぐときの小さな罠

WeKnora は Docker コンテナの中で動いているので、コンテナから見た localhost は「コンテナ自身」を指してしまいます。ホストの Mac で動いている Ollama に繋ぐには、localhost ではなく http://host.docker.internal:11434 を使います。ここは地味に間違えやすいポイントです。

補足:今回使った版では、Ollama のモデル名が登録フォームのドロップダウンに自動で出てきて、接続は特別な設定なしで通りました。版や環境によっては、Ollama 側を全インターフェース受け付け(OLLAMA_HOST=0.0.0.0)にする必要が出る場合もあります。

結果:日本語RAG、ちゃんと動きました

最終構成(埋め込み:bge-m3 / LLM:gemini-2.5-flash)で、架空の社内ハンドブック(FAQ形式のサンプル)を取り込んで、日本語で質問してみました。結果は、ぜんぶ文書どおりの答えが返ってきました。

<!-- ▼ 画像④:取り込み成功の画面(おすすめ:16_20_04 / 中身プレビューが出て Parsing failed が消えたスクショ) -->

▲ 埋め込みをローカル Ollama にしたら、今度はあっさり成功(中身のプレビューが表示され、エラーが消えた)

質問

回答

文書の記載

夏季休暇は何日とれる?

5日間

一致

リモートワークは週に何日まで?

週最大3日

一致

経費の締め日はいつ?

毎月25日

一致

健康診断はいつ実施される?

年1回・毎年10月

一致

在宅勤務手当はいくら?

月額3,000円

一致

ローカルの bge-m3 でも、日本語の意味をちゃんと拾って正しい答えを返してくれました。ここはシンプルに嬉しかったところです。

実際のやりとりを、順に貼っていきます。


▲ 最初の回答は中国語。「日本語で答えて」を添えると、以降は日本語で返ってくるように(夏季休暇は5日間)

▲ 「月額3,000円」も正確に回答

ひとつだけ注意:回答の言語

ちょっと面白かったのが、最初の回答が中国語で返ってきたこと(上の1枚目)。WeKnora は内部のプロンプトが中国語ベースなので、それに引っ張られたのだと思います(画面の表示言語の設定とは別物です)。質問に「日本語で答えて」と添えると日本語に切り替わり、その後は同じ会話の中ではずっと日本語のままでした。常に日本語で使いたいなら、システムプロンプト側で言語を指定しておくのがよさそうです。

まとめ・ふりかえり

  • WeKnora 自体は導入は簡単。ただ、Gemini を OpenAI 互換エンドポイントで使うなら、埋め込みまわりに2つ落とし穴がある

    • Base URL の末尾スラッシュで 404。

    • truncate_prompt_tokens の送信で 400(WeKnora 側の既知不具合。修正PRはあるが、検証時の :latest には入っていなかった)。

  • 詰まったら、ログを「docreader(解析)」と「app(埋め込み)」に分けて見ると早い。404 なのか 400 なのか、メッセージのフィールド名まで読むと一気に原因に近づけます。

  • どうにもならないときは、埋め込みをローカル Ollama に逃がすのが確実。LLM はクラウドのまま使えるので、「埋め込みローカル+LLMクラウド」のハイブリッドは、データを外に出さない社内文書RAGとしてもけっこう理にかなっています。

  • 回答の言語は中国語寄りがデフォルト。日本語で使うなら、プロンプトでの言語指定を前提にしておくと安心です。

この記事の数値やエラーは、検証時点で実際に確認したものです。WeKnora は更新が活発なので、truncate_prompt_tokens の不具合は新しいイメージではもう直っているかもしれません。最新版で試すときは、まず Gemini 構成のまま取り込みが通るか確かめて、ダメだったらこの記事のローカル Ollama 構成に切り替える、という順番がおすすめです。