最初から企業別に深く入りすぎず、共通構造+軽い結合で“基盤モデル”を作っておき、後から企業別に強化する。これが「スケールとカスタム」の両立策です。
🧩 1. 薄い共通モデル(Foundation Layer)
まずはすべての企業で共通する業務ドメインの骨格を小さく定義します。
(Neo4j / Postgres どちらでも格納できます)
| カテゴリ | ノード例 | 主な関係 | 目的 |
|---|---|---|---|
| 人・組織 | Person, Org, Department | (:Person)-[:BELONGS_TO]->(:Org) | 誰が誰に関係するか |
| プロセス | Project, Order, Invoice, BankReconciliation | (:Invoice)-[:SETTLED_BY]->(:BankReconciliation) | 会計・取引の流れ |
| 製品・部品 | Product, Component, BOM | (:Product)-[:HAS_PART]->(:Component) | 製造・構成要素 |
| 顧客接点 | Email, ChatMessage, Meeting | (:Person)-[:SENT]->(:Email) | 非構造情報の入り口 |
| 意味構造 | Decision, ActionItem, Topic | (:Email)-[:MENTIONS]->(:Topic) | 暗黙知の整理軸 |
ここで大事なのは、「すべてのメール・チャット・Odooデータが、
どれか1つの“共通ノード”にゆるく繋がっている」ことです。
例えば:
(:Email)-[:ABOUT]->(:Invoice)
(:ChatMessage)-[:ABOUT]->(:Project)
(:Email)-[:MENTIONS]->(:BankReconciliation)
これだけで「誰が、どの請求・入金・案件の話をしているか」が
横断的に見えるようになります。
⚙️ 2. 実装の段階(3層アーキテクチャ)
| 層 | 目的 | 具体例 |
|---|---|---|
| L0: データ接着層(Universal Connectors) | 各システムの基本項目を取り出す | Gmail, Slack, Odoo (sales, invoice, stock) |
| L1: 意味付け層(Semantic Bridge) | メール文・件名・添付などをEmbedding+LLM解析 | 「この文は入金確認を指す」などの分類 |
| L2: 構造化層(Knowledge Graph) | ノード化し、関連リンクを張る | (:Email)-[:ABOUT]->(:BankReconciliation) |
最初は L0+L1 のみで“関係候補”を推測 → Neo4j へ反映して L2 を作ります。
💡 3. 「薄いモデル」でできること(すぐ価値が出る)
| 機能 | 実現方法 |
|---|---|
| 類似メール検索 | ChromaDBで埋め込み検索 |
| メール→取引照合 | LLMで「入金」「送金」「残高一致」などを分類 |
| 会計仕訳リンク | Odooのaccount.moveまたはbank.statement.lineへ軽い参照リンクを付与 |
| 暗黙知抽出 | 「どんな取引でトラブルが多いか」「顧客からの指摘が多い工程」などをNeo4jの関係数で可視化 |
| ドメイン拡張 | 企業別にノード属性やリレーションを追加 |
🧠 4. 「基盤モデル」を作る際のコツ
- 全社共通のオントロジー名を決める(例:
Invoice,Payment,Project,Person) - ゆるい関係で繋ぐ(
ABOUT,MENTIONS,REFERS_TOなど曖昧でも可) - LLM分類器を1本用意して「このテキストはどの業務カテゴリか」を自動タグ付け
# 例: promptで分類 classify_prompt = """ Classify the text into one of: [Invoice, Payment, Order, Delivery, Reconciliation, Other]. Respond with JSON: {"category": "<label>"} """ - Neo4jでは共通インデックスを固定化
CREATE INDEX idx_category IF NOT EXISTS FOR (n) ON (n.category); CREATE INDEX idx_external_id IF NOT EXISTS FOR (n) ON (n.external_id); - 後で強化学習できるようにEvidenceを残す
(どのメールから、どの取引推論が生まれたか)
🪴 5. 成長パス(プロジェクト計画イメージ)
| フェーズ | 目的 | 期間目安 |
|---|---|---|
| Phase 1: 基盤構築 | 薄い共通ノード+Chroma検索連携 | 1–2か月 |
| Phase 2: 暗黙知抽出 PoC | メール・チャットから「業務トピック」分類(決済・調達・遅延) | 1か月 |
| Phase 3: Odoo連携 | Odooのモデル(invoice, stock, crm)にexternal_idリンク | 1か月 |
| Phase 4: 企業別強化 | 各社固有ノード・関係の追加、辞書拡張 | 継続 |
🚀 まとめ
- まずは **共通業務領域の「薄い知識グラフ」**を全企業で使える形にする。
- メール/チャット → 埋め込み+軽分類器 → Odoo参照リンク → Neo4j格納 という流れで、企業を問わず動く。
- 後からヒアリングで各社固有ノード(例:承認フロー・特定部品群)を追加すれば“深い暗黙知”に進化する。
- これができれば「企業共通の業務知識OS」のようなサービスが成立します。
まずは“薄い共通モデル(基盤層)”の雛形を一式置いておきます。
そのまま PoC に使えるように、**(1) ノード/リレーション一覧(約10種) → (2) Cypher DDL(制約/索引付き) → (3) LLM分類プロンプト(英日対応)**の順にまとめました。
1) ノード/リレーション一覧(基盤レイヤ)
ノード(代表プロパティ)
- Person:
{ email_hash, name?, org_key?, role?, tenant } - Org:
{ org_key, legal_name?, domain?, tenant } - Department:
{ dept_key, name, org_key, tenant } - Project:
{ project_key, name?, customer_org_key?, tenant } - Order(Sales/Purchase 両用):
{ order_key, order_type: 'sales'|'purchase', partner_org_key, order_date, tenant } - Invoice:
{ invoice_key, order_key?, partner_org_key, issue_date, due_date?, currency?, amount?, tenant } - BankReconciliation(入出金照合の核):
{ recon_key, statement_date, method?, tenant } - Payment:
{ payment_key, bank_account?, paid_on, amount?, currency?, tenant } - Product:
{ product_key, name?, sku?, tenant } - Component:
{ component_key, name?, tenant } - Email(Evidence の役割):
{ message_id, sent_at, subject?, snippet?, from_hash, to_hashes[], thread_id?, lang?, tenant } - ChatMessage(Evidence):
{ message_id, sent_at, text?, user_hash, channel?, thread_ts?, lang?, tenant } - Topic(任意・タグ軸):
{ name, tenant }
external_id を Odoo 由来で持つなら、
*_keyはodoo:account.move/123のようにネームスペース付きを推奨。
リレーション(代表)
(:Person)-[:BELONGS_TO]->(:Org)(:Person)-[:IN_DEPT]->(:Department)(:Person)-[:SENT]->(:Email|:ChatMessage)(:Email|:ChatMessage)-[:ABOUT]->(:Project|:Order|:Invoice|:Payment|:BankReconciliation|:Product)(:Email|:ChatMessage)-[:MENTIONS]->(:Topic)(:Email)-[:IN_THREAD]->(:Email)(親子/スレッド)(:ChatMessage)-[:IN_THREAD]->(:ChatMessage)(:Invoice)-[:FOR_ORDER]->(:Order)(:Payment)-[:SETTLES]->(:Invoice)(支払→請求のひも付け)(:BankReconciliation)-[:SETTLED_BY]->(:Payment)(照合作業→個別支払)(:Product)-[:HAS_PART]->(:Component)(:Project)-[:FOR_CUSTOMER]->(:Org)
まずは ABOUT と SETTLES / FOR_ORDER / SETTLED_BY を“薄く”張るだけで「メール・チャット ↔ 会計/受発注/製品」を横断できます。
2) Cypher DDL(制約 / 索引 ひな形)
// ===== Tenancy(任意) =====
CREATE CONSTRAINT tenant_org IF NOT EXISTS
FOR (o:Org) REQUIRE (o.org_key, o.tenant) IS UNIQUE;
CREATE CONSTRAINT tenant_person IF NOT EXISTS
FOR (p:Person) REQUIRE (p.email_hash, p.tenant) IS UNIQUE;
CREATE CONSTRAINT tenant_dept IF NOT EXISTS
FOR (d:Department) REQUIRE (d.dept_key, d.tenant) IS UNIQUE;
CREATE CONSTRAINT tenant_project IF NOT EXISTS
FOR (p:Project) REQUIRE (p.project_key, p.tenant) IS UNIQUE;
CREATE CONSTRAINT tenant_order IF NOT EXISTS
FOR (o:Order) REQUIRE (o.order_key, o.tenant) IS UNIQUE;
CREATE CONSTRAINT tenant_invoice IF NOT EXISTS
FOR (i:Invoice) REQUIRE (i.invoice_key, i.tenant) IS UNIQUE;
CREATE CONSTRAINT tenant_payment IF NOT EXISTS
FOR (p:Payment) REQUIRE (p.payment_key, p.tenant) IS UNIQUE;
CREATE CONSTRAINT tenant_recon IF NOT EXISTS
FOR (r:BankReconciliation) REQUIRE (r.recon_key, r.tenant) IS UNIQUE;
CREATE CONSTRAINT tenant_product IF NOT EXISTS
FOR (p:Product) REQUIRE (p.product_key, p.tenant) IS UNIQUE;
CREATE CONSTRAINT tenant_component IF NOT EXISTS
FOR (c:Component) REQUIRE (c.component_key, c.tenant) IS UNIQUE;
CREATE CONSTRAINT tenant_email IF NOT EXISTS
FOR (e:Email) REQUIRE (e.message_id, e.tenant) IS UNIQUE;
CREATE CONSTRAINT tenant_chat IF NOT EXISTS
FOR (m:ChatMessage) REQUIRE (m.message_id, m.tenant) IS UNIQUE;
CREATE CONSTRAINT tenant_topic IF NOT EXISTS
FOR (t:Topic) REQUIRE (t.name, t.tenant) IS UNIQUE;
// ===== よく使う索引 =====
CREATE INDEX idx_person_org IF NOT EXISTS FOR (p:Person) ON (p.org_key);
CREATE INDEX idx_person_dept IF NOT EXISTS FOR (p:Person) ON (p.tenant, p.org_key);
CREATE INDEX idx_project_customer IF NOT EXISTS FOR (p:Project) ON (p.customer_org_key);
CREATE INDEX idx_order_date IF NOT EXISTS FOR (o:Order) ON (o.order_date);
CREATE INDEX idx_invoice_dates IF NOT EXISTS FOR (i:Invoice) ON (i.issue_date, i.due_date);
CREATE INDEX idx_payment_date IF NOT EXISTS FOR (p:Payment) ON (p.paid_on);
CREATE INDEX idx_email_sent IF NOT EXISTS FOR (e:Email) ON (e.sent_at);
CREATE INDEX idx_chat_sent IF NOT EXISTS FOR (m:ChatMessage) ON (m.sent_at);
CREATE INDEX idx_about_lookup IF NOT EXISTS FOR (t:Topic) ON (t.name);
// ===== 参照整合(任意:弱制約代替の運用) =====
// 例えば ABOUT の先に必ず tenant が一致するよう、アプリ側でチェックするのが無難。
// ===== (任意)ベクトル検索を使う場合の索引 =====
// m.embedding / e.embedding に LIST<FLOAT> を格納する前提
// 次元数は利用モデルに合わせて(例:1536)
CREATE VECTOR INDEX email_embed_idx IF NOT EXISTS
FOR (e:Email) ON (e.embedding)
OPTIONS { indexConfig: { `vector.dimensions`: 1536, `vector.similarity_function`: 'cosine' }};
CREATE VECTOR INDEX chat_embed_idx IF NOT EXISTS
FOR (m:ChatMessage) ON (m.embedding)
OPTIONS { indexConfig: { `vector.dimensions`: 1536, `vector.similarity_function`: 'cosine' }};
まずは ユニーク制約(tenant+自然キー) と 日時索引 だけで十分です。
ベクトル索引は「後から」でも OK(PoC 段階では Chroma 側で近傍探索→ID 連携でも回せます)。
3) LLM分類プロンプト(英日対応・JSON厳格)
3.1 出力スキーマ(固定)
category(必須・1つ):["Invoice","Payment","Order","Delivery","Reconciliation","Project","Product","Support","Scheduling","Other"]about_refs(0..n・軽い参照):例)["invoice:INV-2025-0012","order:SO-000345","project:PRJ-MAPS"]intent(任意・1つ):["inform","request","approval","issue","schedule","other"]signals(任意・0..n):["due_date","amount","bank","discrepancy","delay"]confidence(0.0–1.0)language(”ja” / “en” / 他)
JSON 例(英語メール)
{
"category": "Reconciliation",
"about_refs": ["invoice:INV-2025-0012","payment:PAY-83421"],
"intent": "inform",
"signals": ["discrepancy","amount","bank"],
"confidence": 0.86,
"language": "en"
}
3.2 英語プロンプト(Classifier / JSONモード想定)
SYSTEM:
You are a strict business email/chat classifier for ERP workflows.
Return ONLY a single-line JSON object following the schema provided.
No prose, no explanations.
USER:
Classify the following text into business category and attributes.
Categories (exactly one):
["Invoice","Payment","Order","Delivery","Reconciliation","Project","Product","Support","Scheduling","Other"]
Intent (0..1):
["inform","request","approval","issue","schedule","other"]
Signals (0..n):
["due_date","amount","bank","discrepancy","delay"]
Output JSON fields:
{
"category": "<one_of_categories>",
"about_refs": ["<light_reference_or_empty>"], // e.g., "invoice:INV-2025-0012"
"intent": "<optional_intent_or_omit>",
"signals": ["<zero_or_more_signals>"],
"confidence": <0.0-1.0>,
"language": "<ja|en|...>"
}
Text:
---
{TEXT}
---
Rules:
- Be conservative: if unsure between Invoice/Payment/Reconciliation, prefer "Reconciliation" only when the message is explicitly about matching statements or linking invoices to payments.
- Prefer extracting light references from obvious tokens (invoice/order numbers, project codes).
- Respond with exactly one compact JSON line. No comments.
3.3 日本語プロンプト(同等内容)
SYSTEM:
あなたはERP業務向けの厳格なメール/チャット分類器です。
出力は指定のJSONオブジェクト1行のみ。説明文は不要です。
USER:
以下の本文を分類し、指定のスキーマでJSONを返してください。
カテゴリ(いずれか1つ):
["Invoice","Payment","Order","Delivery","Reconciliation","Project","Product","Support","Scheduling","Other"]
インテント(0〜1個):
["inform","request","approval","issue","schedule","other"]
シグナル(0個以上):
["due_date","amount","bank","discrepancy","delay"]
出力JSONの形式:
{
"category": "<上記のいずれか1つ>",
"about_refs": ["<簡易参照 or 空>"], // 例: "invoice:INV-2025-0012"
"intent": "<任意: 上記のいずれか or 省略>",
"signals": ["<0個以上>"],
"confidence": <0.0-1.0>,
"language": "<ja|en|...>"
}
本文:
---
{TEXT}
---
ルール:
- Invoice/Payment/Reconciliation の迷いがある場合、「入出金の照合(明細と請求の突合せ)」が主題なら Reconciliation を選ぶ。
- 請求書番号や注文番号、案件コードなど明白なトークンから about_refs を素直に抽出。
- 出力は厳密に1行のJSONのみ。コメントや説明は不要。
3.4(任意)検出の精度を上げるための補助プロンプト
- 参照抽出ヘルパ(番号・金額・日付・取引先名の抽出だけを行う小プロンプト)
- 正規化辞書(Odoo 側の external_id や命名規則を few-shot で提示)
- 英⇄日 並列表現(請求書=Invoice, 照合=Reconciliation, 入金=Payment などの対訳表)
使い始めの運用メモ(超要点)
- 最初は Email/Chat → category+about_refs の“薄い”付与だけで十分。
- Reconciliation 検出は signals=[“discrepancy”,”amount”,”bank”] の組合せが効きます。
- 企業別の細かいルールは後から Topic でタグ拡張 or 別モデル(Issue/Decision)として増やす方が安全。
コメントを残す