見積りを「自社の過去実績」で裏づける|製造業AIのためのRAGと値付けのグラウンディング

一品一様の金属加工の現場において、見積りはベテランの暗黙知でした。「この材質でこの形なら、これくらい」。図面を見た瞬間に、頭の中の過去案件と照らして値段が浮かぶ。この勘を、汎用のAIではなくその工場の過去実績で裏づける——これこそが我々がRAGを使って実現したかった事です。

しかも、これは悠長に構えていられる課題ではありません。製造業、とりわけ金属加工の現場では、その勘を持つベテランの多くがすでに定年を迎えています。図面を見ただけで値段が浮かぶ職人が現役でいるうちに、その判断を形にして残す——ベテランの暗黙知を継承することは、いま取り組まなければ間に合わない急務です。

汎用のLLMは「一般的な相場」なら語れますが、「御社が実際にいくらで請け負ってきたか?」は知りません。そこを埋めるのがRAG(Retrieval-Augmented Generation=検索で根拠を引いてから生成する)です。

この記事では、過去案件をどう探し、どう埋め込み、どう値付けの根拠にするかを、実際のコードでお見せします。値付けの係数(密度や工数の換算)は、このシステムの核心部分なので伏せますが、仕組みの骨格はすべて開示しています

この業務システムの設計に興味がある方は、以下の記事を参照してください。

また、環境構築を含めた実装の部分について知りたい方は、以下の記事をご覧ください。

目次

なぜ見積りにRAGが有効なのか?

まず、とりわけ「見積り」において、汎用のAIではなく、検索を用いたRAGが必用となるのか解説します。

そもそもRAGとは?

RAG(Retrieval-Augmented Generation)は、回答を生成する前に外部の知識を検索して、根拠としてモデルに渡す手法です。

生成AIは学習済みの一般知識しか持たず、しかも自信ありげに間違えることがあります。その問題を解決するためにRAGは以下のプロセスを経て回答します。

  1. 質問に関連する資料を検索する
  2. それを文脈としてプロンプトに埋め込む
  3. その資料に基づいて答えを生成する

こうすると、モデルが知らない自社固有のデータ最新の事実を根拠に回答ができるので、当てずっぽうな解答を抑制できます。このシステムでは、その「検索する資料」が汎用の文書ではなく、その工場の過去の受注実績である点がポイントになります。

見積りはベテランの暗黙知

事業承継期の町工場でいちばん失われやすいのが、この「値付けの勘」です。

ベテランが辞めると、同じ図面でも値段が出せなくなる。属人化の解消=暗黙知をデータとして残すこと、が導入の切実な動機でした。RAGは精度のためだけでなく、判断のプロセスを会社の資産として残すための仕組みでもあります。そして、それは同時に業務の属人化という長年、経営者を悩ませてきた課題を解決することにも繋がります。

汎用知識ではなく「自社の履歴」を引く

判断の材料とするのは past_jobs テーブルです。図番・形状・材質・実績単価・実績工数・質量、そして「特急対応」などの項目を持ちます。導入時に顧客の過去データをここへ投入する——この作業が、そのままベテランの判断を会社の資産に変える工程になります。

past_jobsテーブルの中身の例

過去実績をどう参照するか?

では、過去の実績をどのように参照するか、詳細を見ていきましょう。

1.図番の完全一致(強い根拠)

一番確かなのは見積もりの製品と図番が完全に一致する過去データが見つかるパターンです。

町工場では、同じ部品なら同じ図番が振られます。これほど強いシグナルはなく、決定論的なので常に先頭に置きます。

Python
def by_drawing(drawing_number: str, limit: int = 10) -> list[dict]:
    """同一図番の履歴=最強の根拠。決定論的なので常に先頭。"""
    if not drawing_number:
        return []
    rows = (get_client().table("past_jobs").select(_COLUMNS)
            .eq("drawing_number", drawing_number)
            .order("ordered_at", desc=True).limit(limit).execute().data or [])
    for r in rows:
        r["match"] = "exact_drawing"
    return rows

2.ベクトル検索(意味検索)

次に強いのがベクトル検索です。

品名や形状の内容の近似値で探すので、意味検索(セマンティック検索)とも呼ばれます。

初めて受注が入った部品には図番の履歴がありません。しかも受注の品名は日本語、履歴の形状記述は英語で書かれていることがある。この二つを橋渡しできる手段が、意味検索なのです。

検索は Supabase(pgvector)側の関数を使い、材質でも絞れるようにしています。

Python
def semantic(query, material=None, top_k=8) -> list[dict]:
    """形状・材質の意味的な近傍。similarity(1.0が完全一致)つきで返す。"""
    vec = to_pgvector(embed_query(query))
    rows = get_client().rpc("match_past_jobs", {
        "query_embedding": vec, "match_count": top_k, "filter_material": material,
    }).execute().data or []
    for r in rows:
        r["match"] = "semantic"
    return rows

実際の呼び出しは、この2つを束ねた hybrid() です。図番の完全一致を先頭に置き、足りない分だけ意味検索で埋め、id で重複を排除する。「強い証拠を優先し、足りなければ似たもので補う」という素直な設計です。

なお、この意味検索は値付けエージェント専用です。同じ工場のデータでも、検査役(社外に出る数字を止める番人)はこれとは別系統の検索を使います。その理由と実装については以下の記事をご覧ください。

埋め込みモデルを「自前・ローカル」で持つ

検索の質を決めるのは埋め込みモデルとそれが作るベクトルです。この2つをどちらも外部に出さず自前で抱える——ここには、性能以外の理由からいくつかの設計判断があります。

そもそも埋め込みモデルとは?

埋め込みモデルは、品名や形状などのテキストを、意味を表す数値の並び=ベクトルに変換するモデルです。

似た意味の文どうしは、近い場所のベクトルになる。だからベクトル同士の距離を測れば「意味が近い過去案件」を引ける——これが前段のベクトル検索の中身です。

ここでは、埋め込みモデルと関連する用語を押さえておきます。

  • ベクトル/埋め込み:テキストを表す数値の配列。このモデルは1件を384個の数値で表します。
  • 次元:ベクトルの数。ここでは384次元。多いほど表現力は上がるが重くなる。
  • 類似度(コサイン類似度):2つのベクトルがどれだけ同じ向きか=意味がどれだけ近いか。1.0で完全一致。
  • ベクトル検索/近傍探索:問い合わせのベクトルに近いものを大量の中から探す処理。ここでは PostgreSQL の拡張 pgvector が担います。
  • 多言語モデル:日本語と英語を同じベクトル空間に置けるモデル。品名が日本語・履歴が英語でも橋渡しできる(multilingual の由来)。

要するに、埋め込みモデルは「意味を座標に変える定規」で、その座標の近さで過去案件を引く、という仕組みです。

埋め込みは外部に出さない(データ主権)

埋め込みモデルは multilingual-e5-small(384次元・約120MB)を使います。

過去の受注実績は顧客の資産であり、値付けの根拠となるものです。埋め込みAPIにリクエストを送ることは、図番と形状と材質を他社のサーバに送ることとなります。埋め込みモデルはローカルでも十分に速く、埋め込みを外部に出す理由がありません。

Python
MODEL_NAME = os.environ.get("EMBEDDING_MODEL", "intfloat/multilingual-e5-small")
DIM = 384

@lru_cache(maxsize=1)
def _model():
    from sentence_transformers import SentenceTransformer   # 遅延 import
    return SentenceTransformer(MODEL_NAME)

初回の読み込みはモデルのダウンロードと展開で数十秒かかります。それが値付けの最中に起きると、根拠を集める前に時間切れになる。そこで起動時に warm() で先読みしておきます。

専用ベクトルDBは立てない

専用のベクトルDBは立てませんでした。ベクトルを past_jobs同じ行に置けば、実績価格との整合が保たれます。別システムに分けた瞬間、値付けの根拠(ベクトル)と過去実績(価格)が、いつか静かに食い違う。一つの行に同居させることが、いちばんの整合性保証でした。

SQL
create extension if not exists vector;   -- 専用ベクトルDBは立てない
create table past_jobs (
  ...
  actual_unit_price numeric,
  mass_kg           numeric,          -- 図面の物理的性質。同じ図番なら常に同じ
  embedding         vector(384)       -- 同じ行に同居=根拠と価格が食い違わない
);

専用ベクトルDBを使う場合、今回のDBと同じ行に置くパターン(pgvector)のメリット・デメリットを下の表にまとめました。

観点専用ベクトルDBRDBに同居(Postgres + pgvector)
検索性能・スケール◎ 数千万〜数十億ベクトル・高QPSに強い。○ 数百万規模までは実用十分。超大規模・超高QPSでは劣る
インデックス/最適化◎ HNSW/IVF/量子化(PQ)・シャーディング等が標準○ HNSW/IVFFlat対応。量子化など高度最適化は限定的
ベクトル特化機能◎ ハイブリッド検索・リランキング・名前空間・リアルタイム更新等△ 近傍検索+SQLフィルタが中心。専用機能は薄い
業務データとの整合性△ 別システム=二重管理・同期ズレ・トランザクション跨げない◎ ベクトルと実績が同じ行・同じトランザクション(今回の主眼)

e5 の接頭辞という落とし穴

細かい点ですが注意が必要です。e5 系のモデルは接頭辞を要求します。

検索する側の文には query:、格納する側の文書には passage:を付けます。付け忘れると精度が落ち、しかも落ちたことに気づけないことがよくあります。だから、接頭辞を付ける関数を、検索用と格納用で切り分けました。

Python
def embed_query(text: str) -> list[float]:      # 検索する側
    return _encode([f"query: {text}"])[0]

def embed_passages(texts: list[str]):           # 格納される側
    return _encode([f"passage: {t}" for t in texts])

見つけた実績を値付けに結び付ける

検索はゴールではありません。引いてきた過去案件を、値付けエージェントの「根拠」にして初めて意味を持ちます。グラウンディング(接地)とは、モデルの出力をこうした実データに結びつけ、推測でなく根拠に基づかせることを指します。

エージェントは価格を決めない

肝心なのは分業です。値付けエージェントは、価格を直接決めません。部品の質量(kg)と加工工数(時間)という二つの物理量を見積り、会社のレートカードがそれを価格に変換します。

エージェントの仕事は、推定と、その根拠を示すこと。まず同一図番の履歴を引き、なければ意味検索で似た案件を引き、質量は図面の寸法から、工数は履歴から導く。根拠のない値は出さず、出せないなら空欄のまま人間に回します。

値付けプロンプトの考え方について(なぜ価格を直接決めさせないか)は、開発記録の記事で触れています。

「何を見て、どう考えたか」を残す

見積りテーブルには、参照した過去案件のID群(generation_basis=何を見たか)に加えて、reasoning_trace(どう考えたか)を残します。ツール呼び出しの引数・戻り値・消費した予算・停止理由まで。これがあると、あとから値段を再現できる。なぜこの見積りになったのかを、数字を追って説明できます。

Python
-- quotes 抜粋
generation_basis  jsonb,   -- 参照した past_jobs の id 群(何を見たか)
reasoning_trace   jsonb    -- ツール呼び出しの軌跡(どう考えたか)。値段を再生できる

事業承継でベテランが去ったとしても、この「根拠つきの値付け」が積み上がっていれば、後継者はゼロから見積りの勘を育て上げずに済みます。承認画面では、この生成に使った根拠を人が確認してから承認できます。

検証:テストで値付けの精度を確かめる

値付けは「動いた」だけでは終わりではありません。出てきた値段が、妥当かどうか確かめる必要があります。そのために、今回は性質の違う2つのテストを用意しました。

正しさのテスト(構造が壊れていないか)

1つ目は、ノードの振る舞いを確かめる検証スクリプトです。

確定注文1行+要見積り1行を混ぜた状態で値付けノードを実際に動かし、見積りが「要見積りの行だけ」を対象にしているか、DBに保存された明細も同じか、確定注文だけの受注なら見積りが作られないか——を確認します。作った見積りは最後に自分で消す、後処理つきの結合テストです。

Python
# verify_generate_quote.py(抜粋):要見積の行だけを見積っているか
out = generate_quote(state)          # confirmed_order 1行 + needs_quote 1行
quote = out["quote"]
dns = {l["drawing_number"] for l in quote["lines"]}
assert dns == {"QUOTE-ME"}           # 確定注文は見積らない

精度のテスト(値段が現実に近いか)

2つ目が本命です。ホールドアウト——過去実績のうち何件かを、わざと検索対象の履歴から外しておき、その部品をエージェントに見積らせて、実際の単価(actual_unit_price)と突き合わせる

答えを知っている問題を解かせて、どれだけ外すかを測定するというわけです。使ったレートカードのバージョンや seed、案件の層(stratum:単発/リピート等)も残し、条件を揃えて比較できるようにしました。

Python
// quote_holdout.json(抜粋):履歴から除外した実績。エージェントの単価をこれと比べる
{ "rate_card_version": "2026-07-v1",
  "jobs": [{ "drawing_number": "DWG-E-3775", "material": "SUS304",
             "mass_kg": 0.1435, "actual_unit_price": 1580, "stratum": "one_off" }] }

テスト → 失敗 → 修正を、4周する

このホールドアウトには時間が掛かりました。

Claude Code にコードを直させる → ホールドアウトで測る → まだ外している → また直す。これを4周ほど繰り返しています。毎回どこを触っていたかというと、調整の余地があるのは大きく4か所でした。

  1. 検索:図番の履歴が無い部品で、ベクトル検索が的外れな類似を返す。top_k や材質の絞り込み、図番一致とベクトル検索の混ぜ方(hybrid)を調整。
  2. 埋め込み:e5 の接頭辞(query:/passage:)や正規化まわり。ここがズレると近傍が静かに悪くなる。
  3. エージェントの見積り方(プロンプト):質量・工数をどう推定させるか。判断の記述を調整。
  4. レートカード:ホールドアウトの実績と系統的にズレるときは、係数・バージョンを見直す。

大事なのは、毎周ちゃんと数字で「まだ外している」と言ってくれる相手がいたことです。ホールドアウトが無ければ、「それらしい見積り」が出た時点で満足してしまい、現実とのズレに気づけません。

Claude Code は速くコードを書き換えますが、その速さを安全に使えたのは、外したことを即座に教えてくれるテストがあったからです。

まとめ

見積りの差別化は、その工場の履歴をどう引いて、どう根拠にするかにありました。

強い証拠(図番の完全一致)を先頭に、足りなければ意味検索で橋渡しする2段の検索。埋め込みはローカルで持ってデータ主権を守り、ベクトルは実績価格と同じ行に同居させて整合性を保つ。そして値付けエージェントは価格を決めず、質量と工数を見積もり、根拠(generation_basisreasoning_trace)を残す。

RAGは「賢く見せる」ためではなく、判断を説明可能な資産として残すための仕組みです。

【中部地方】製造業向けAI自動化ツール 実地検証パートナー募集

株式会社ニューロシンクでは、製造業のバックオフィス業務を自動化するツール「Synaptiq」の開発に伴い、実際の現場で効果を検証していただける企業様を募集しています。 受注・見積もり・請求書の作成から工程管理までをシステムが自動処理し、人間は「内容の承認」を行うだけの環境を目指す取り組みです。

現在は本格提供前の開発段階のため、導入の義務は一切ございません。まずは貴社の業務課題をお伺いし、どこから自動化できるかを無料で一緒に整理させていただきます。

  • 対象エリア: 名古屋駅から2時間圏内の製造業様
  • ご協力内容: 現行業務のヒアリングと検証(費用負担・導入義務なし)

Synaptiqの詳しい内容を見る
相談を申し込む(15分・オンライン)

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

1981年生まれ、名古屋出身。

2008年よりドイツ・ベルリンに在住。
ドイツの国家資格である職業訓練プログラム「アプリケーション開発専門IT技術者」を修了後、医療系自社開発企業にてデスクトップ・Webアプリケーションの開発に4年間従事。
2022年よりドイツの大手SIer「Adesso SE」にて、フルスタックエンジニアとしてリードポジションを務める。

2027年に日本へ帰国し、日本の中小企業へのAI導入支援を本格的に開始予定。

著書「AI時代の海外移住戦略

コメント

コメントする

目次