第1幕: 予兆と調律 ── 手押し車の軋み
雨脚の強まる午後でした。工房にはいつものように、磨かれた真鍮の歯車とオイルの匂いが満ち、ガスランプの淡い光が、雨粒の伝う窓ガラス越しにぼんやりと滲んでいました。その穏やかさを破るように、劇場の扉が、いつものように軽やかにではなく、何か重いものに押されているかのように、ぎいっ、と鈍い音を立てて開きました。
私は作業台の前で別の修繕の後片付けをしていた手を止め、音のした方を振り返りました。
扉の向こうから現れたのは、古い木製の手押し車でした。車輪が濡れた石畳の上で軋み、雨水を撥ねながら、ゆっくりと工房の中へと押し込まれてきます。押していたのは——初めて見る顔の女性でした。雨に濡れて額に張りついた白髪交じりの髪、上がりきった息、そして手押し車の柄を握る手の甲に浮いた太い血管。長年、体を動かして働いてきた人の手だと、一目で分かりました。
「あんた、ちょっと手を貸しとくれ」
前置きも、自己紹介もありませんでした。ただそれだけ言うと、彼女は手押し車を工房の中央まで押し切り、そこでようやく足を止めて肩で息をしました。
私は手押し車の上に横たわっているものを見て、思わず息を呑みました。
大きな道具袋を背負った、丸みを帯びたシルエットのからくり人形でした。無数のポケットとベルトに、大小さまざまな道具がびっしりとぶら下がっています。前脚の先には道具を掴むための頑丈な鉤爪状の手があり、胸元には小さな真鍮の目盛り盤が埋め込まれています。その人形は、片方の腕を斜め上——おそらくは工具棚の方向——へと伸ばしたまま、指先が何かに届く直前の姿勢で、完全に静止していました。
「これは……」
駆け寄って人形の体に触れると、真鍮の表面がわずかに温かいことに気づきました。まるで、つい先ほどまで全身の歯車を全力で回し続けていたかのような、熱の名残です。胸の目盛り盤に目をやると、細い針が右端の赤い領域まで振り切れて止まっていました。
「一体何が……」
私が尋ねると、女性は手押し車の縁に手をついたまま、途切れ途切れに語り始めました。
「あたしはオルコット。裏通りの、古い共同住宅を何十年も一人で切り盛りしてる者だよ。この子はハンフリー——うちの建物専属の、何でも屋の修繕人形さ」
「困った人形を、ちゃんと立て直してくれる工房があるって、界隈じゃ前々から聞いていてね。今日という今日は、それに縋るしかなかったんだよ」
「今日は朝からずっと雨でね。三階の屋根裏から雨漏りが始まって、ハンフリーに応急の手当てをさせてたんだ」オルコットさんは一度言葉を切り、拳を握りしめました。「屋根の応急処置はまだ終わっちゃいない。あの子が手を動かしている真っ最中に、今度は窓の一件が飛び込んできたんだよ」
「二階の奥の部屋でね、寒波のせいで窓が動かなくなっちまった。あそこには、この間から体調を崩してる年寄りの店子が一人でいるんだ。この時季に窓が開かないままじゃ、部屋の空気も入れ替えられやしない……」
「その二件目の指示を出した途端だったよ。ハンフリーの動きが、見る間に重くなっていってね。とうとう、ああして固まっちまった」
私はハンフリーの丸まった背中の道具袋を見つめました。大小さまざまな道具が、はち切れそうなほど詰め込まれています。その様子があまりに愛らしく、また同時に痛々しくもあって、私は胸の内でひそかに「工具だるまさん」と呼ぶことに決めました。
「ちょっと中を見せてもらってもいいですか」
オルコットさんが頷くのを確認し、私はハンフリーの背中にある小さな扉を開けました。中には、彼の思考の元になる指示の巻物——プロンプト(大規模言語モデルに対して指示や制約を与え、特定の応答や挙動を促すための入力テキスト)が収められています。
巻物を引き出した瞬間、私は思わず息を呑みました。何ページにも渡って、びっしりと細かい文字で道具の説明が書き連ねられていたのです。
「長年、何かあるたびに新しい道具の使い方を書き足してきたんだよ」オルコットさんが、私の手元を覗き込みながら言いました。「屋根も直せる、水道も直せる、電気も、壁も、階段もね。数えたことはないけど、もう百は超えてるんじゃないかね。抜けがあると、いざという時に困るからさ」
なるほど、と私は思いました。この巻物の分厚さは、彼女がハンフリーに寄せてきた長年の信頼と、頼み事の積み重ねそのものなのでしょう。
「これだけ丁寧に説明が書いてあれば、ちゃんと選べるはずなのに……」
私は首をかしげながら呟きました。まさかこれから、自分も同じような思い違いをすることになるとは、その時はまだ知る由もなかったのです。
第2幕: 絡糸と破綻 ── 積み重なる注釈
私はオルコットさんに「少し調整させてください」と断って、ハンフリーの巻物を作業台に広げました。
これだけの道具が並んでいるのに迷ってしまうのなら、選び方のこつをもう少し詳しく教えてあげればいい——そう考えた私は、羽根ペンを手に取り、巻物の余白に書き込みを始めました。
「窓に関する依頼が来たら、窓関連の道具の中から最も適したものを選ぶこと。ガラス自体の破損であればガラス交換を、枠の歪みであれば枠の交換を選ぶこと。両方に該当するように見える場合は、依頼人の言葉をよく確認してから……」
書いているうちに、次々と例外のケースを思いついてしまいます。「屋根の場合は雨漏りの程度によって……」「配管の場合は水の勢いによって……」——気づけば数行のつもりだった書き込みは、巻物の余白をほとんど埋め尽くすほどの長さになっていました。
試しに、もう一度ハンフリーに「屋根の水漏れをもう一度」と指示を出してみます。動き出しはしましたが——以前よりも、ほんのわずかに、鉤爪の動きが鈍く感じられました。
まだ、致命的というほどではありません。私はその小さな違和感に気づきながらも、「動いてるうちは大丈夫でしょ」と、自分に言い聞かせるように呟いてしまいました。
手応えを感じた私は、念のため、と屋根や窓だけでなく、配管や電気の道具についても同じように使い分けのヒントを書き加えていきました。「これで、どんな依頼が来ても迷わないはず」——そう考えるたびに、羽根ペンはまた新しい行を書き足していきます。気づけば、最初は数行のつもりだった余白の書き込みは、もとの道具目録の説明そのものよりも長くなっていました。
そこへ、オルコットさんが急かすように言いました。「二階の窓の件も、頼めるかい。年寄りが待ってるんだ」
私はハンフリーに窓の依頼を伝えました。ハンフリーの鉤爪が道具棚に向かって伸び——迷うように、窓ガラスの交換道具と、窓枠の交換道具の間で、揺れ動きました。
「おい、それじゃない!」
オルコットさんが声を上げました。ハンフリーが伸ばしかけていたのは、窓枠の交換道具の方だったのです。今回は割れたガラスを直すだけでよかったはずなのに。
「す、すみません、すぐに直します」
私は慌てて、巻物にさらに指示を書き足そうとしました。「ガラスのひび割れのみの場合は……」「枠自体に歪みが見られる場合に限り……」——けれど、書き足せば書き足すほど、ハンフリーの動きは重くなっていくばかりでした。
歯車の音が、次第に籠もったものに変わっていきます。まるで濃い蜜の中を掻き分けるように、粘つく重さを増しながら、ゆっくりと、ゆっくりと遅くなっていきました。
そして、ついに——工具棚に向かって伸ばした腕の途中で、唸っていた歯車の音が力なく弱まり、そのまま完全に静止してしまったのです。
「……また壊れちまったのか?」
オルコットさんの声が青ざめていました。私は自分が書き足した注釈の山を見つめ、血の気が引いていくのを感じました。直そうとして書き足したはずのものが、かえってハンフリーに止めを刺してしまった。そのことを、私は嫌というほど理解していました。
第3幕: 示唆と編み直し ── 呼ばれた分だけ
工房の扉が開き、駆け込んできたのはアンでした。舞台の照明装置の調整をしていたと見えて、その手にはまだ小さな工具を握ったままです。悪化の報せを受けて、いても立ってもいられずに来てくれたのでしょう。
アンは作業台の上に広げられた、注釈で埋め尽くされた巻物に目を落とすと、水晶のような瞳を細めました。
「あなた、これだけの道具の説明を、毎回全部あの子に読ませているの? そんなに一度に差し出されたら、どれが今必要なのか分からなくなって当然だわ」
その言葉は、静かでありながら、私の胸の奥に鋭く刺さりました。たしかに、似たような名前の道具が何十件も同じ一覧に並んでいれば、どれが今の用向きに合うのか迷って当然です。しかも一覧が長くなるほど、後ろの方に埋もれた道具ほど見落とされやすくもなるのでしょう。
奥の作業台から、マエストロが静かに歩み寄ってきました。彼はずっと、古い道具目録カードを一枚一枚、小さな引き出しへと仕分ける作業を続けていたのです。マエストロはその引き出し棚をそのまま持ち上げ、私たちの前の机に置きました。
そして無言のまま、すべての引き出しを一斉に引き抜き、中のカードを机いっぱいに広げてみせました。雪崩のように散らばったカードの山を、私たちはただ見つめるしかありませんでした。
マエストロはそれを静かに片付けると、代わりに棚の脇に据えられた小さな呼び鈴を、ちりん、と鳴らしました。
すると——鈴の音に応じるように、関連するいくつかの引き出しだけが、カタン、と小さな音を立てて、するりとせり出してきたのです。
「……今、目の前の一件に要るのはどれだ?」
マエストロの短い問いが、静まり返った工房に落ちました。私は言葉に詰まりました。
「一度に全部を見せる糸に、丁寧さはない。今、必要な分だけを渡すのが、本当の親切だ」
その一言が、私の中で絡まっていた考えを、静かにほどいていきました。
「抜けを怖がって全部渡すことと、ちゃんと選んで渡すことは、同じじゃない……」
私は呟きました。オルコットさんが長年書き足してきた道具の説明も、私が慌てて書き加えた注釈も、どちらも「念のため」という善意から生まれたものでした。けれど、その善意の積み重ねこそが、ハンフリーの思考を押し潰していたのです。
私は作業台に向き直り、この気づきをPythonの構造へと落とし込む作業に取りかかりました。目指すのは、動的ツール選択(Dynamic Tool Selection)(LLMエージェントが利用できる外部ツールの定義を、あらかじめ全件プロンプトへ含めるのではなく、実行時の依頼内容に応じて関連する分だけを動的に抽出しコンテキストへ渡す設計手法)です。ハンフリーを苦しめていたツール過積載——道具の説明を全部まとめて一度に渡してしまうという過ちから、マエストロの引き出し棚がそうしていたように、必要な分だけをその都度取り出す仕組みへと編み直します。
まず、道具ひとつひとつの名前と説明を収める小さな器としてToolSpecを、検索結果——ある道具が今回の依頼にどれくらい近いかという度合いを収める器としてToolMatchを、型付きで用意しました。
道具の説明文は、あらかじめ数値の列——埋め込み(テキストの意味的な特徴を、機械が距離や近さとして比較できるよう数値の列に変換したもの)に変換し、build_index関数で一箇所にまとめておきます。依頼が来るたびに、その依頼文もまた同じやり方で数値の列に変換し、search_tools関数の中で、あらかじめ用意しておいた道具の数値の列たちとどれだけ近いかを見比べるのです。あらかじめ向きの長さを一定に揃えておいた数値の列同士であれば、掛け合わせて足し合わせるだけの単純な計算(np.dot、内積)で、二つがどれだけ同じ方向を向いているか——つまり意味がどれだけ近いか——を測ることができます。
ここで肝心なのは、この数値への変換処理そのものを、embed_fnという名の、外から差し替えられる関数として受け取る作りにしたことでした。本番では実際の変換の仕組みを渡し、模擬公演(テスト)では、あらかじめ決めておいた数値だけを返す簡易な仕組みに差し替えられます。おかげで、本物の通信を一切行わずとも、確実で再現性のある検証ができるのです。
「名前だけで探せばいいのでは?」と思われるかもしれません。けれど、この仕組みの利点はまさにそこにあります。名前がぴったり一致しなくても、意味が近ければ拾い上げることができ、逆に今回の用向きとまるで縁のない道具——配管や電気の道具など——は、静かに見送ることができるのです。ただし、これで最後の一手までハンフリーの代わりに決めてくれるわけではありません。窓ガラスの交換道具と窓枠の交換道具のように、同じ「窓」の仲間として一緒に拾われてくる道具同士もあります。その中から本当にどちらを使うべきかは、これまでと同じように、依頼の文脈——「ガラスが割れた」のか「枠が歪んだ」のか——を読み取ったハンフリー自身の判断に委ねられています。変わったのは、その判断を、無関係な道具に埋もれることなく、ごく少数の紛れもなく関連した候補の中から下せるようになったことです。
ただし、これで万事解決というわけではありません。この検索は、あくまで「関連度が高そうなもの」を返すだけであり、完璧な正解を約束するものではないのです。もし道具目録がさらに桁違いに巨大化すれば、この検索そのものの精度もまた、いつか新たな課題になるかもしれません。私はそのことを、心の片隅に留めておくことにしました。
雪崩のように広がったカードの山と、鈴ひとつで静かにせり出してきたわずかな引き出し。同じ道具目録のはずなのに、机の上の景色はこれほど違いました。全件を毎回抱え込む経路と、依頼のたびに絞り込まれていく経路——コードに手を付ける前に、この二つの道筋の分かれ目を、ここではっきりさせておきます。

ここまでの対比を目にして、ハンフリーを苦しめていたものの正体が、いっそう鮮やかに見えてきました。
改めて向き合ったハンフリーの中身と、書き改めた設計は、次のようになりました。
before.py
| |
after.py
| |
見比べてみると、「道具は1つだけ選んで、名前で答えよ」という受け答えの流儀そのものは、もうrun()の中に固定で書き込んでいないことに気づきます。それは、ハンフリーを呼び出す側があらかじめ渡す指示——コンストラクタで受け取るsystem_prompt——の役目とし、run()自身は道具の絞り込みと一覧の組み立てだけに専念する形に整理し直したのです。道具箱の中身が入れ替わっても、答え方の流儀そのものは変わらない。そんな分担も、この編み直しでついでに手に入れました。
書き上げたコードを前に、私は工房の片隅で、試験用のオルゴール——本物の思考のゼンマイの代わりを務める模擬環境——を使い、模擬公演に取りかかりました。
まず、直す前のハンフリーに、窓ガラスの交換だけを求める依頼を試してみます。案の定、紛らわしい窓枠の交換道具と同じ一覧に並んでいるせいで、誤った答えが返っても、それをそのまま実行してしまう無防備さが、あらためて浮き彫りになりました。さらに、注釈で膨れ上がった巻物を使うと、受信口が悲鳴を上げるように、処理しきれない分量だと告げて弾き返してくることも確かめました。
次に、新しいハンフリーで同じ依頼を試すと、今度は窓関連の道具だけが的確に絞り込まれ、配管や電気の道具は一切紛れ込んでいませんでした。屋根の依頼を続けて試すと、今度は屋根関連の道具だけが、まったく別の顔ぶれで選び出されてきます。依頼が変われば、渡される道具の顔ぶれも、その都度きちんと変わる——それを確認できた時、工房の空気がひとつ、静かに軽くなったように感じました。道具目録が空の場合や、求める数が目録の総数を超えるような極端な場合でも、静かに壊れることなく応じてくれることも、ひとつずつ確かめていきました。
最後の確認を終えたその時、それまでどこか重く軋んでいたハンフリーの歯車の音が、油を差したようになめらかな、たった一度の短い「コトン」という音に変わりました。
第4幕: 開演と調和 ── 歩いて帰る足
私は新しい仕組みを組み込んだハンフリーを、あらためてオルコットさんの前に立たせました。
「もう一度、頼めるかい」
オルコットさんの言葉に、私は頷きました。まずは屋根の依頼を。配管や電気の道具が紛れ込むことはなく、屋根関連の道具だけを前に、ハンフリーの鉤爪は迷うことなく、防水シートへと伸びていきます。続けて窓の依頼を。今度は窓関連の道具だけに絞られた、ごく短い一覧が示されました。先ほど混同しかけた窓枠の交換道具も同じ一覧の中に並んではいましたが、依頼の言葉——「ガラスにヒビが入って」——をきちんと読み取ったハンフリーは、迷うことなく窓ガラスの交換道具の方を選び出しました。
伸ばされた鉤爪が、今度は途中で凍りつくことなく、迷いを断ち切るように動きました。けれど、窓ガラスの交換道具と窓枠の交換道具は、今回もそろって上位候補に残っていたのです。search_toolsが絞り込んだ先になお両方が生き残っていたという事実は、この仕組みの正直な線引きを物語っています。そこから先、どちらの鉤爪を選ぶかを決めていたのは、依頼の言葉を読み取ったハンフリー自身でした。search_toolsの受け持つ範囲と、その先にあるハンフリー自身の判断——その境目を、次のように示します。

道具の絞り込みは仕組みに任せられても、その先の一手を託されているのは、今も昔もハンフリー自身であることに変わりはありません。
「だけど、これからも道具は増えていくんだろう? また今日みたいに、パンパンに膨れ上がっちまうんじゃないのかい」
オルコットさんの問いに、私は首を横に振りました。
「いいえ。道具がいくつ増えても、ハンフリーが一度に読む量そのものは変わりません。依頼が来るたびに、その時々で関連する分だけを探し出して渡す仕組みになりましたから。ただ——」
私は正直に言葉を継ぎました。
「この探し方も、万能というわけではないんです。道具の数がさらに桁違いに増えれば、探し当てる精度そのものが新しい課題になるかもしれません。それでも、今日みたいに、全部を一度に抱え込んで立ち尽くすようなことは、もう起きないはずです」
オルコットさんは多くを語らず、ただ深く息をつきました。
「……上出来だよ、あんた」
そして、運び込まれてきた時とはまるで対照的に、ハンフリーは自分の足で、しっかりとした足取りで工房の扉へと向かって歩き出しました。あの重たく軋んでいた手押し車の記憶が嘘のように、彼は軽やかに、オルコットさんの隣を歩いていきます。
戸口の前で、オルコットさんは懐から、長年使い込まれた真鍮の巻尺を取り出し、私の手に握らせました。
「あたしが長年使ってきたものだ。あんたにも、測って選ぶ仕事の役に立つだろうよ」
そう言い残すと、彼女はハンフリーを連れて、まだ降り続く雨の中へと戻っていきました。戸口を出る間際、ハンフリーがふと足を止め、鉤爪の手をこちらへ小さく持ち上げてみせました。手を振るにはあまりに不器用な仕草でしたが、それでも彼なりの挨拶なのだと、私には分かりました。
「次にまた道具が増えても、あの子の重さはもう変わらないでしょうね」
アンが戸口を見送りながら、静かに呟きました。「……悪くない仕事だったわ」
その声には、いつもの辛辣さの奥に、かすかな労いの色が滲んでいるように感じられました。
工房に戻ると、マエストロはあの引き出し棚を、名残惜しむふうでもなく、いつもと変わらぬ手つきで、そっと棚の元の位置へと戻しているところでした。
私は「操り人形師の覚書」を取り出し、五ページ目を開きました。いつものように文字を綴り始める前に、私はふと筆を止め——そして初めて、余白に小さな棚の見取り図を描き添えました。引き出しがいくつも並んだ、あの棚の絵です。
それから、私はペンを走らせました。
「全部を持たせてあげることが、優しさだと思っていた。でも、本当に必要な分だけを、その都度渡してあげることも、優しさなんだ」
🎭 操り人形師の覚書(Colette’s Puppeteer Log)
- 調整したからくり(モジュール/関数名):
HandymanAgent(Before:TOOL_CATALOG全件を毎回結合したsystem_prompt/ After:ToolIndexとsearch_toolsによるクエリ依存の動的抽出) - 思考のゼンマイ(思考素子/LLMの特性):
- 名前や説明が似た道具が同じ一覧に並ぶと、思考のゼンマイはそのどちらかを取り違えやすく、長い一覧の途中に埋もれた道具ほど見落とされやすい
- 操り糸の張り具合(運糸の法/プロンプトと制御):
- Pydanticによる
ToolSpec/ToolMatchの型定義、埋め込み取得を関数として注入可能にしたbuild_index/search_tools、依頼のたびに関連する道具だけを含めて組み立て直すsystem_prompt
- Pydanticによる
- 舞台裏の絡まり(背伸びの過ち/避けるべき罠):
- 「抜けがあったら困る」という不安から、道具の説明とその選び方の注釈を際限なく書き足し、かえって指示の巻物を圧迫して、誤選択と処理の重さの両方を悪化させたこと
- 模擬公演の記録(検証のポイント):
- 紛らわしい名前の道具同士の誤選択、注釈で膨れ上がった巻物によるコンテキスト超過、関連する道具だけが上位に絞り込まれること、無関係な道具が除外されること、道具が0件や上限を超える場合の挙動
- マエストロの言葉:
- 「一度に全部を見せる糸に、丁寧さはない。今、必要な分だけを渡すのが、本当の親切だ」
