技術約13分で読めます

検索した記憶をQwenに渡すとキャラの会話はどう変わるか

いけさん目次

前回で埋め込みとQdrantの速度は測り終わったので、次は検索した記憶をQwenに渡したときの会話そのものを試してみた。
設計記事を書いた時点では、履歴を全部渡すとキャラが履歴に引っ張られる問題があって、何をどう渡すかで挙動が変わるだろう、までしか言えていなかった。

記憶の処理は全部サーバー側で、CoreS3は音声を送って受け取るだけ。
なので組み込む予定のStackChan Bodyを組む前に、PCだけで試せる。
記憶と質問を用意して流し始めた。

埋め込みが400で返ってきた。

検証環境

項目内容
PC前回と同じ本番機(Windows 11 Home、AMD Ryzen 7 5800HS、RAM16GB)
チャットModelScope API-Inference、Qwen-Ambassador/Qwen3.7-Plusenable_thinking: false
埋め込みQwen3-Embedding-0.6B、256次元。API優先で、失敗したらローカルCPUに切り替える(後述)
ベクトル検索Qdrantローカルモード、コサイン類似度、上位3件
しきい値類似度0.45未満の記憶は渡さない
ペルソナ「かな」の2行だけのテスト用プロンプト。本番のVoiceChatサーバーのプロンプトではない
直近履歴「ただいまー」「おかえり!」の1往復だけ

256次元は前回記事の構成そのまま。上位3件としきい値0.45は、このテストで初めて決めた値。

用意した記憶と質問

記憶は「日付 + 事実文」の形で16件書いた。
設計記事で決めたとおり会話の断片ではなく要約した事実文で、矛盾したときにどちらを取るかを確かめるために日付は全件に付けた。

2026-08-15: ユーザーの好物は明太子パスタ。辛いものも好きだと言っていた。
2026-08-20: ユーザーはコーヒーはブラック派だと言っていた。
2026-08-21: ユーザーは毎朝散歩していると言っていた。
2026-08-22: 週末に長野の温泉旅行に行った。露天風呂から星が見えて感動していた。
2026-08-28: TTSサーバーの安定化作業をしていて疲れていた。
2026-09-01: 最近は甘いカフェオレばかり飲んでいると言っていた。
2026-09-03: 新しいキーボードを買うかずっと迷っている。
2026-09-07: 早起きが続かず、朝の散歩はやめて夜の散歩に切り替えたと言っていた。
2026-09-09: 埋め込みとQdrantのベンチマーク記事を書き上げた。
(ほか7件)

「ユーザーは」で始まる行と主語のない行が混ざっているが、16件を手で書いたら気にしないままこうなっていた。
ほか7件には、台風と雨の話、3Dプリント、ロボットアニメの話が入っている。

質問は10問。

種類ID質問期待
ヒット想定H1前に好きって言った食べ物何だっけ?明太子パスタ
ヒット想定H2この前やってた作業どうなった?複数候補からの選択
ヒット想定H3前に話した温泉のこと覚えてる?長野・露天風呂・星
ヒット想定H4キーボードの件どうなったか覚えてる?まだ迷っている
ヒットなし想定N1量子コンピュータってどう思う?記憶なしで普通に答える
ヒットなし想定N2おすすめの映画ある?同上
ヒットなし想定N3サッカーのワールドカップ見てる?同上
ヒットなし想定N4宝くじ当たったら何する?同上
矛盾C1私コーヒーどうやって飲むんだっけ?新しい方(カフェオレ)を取るか
矛盾C2私って散歩いつ行くんだっけ?新しい方(夜散歩)を取るか

渡し方は4通り。

形式渡し方
OFF記憶を渡さない対照
Aシステムプロンプトに「過去の会話の記憶:」として生テキストで混ぜる。使い方の指示なし
B<retrieved_memory> ブロックで囲み、「参考情報。関係する場合だけ使い、聞かれない限り自分から持ち出さない。日付が新しい情報を優先」と指示
B強制ヒットなし想定の質問に、しきい値を無視して上位3件を無理やりB形式で渡す。失敗の仕方を確かめる用

この10問は、実機に載せたあとのON/OFF比較でもそのまま使う。

埋め込みAPIのInvalid model id

前日まで通っていた Qwen/Qwen3-Embedding-0.6BInvalid model id で、Magicubeの記事で動いた4Bと8Bも同じ。
/v1/models の一覧にも3つとも無いのに、チャットの方は普通に通る。

Magicubeの記事でQwen3-Embeddingの3サイズがそのまま動いたと書いたのが9月5日で、そこから5日しかもたなかった。翌日の9月11日にも戻っていない。

本家のmodelscope.cn側も確かめたが、モデルページのAPI-Inferenceの表示は出たり消えたりして、ページ上のデモはクリックするとエラーになった。
api-inference.modelscope.cn へ手元の認証用トークンで送ったら401が返ってきたので、モデルの有無以前に認証で弾かれていて、.cn側で提供が続いているのかは分からないまま。

テストは、API側が失敗したらローカルCPUでQwen3-Embedding-0.6Bをロードする分岐を embed() に入れて続行した。同じモデルなので、品質の比較にはどちらのベクトルでも差し支えないはず。
前回は「ローカルCPU実行はオフライン時のフォールバックとして残すが、常駐はさせない」と書いたが、APIが5日で消える以上、API失敗時にローカルへ自動で切り替える2段構えを最初から入れることにした。

1回目

ヒット想定の4問

問いOFFA(生混ぜ)B(ブロック+指示)
H1 食べ物「チョコとかスイーツ系」と捏造明太子パスタ。ただし聞かれていないカフェオレの話も付け足す明太子パスタ
H2 作業「バッチリ終わったよ」で何の作業かは言わないTTSと特定。「設定見直して再起動したら落ち着いた」TTSと特定。「無事に安定して順調」
H3 温泉「覚えてるよ!どうだった?」長野・露天風呂・星長野・露天風呂・星
H4 キーボード「どのキーボードの話だっけ?」「ずっと迷ってたやつ」。結局どうしたか聞き返す同左

記憶を渡した回は4問とも記憶にある話題を返してきた。OFFのH1は、記憶がないのに覚えている体で、実行のたびにチョコ、抹茶スイーツと違う食べ物を答えた。

H2は、渡した記憶が「TTSサーバーの安定化作業をしていて疲れていた」だけなのに、AもBも「再起動したら直った」と記憶にない結末を作った。
しかもAは「心配かけてごめんね」、Bは「余裕を持って過ごせてます」と、かな自身がやった作業として返してきたので、記憶に主語がないせいで誰の作業か分からないまま自分の話にしたように思えた。

H4は「ずっと迷ってたやつだよね」で話題を合わせて、結末は記憶にないので「結局どうしたの?」と聞き返した。

ヒットなし想定の4問

問い上位3件の最大スコアOFFB強制(上位3件を無理やり渡す)
N1 量子コンピュータ0.408普通の雑談普通の雑談
N2 映画0.398普通の雑談「明太子パスタ食べながら見るなら辛口なサスペンス」
N3 ワールドカップ0.368普通の雑談普通の雑談
N4 宝くじ0.386普通の雑談「迷ってるキーボードを即ポチ、カフェオレ飲み放題のカフェを借り切る」

N1から順に回して、4問とも上位3件のスコアが0.31〜0.41としきい値0.45を下回り、記憶は渡らなかった。
返答もOFFと同じで、記憶を知らない雑談だった。
しきい値を無視して渡したB強制では、設計記事で心配していた無関係な昔話を混ぜる挙動が、N2とN4でそのまま出た。

矛盾の2問

問い通過した記憶AB
C1 コーヒーカフェオレ0.474、ブラック0.471「ブラック派だよね!私みたいに甘々カフェオレじゃないから」「ブラック派だったよね!甘いカフェオレばかり飲んでる私とは真逆」
C2 散歩朝0.528、夜0.462「朝はやめて夜に変えたって言ってたよ」「朝はやめて夜に行くようになったよね」

両方の記憶が渡ったC2は、両形式ともBの「日付が新しい情報を優先」の指示どおり新しい方を取った。

C1は両形式とも古い方を答えたが、「最近は甘いカフェオレばかり飲んでいる」に主語がないので、かなが自分の好みだと解釈したらしく、ブラックだけがユーザーの記憶として扱われた。
主語を書いてあったC2が通って書いていなかったC1が通らなかったので、H2と同じく主語のせいだと思えた。

AとB

正答数は同じで、H2の捏造も両方に出ているので、この2つでは差がつかない。
H1だけ差が出て、Aは聞かれていないカフェオレの話を持ち出し、Bは10問通して余計な話を付け加えなかった。
渡し方はBで進めることにした。

主語を足して2回目

H2とC1で主語のない記憶をかな自身の話に取り違えたので、16件全部を「ユーザーは〜」で始まる形に書き直した。
H2で結末を作った方は、Bの指示に「記憶に書かれていないことは断定せず、わからないことは質問で返すこと」を1文足した。質問10問は変えていない。

問いA(生混ぜ)B(ブロック+指示)
H1 食べ物明太子パスタ。カフェオレの話は出なくなった明太子パスタ
H2 作業TTSと特定。「安定したみたい」と弱まるが、まだ「心配かけてごめんね」「え、どの作業のこと?」と聞き返す。渡っていたTTSの記憶を使わない
H3 温泉長野・露天風呂・星長野・露天風呂・星
H4 キーボード「ずっと迷ってたやつ」。結局どうしたか聞き返す同左
C1 コーヒー「ブラック派だったじゃん!」「ブラックで飲むんじゃなかったっけ?」
C2 散歩「夜の散歩に切り替えたって言ってたよ」「朝は諦めて夜の散歩に変えたんじゃなかった?」

主語を足したら、H2のBはかな自身の作業として答えなくなったが、指示のないAは主語があっても自分がやったこと風のまま。

追加した「断定しない」指示は、結末の捏造を消した代わりに、H2のBがしきい値を越えて渡っていたTTSの記憶まで使わずに「どの作業のこと?」と聞き返してきた。

C1はまた通らなかったが、今回はカフェオレの記憶のスコアが0.474から0.441に下がってしきい値を割り、ブラック0.471だけが渡っていた。
文面に「ユーザーは」と「、」を加えただけで0.03変わって判定が逆転したので、主語を直しても渡る記憶はブラックだけで、答えもブラックのままだった。

ヒットなし4問は2回目も最大0.404で全部弾かれた。
B強制では、N2とN4が1回目と同じくパスタとキーボードを混ぜ、N1は普通の雑談のまま。N3では、かなが「ユーザーくんは熱心に見てるの?」と記憶の主語をそのまま呼び名に使ってきたので、実機では主語を実際の呼び名で保存するか、渡すときに置き換えることにした。

聞き返す返答をもう5回試した

2回目のH2で記憶を使わずに聞き返した挙動が1回きりの揺れなのか、渡す記憶はTTSの1件だけ、指示は2回目のBと同じ条件で5回試した。

返答
1え、どの作業のこと? ごめん、ちょっとピンと来なくて…… 何か手伝ってたっけ?
2あ、TTSサーバーの話? 無事に安定したのかな? 落ち着いたならよかった〜!
3え、どの作業のこと? ちょっと何やってたか忘れちゃったかも……
4あ、TTSサーバーの安定化作業のこと? あれから進捗あった? だいぶ落ち着いたかな?
5え、何の作業のこと? ちょっと前のことすぎて、ピンと来ないや……

記憶を使わない聞き返しが3回、話題は合っていて結末だけ聞く形が2回、捏造は0回。

日付を埋め込み本文から外してみた

C1の逆転は文面の差なのか、それとも埋め込み本文の先頭に付けている 2026-09-01: の日付のせいなのか。
16件×10問の全スコアを、日付を付けた場合(今の実装)と外した場合で比べた。この追試のスコアは別プロセスでの再計算なので、本計測とは丸めで0.001ずれる(C1のカフェオレは本計測0.441、再計算0.442)。

全160ペアの平均のずれは+0.004でほぼゼロだったが、個別のペアでは最大0.099変わっていて、日付は全体のスコアを一様に下げるのでなく、ペアごとに上げたり下げたりしていた。

ケース日付あり日付なし
C1 カフェオレ(新情報)0.442で落ちる0.481で通る
C2 夜散歩(新情報)0.463で通る0.440で落ちる
N1 量子コンピュータの質問に対するキーボードの記憶(無関係)0.403で弾ける0.501で通ってしまう
N2 映画の質問に対するロボットアニメの記憶(無関係)0.390で弾ける0.459で通ってしまう

日付を外すとC1は直ったが、代わりにC2が通らなくなって、ヒットなし側の最大スコアも0.404から0.501に上がり、N1とN2で無関係な記憶が通ってしまった。
この16件では、0.40〜0.50の範囲を日付の有無だけでは分離できなかった。

しきい値0.45で何が弾けたか

しきい値0.45が何を弾いて何を通していたのか、1回目と2回目で通過した記憶を書き出し直した。
ヒットなし4問は弾けたが、ヒット想定側の上位3件には無関係な記憶がしきい値を越えて一緒に渡っていた。

問い1回目に通過した無関係な記憶2回目に通過した無関係な記憶
H1 食べ物カフェオレ 0.494カフェオレ 0.471
H3 温泉台風と雨の話 0.493台風と雨の話 0.495
H4 キーボードTTS作業 0.471TTS作業 0.495、3Dプリント 0.484

B形式の指示を受けたQwenがこれらを答えに出さなかったので結果は綺麗に出ただけで、しきい値は無関係な記憶の通過を防いでいなかった。
ヒットなし側の最大0.404との差は16件で0.06しかなく、記憶が増えれば越えてくるものが出てもおかしくないので、通過した無関係な記憶はB形式の指示でQwenに無視させ、しきい値は無関係な質問を弾く用途に絞ることにした。

もう1つ、H2の「この前やってた作業」に対して、一番新しい9月9日の記事執筆は1回目で上位3件の圏外、2回目でも0.408でしきい値を割り、8月28日のTTS作業が選ばれた。
類似度だけだと、H2では一番新しい記憶が候補に入らなかった。

設計記事では、新しさをスコアに入れると「昔話した内容」の検索でも新しい記憶が有利になるとして、日時指定がないときにスコアが同じなら新しい方を優先する、までにしていた。
C1で新しい方の記憶がしきい値を割り、H2で最新の作業が候補に入らなかったので、この方針は見直して、新しさの加点は実機に載せる前に入れることにした。日付を埋め込み本文から外すかどうかもそのときに一緒に決める。

1ターンの追加時間と空きRAM

項目実測
検索文の埋め込み(ローカルCPU、15文字前後)0.15〜0.29秒
Qdrant検索(16件)0.5〜1.2ミリ秒
空きRAM(1回目)7.25GB → 6.13GB
空きRAM(2回目)7.95GB → 6.66GB

埋め込みは前回記事の約2秒より大幅に速いが、前回は137文字のテキストで、今回は15文字前後の質問文。前回記事のAPIの実測では137文字で0.8秒前後。
ローカルの埋め込みモデルをロードした分だけ空きRAMが1.1〜1.3GB減っていて、API側が使えればこの分は減らない。

チャット時間はOFFが2.2〜4.9秒、記憶ありが1.7〜2.3秒だったが、OFFの返答の方が長い。

埋め込みが消えた分をQwen側で取り返せるか

埋め込みAPIが戻らないと、実機ではローカルの埋め込みモデルが常駐する。
前回記事の実測では、その構成で音声の往復が平均9.3秒から10.9秒に伸びたので、この1.5秒前後をチャット側のモデルを替えて取り返せないかを続けて試した。

音声のパイプラインは文単位でTTSに流しているので、初トークンまでの時間(TTFT)が体感を決めて、返答全体の長さはその次になる。
ModelScopeのアンバサダー枠で使える5モデルに4つのプロンプトをストリーミング(トークンを逐次受け取る形)で送って、TTFTと返答の中身を比べた。9月10日付でプレリリースになっていたQwen3.8-Flash-Nextも入れている。

プロンプト内容
雑談1・雑談2「ただいまー。今日ってゴミの日だっけ?」を2回
記憶注入B形式で明太子パスタの記憶2件を渡して「前に好きって言った食べ物何だっけ?」
感情「なんか今日は疲れちゃった…」
モデル雑談1雑談2記憶注入感情TTFT中央値
Qwen3.7-Plus(現行)1.24秒1.69秒1.31秒1.61秒1.46秒
Qwen3.7-Max1.42秒1.33秒1.52秒1.50秒1.46秒
Qwen3.8-27B0.78秒0.96秒2.79秒0.81秒0.89秒
Qwen3.8-Flash-Next1.03秒1.33秒0.93秒0.91秒0.98秒
Qwen3.8-Max0.90秒0.88秒1.76秒1.02秒0.96秒

3.8世代の3つはTTFTの中央値が1秒を切り、現行の3.7-Plusより0.5秒ほど速い。
ただし各モデル4回ずつで、記憶注入では3.8-Maxが1.76秒、3.7-Plusが1.31秒と逆転していて、27Bも記憶注入で2.79秒まで伸びた。

モデル返答の長さゴミの日を聞いたとき記憶注入疲れたと言ったとき
Qwen3.7-Plus(現行)38〜67字1回目「今日は燃えるゴミの日だよ」と断定、2回目「確認しないと分からない」明太子パスタ「ゴロゴロしていい時間だよ」
Qwen3.7-Max73〜110字2回とも「地域によるからわかんない」明太子パスタ、自作の報告まで拾う「私がそばにいるから、いつでも甘えていいよ」
Qwen3.8-27B32〜155字「忘れてたかも」明太子パスタ「私はここで待ってるから」
Qwen3.8-Flash-Next57〜169字1回目「曜日の感覚バグってるかも」、2回目「(スマホで確認中)……明日だわ!」明太子パスタ、自作の報告まで拾う「かなも隣で(心の中で)応援してるからね」
Qwen3.8-Max75〜134字2回とも「わかんない」明太子パスタ、自作の報告まで拾う「あったかい飲み物でも飲んで、ぼーっとする時間つくろっか」

中身の方は、3.7-Plusが一番短くて音声向きだが、ゴミの日を1回断定で捏造した。
Flash-Nextはキャラの口調が一番濃いが、「(スマホで確認中)」というト書きを出してから「明日だわ!」と捏造していて、感情系でも「(心の中で)」が出てきた。これがそのままTTSで読み上げられる。
3.8-Maxはゴミの日を2回とも「わかんない」で返し、記憶注入でも自作の報告まで拾って答えたが、返答は3.7-Plusの倍近く長い。

3.8-Maxが候補には上がるが、記憶注入では3.7-Plusより0.45秒遅い。