Ein vollständiges Beispiel dafür, wie Sie einen RAG-Chatbot erstellen, der eine interne Wissensdatenbank mit Claude durchsuchbar macht, umgesetzt für etwa 5.000 japanischsprachige Dokumente. Wir veröffentlichen es einschließlich etwa 30 Zeilen Code und einer Schätzung der monatlichen Betriebskosten.
Gesamtübersicht
- Dokumente in Embedding-Vektoren umwandeln (text-embedding-3-large)
- Die Nutzerfrage mit demselben Modell in einen Vektor umwandeln und die fünf relevantesten Treffer suchen
- Den abgerufenen context an Claude Sonnet 4.6 senden und eine Antwort generieren
- Die Kosten wiederholter Aufrufe mit Prompt-Caching um 90 % senken
1) Dokumente einbetten (nur einmal)
# 1) 5,000 件の社内文書を埋め込みベクトル化
from openai import OpenAI
# 埋め込みは Kunavo では提供していません(/v1/embeddings は実装済みの
# ワイヤフォーマットですが、有効なモデルがないため呼ぶと失敗します)。
# 埋め込みは OpenAI に直接、生成は Kunavo に — クライアントは 2 つ持ちます。
embedder = OpenAI(api_key="sk-...") # OpenAI 本家
kunavo = OpenAI(
api_key="sk-kn-...",
base_url="https://api.kunavo.com/v1",
)
documents = load_documents() # [{id, text, metadata}, ...]
# バッチ 100 件ずつで埋め込みを呼ぶ
batches = [documents[i:i+100] for i in range(0, len(documents), 100)]
all_vectors = []
for batch in batches:
resp = embedder.embeddings.create(
model="text-embedding-3-large",
input=[d["text"] for d in batch],
)
all_vectors.extend(resp.data)
# Postgres + pgvector に保存(または Pinecone, Qdrant など)
save_to_vector_db(documents, all_vectors)5.000 Dokumente × durchschnittlich 500 Token ergeben 2,5 Millionen Token. Da Embeddings bei Kunavo nicht angeboten werden, wird nur dieser Schritt direkt von OpenAI abgerechnet – den Einheitspreis finden Sie auf der Preisseite von OpenAI. Hier keine Zahl anzugeben, verhindert, dass ein Preis für etwas, das wir nicht verkaufen, veraltet, ohne dass es jemand bemerkt. Dieser Vorgang wird nur einmal ausgeführt und das Ergebnis in pgvector gespeichert.
2) Suche und Antwortgenerierung zur Abfragezeit
# 2) ユーザー質問 → 関連文書を検索 → Claude に投げて回答
def answer(question: str) -> str:
# 質問を埋め込み化
q_embed = embedder.embeddings.create( # 埋め込みは OpenAI 直
model="text-embedding-3-large",
input=[question],
).data[0].embedding
# 上位 5 件を検索
relevant = vector_search(q_embed, top_k=5)
# Claude に context + question を渡す
context = "\n\n---\n\n".join(d["text"] for d in relevant)
resp = kunavo.chat.completions.create( # 生成は Kunavo
model="claude-sonnet-4-6",
messages=[
{
"role": "system",
"content": (
"あなたは社内ナレッジ ベースをもとに正確に答えるアシスタントです。"
"提供された context にない情報については「情報がありません」と答えてください。"
),
},
{"role": "user", "content": f"# Context\n{context}\n\n# 質問\n{question}"},
],
max_tokens=800,
)
return resp.choices[0].message.contentPro Abfrage verwendete Token: Embedding der Frage (~0,000005 $) + etwa 5.000 Eingabe-Token von Claude Sonnet 4.6 (context) × 1,20 $/1M = 0,006 $ + etwa 500 Ausgabe-Token × 6,00 $/1M = 0,003 $. Etwa 0,009 $ pro Abfrage (etwa 1,4 Yen).
3) Mit Prompt-Caching weiter reduzieren
Da derselbe System-Prompt jedes Mal gesendet wird, greift Anthropics Prompt-Caching:
# 3) Cache_control で再呼び出しコストを 90% 削減
# 同じ context(社内ドキュメント)を何度も使うので、Anthropic prompt caching が効く
resp = client.chat.completions.create(
model="claude-sonnet-4-6",
messages=[
{
"role": "system",
"content": [{
"type": "text",
"text": SYSTEM_PROMPT_AND_GUIDELINES, # 安定したシステムプロンプト
"cache_control": {"type": "ephemeral"},
}],
},
{"role": "user", "content": f"# Context\n{context}\n\n# 質問\n{question}"},
],
)
# 2回目以降の同じシステムプロンプトはキャッシュヒット → 入力料金の 10%Wenn der System-Prompt mit 3.000 Token unverändert bleibt, wird ab dem zweiten Aufruf die Eingabe dieses Abschnitts mit 10 % des Tarifs berechnet. Messungen zeigen, dass die Kosten auf etwa 0,004 $ pro Abfrage (etwa 0,6 Yen) sinken.
Schätzung der monatlichen Betriebskosten
| Nutzung | Kosten | Anmerkung |
|---|---|---|
| Initiales Embedding | $0.25 | Einmalig |
| 1.000 Abfragen/Tag | Etwa 270 $/Monat | Ohne Caching |
| 1.000 Abfragen/Tag | Etwa 110 $/Monat | Mit Caching |
| 10.000 Abfragen/Tag | Etwa 1.100 $/Monat | Mit Caching, Größenordnung eines Slack-Bots |
Besondere Hinweise für Japanisch
- Tokenverbrauch: Japanisch kann zwei- bis dreimal so viele Token wie Englisch verbrauchen (aufgrund der Kodierung von Kanji und Kana). Begrenzen Sie context von 5K auf 3K und setzen Sie auf Suchgenauigkeit.
- Embedding-Modell: text-embedding-3-large unterstützt mehrere Sprachen und bietet auch für Japanisch ausreichende Genauigkeit. Bei vielen internen Begriffen empfiehlt sich ein separates Synonymwörterbuch.
- Claude vs. Gemini 2.5 Pro: Claude ist bei langen Zusammenfassungen und Zitaten genauer, Gemini 2.5 Pro bietet eine breitere Suche. Beginnen Sie mit Sonnet 4.6 und eskalieren Sie bei Bedarf zu Opus 4.7 – das ist das übliche Vorgehen.
- Maßnahmen gegen Halluzinationen: Geben Sie im System-Prompt ausdrücklich vor: „Wenn die Information nicht in context enthalten ist, antworten Sie mit ‚Keine Informationen vorhanden‘.“ Wenn dennoch Inhalte durchsickern, begrenzen Sie max_tokens, lassen Sie die ID des Quelldokuments zurückgeben und zeigen Sie die Quellenverknüpfung in der UI an.
Checkliste für den Produktionseinsatz
- Aktualisierungsablauf für den Embedding-Index (täglicher und wöchentlicher Neuaufbau per cron)
- Fallback bei Fehlern (zweistufig, etwa Claude → Gemini 2.5 Flash)
- Ratenbegrenzung und Kostenobergrenze (etwa ein tägliches Limit von 50 $ pro Schlüssel unter /app/keys)
- Protokollierung und Monitoring (tägliche Kostenverfolgung im usage-Dashboard)
- Gesetz über bestimmte Handelsgeschäfte (für Kunden in Japan) – /legal/tokutei-shoutorihiki
Beginnen Sie mit einer kostenlosen Registrierung. Laden Sie ab 10 $ Guthaben auf und starten Sie die nutzungsbasierte Abrechnung. Weitere Informationen finden Sie unter /docs/quickstart und /docs/caching.