Skip to main content

エージェント指向 RAG: AI エージェントでリトリーバル強化型生成を強化する

本記事では、エージェント指向RAGが、AIエージェントによって検索戦略を動的に精緻化し、複数のデータソースを調整し、応答精度を高めることで、リトリーバル拡張生成をどのように強化するかを解説します。 本稿はAI翻訳です。誤訳の可能性があれば、コメント欄でご指摘ください。
Created on August 27|Last edited on August 27
リトリーバル拡張生成(RAG) 事前学習済みの知識と外部の情報リトリーバルを組み合わせることでAIシステムを強化し、より正確で文脈に即した応答を生成できるようにします。従来のリトリーバル拡張生成(RAG)は二段構成で動作します。まずクエリに基づいて関連文書を取得し、その内容を言語モデルの応答に統合します。単純な照会には有効ですが、この静的な取得手法は、複雑な推論や多段クエリ、あいまいな要求に弱く、より高度なタスクへの対応力を制限します。
エージェント指向RAGは、リトリーバルを能動的に管理し、検索戦略を精緻化し、複数の情報ソースを調整する知的エージェントを導入することで、これらの課題に対処します。固定的な取得プロセスではなく、クエリに応じて動的に適応するため、精度とスケーラビリティが向上します。活用することで マルチエージェントアーキテクチャ、リアルタイムのツール活用、および W&B Weave のような監視フレームワーク Weave、 エージェント指向RAGは、AIシステムが複雑なクエリをより効率的に処理できるようにします。
本記事では、エージェント指向RAGを取り上げ、シングルエージェントとマルチエージェントの各アーキテクチャを比較し、その強みやトレードオフ、さらにAI主導のリトリーバルを強化するうえでの実践的な適用例を分析します。
W&B Weave を用いたエージェントのツール使用状況のモニタリング

目次

目次リトリーバル拡張生成とは何か従来型 RAG と エージェント指向 RAGエージェント指向 RAG のアーキテクチャ: 単一エージェント vs. 複数エージェント システム単一エージェントシステム: シンプルだが制約ありクエリルーティングの単一エージェント方式マルチエージェントシステム: 専門化とスケーラビリティ高度な推論と協調のためのマルチエージェントシステムマルチエージェントRAGを使うべきタイミング単一エージェント対マルチエージェントを超えて: そのほかのRAGアーキテクチャステップバイステップチュートリアル: シングルエージェントRAGシステムの構築ソフトウェアエンジニアリングにおけるユーザークエリの理解実装の概要1. データを収集し、前処理する2. テキストを埋め込みベクトルに変換する3. クエリ分類とリトリーバルを実装する4. GPT-4o で応答を生成する次のステップ: スクリプトを書くクエリルーティングエージェントの構築クエリルーターの仕組みコード由来のシグナルで精度を高める曖昧なクエリへの対処実装アプローチ次のステップ: クエリルーティングエージェントをコードで実装する社内データベースへのクエリ実行ステップ1: クエリ分類手順 2: ベクトルデータベースの検索手順 3: 応答の生成次のステップ: コードの実装今後の改善候補(クリックして展開)Claude 3.7 のツール使用の統合ベクトル検索とツールベースのリトリーバルClaude 3.7 のツール選択監視と最適化のための Weave動的なクエリ処理と反復実運用のユースケースと応用例法務調査: 判例リトリーバルの高精度化と適応性向上財務分析: リアルタイムの市場インサイトエージェント指向RAGにおける課題と対策戦略1. 複雑なリトリーバルによるレイテンシの増加2. ツール選択の信頼性3. 倫理的懸念: プライバシーとバイアスのリスク結論


リトリーバル拡張生成とは何か

バニラRAGは、取得と生成の2段階で動作します。ユーザーがクエリを送信すると、それは埋め込み(意味を数値で表現したもの)に変換されます。この埋め込みは ベクトルデータベースと比較して 事前処理済みドキュメントの埋め込みを照合して、最も関連性の高い一致を見つけます。システムは類似度スコアを用いてこれらのドキュメントを取得します。
取得されたドキュメントは生成モジュール(LLM)に渡され、応答に取り込まれます。これにより、モデルは事前学習済みの知識と最新の外部情報を組み合わせた回答を生成できます。
バニラRAGはクエリごとに取得を1回だけ実行し、結果の精緻化や反復は行いません。単純な参照には有効ですが、複雑な推論や多段クエリには弱く、そこをエージェント指向RAGは反復的なリトリーバルと自律的なクエリ処理によって改善します。

従来型 RAG と エージェント指向 RAG

前述のとおり、従来型 RAG静的に前処理されたデータベースからクエリごとに1回だけ情報を取得し、そのまま応答に取り込みます。これは単純な参照には有効ですが、クエリが複雑、曖昧、あるいは完全な解決に多段の手順を要する場合には苦戦します。リトリーバル戦略を適応させたり、結果を精緻化したり、多様な複数ソースの情報を統合したりすることはできません。
エージェント指向 RAG これらの制約に対処するため、リトリーバル過程を能動的に管理・強化するインテリジェントなエージェントを導入します。あらかじめインデックス化されたデータのみに依存するのではなく、ウェブサイトのようなリアルタイムソース、SQL などの高度なクエリでアクセスする構造化データベース、外部 API を含む多様なツールやデータベースと動的に連携できます。エージェントはクエリの性質に応じてリトリーバル戦略を適応させ、反復的な取得や自己省察による検索の精緻化を行い、必要に応じて手法を調整します。こうした柔軟性により、 自律的に推論する複数の情報源を横断して連携し、クエリとリトリーバル手法の双方を継続的に精緻化することで、精度が大幅に向上し、幻覚の発生を抑え、関連性が高く文脈に即した応答を実現します。

エージェント指向 RAG のアーキテクチャ: 単一エージェント vs. 複数エージェント システム

エージェント指向 RAG のアーキテクチャは、LLM によって駆動されるエージェントが多様な情報源から情報を取得し、処理することに依拠します。これらの���ステムは単一エージェント構成と複数エージェント構成に分けられ、それぞれ効率性、モジュール性、スケーラビリティの観点でトレードオフがあります。
本節では、リトリーバル拡張生成(RAG)システムにおける単一エージェントおよび複数エージェントのアーキテクチャについて、それぞれの機能、トレードオフ、ユースケースを詳しく解説します。

単一エージェントシステム: シンプルだが制約あり


単一エージェントシステムでは、1 体のエージェントがリトリーバルの全パイプラインを管理します。エージェントは次を行います。
  • クエリを処理する
  • 関連するデータソースを判定する
  • 必要な情報を取得する
  • LLM に渡す前に結果を統合する
この方式は単純で効率的ですが、リトリーバルの課題が複雑になるにつれて制約が生じます。単一エージェントは次のすべてを担う必要があります。
  • 複数のリトリーバル戦略(例: ベクトル検索、キーワードマッチング)
  • 多様なクエリ形式(例: 構造化データと非構造化データ)
  • さまざまなランキング機構関連性の高い情報を優先する
リトリーバルの複雑性が増すにつれ、単一エージェントがボトルネックとなり、システムのスケールと保守が難しくなります。

クエリルーティングの単一エージェント方式

これらの課題がある一方で、単一エージェントアーキテクチャは、構造化され比較的単純なリトリーバルタスクには依然として高い効果を発揮します。集中型のクエリルーターとして機能し、クエリ処理と応答生成を効率的に管理します。
単一エージェント型 RAG システムの主な機能は次のとおりです。
  • クエリの前処理リトリーバル前のクエリの精緻化と再構成
  • 多段リトリーバル:検索を反復的に精緻化し、複数回の取得結果を統合する
  • 検証とランキング:低信頼ソースのフィルタリングと、取得データの相互参照
  • 外部ツール統合:ベクトルデータベース、API、ウェブ検索モジュール、構造化ナレッジベースとの接続
単一エージェントRAGを使うべき場面
  • 〜を伴うタスク一貫したクエリ構造(例: 構造化データベースの照会)
  • 低レイテンシ高速かつ集中的な意思決定が求められる用途
  • 〜なシステム多段の推論を必要としないまたは複雑な相互参照
しかし、より複雑なリトリーバル要件には、マルチエージェントシステムがスケーラブルな代替案となります。そこで登場するのがマルチエージェントシステムです。

マルチエージェントシステム: 専門化とスケーラビリティ



マルチエージェントシステムは、特定のリトリーバル手法やデータ型に最適化された専門エージェントにタスクを分散することで、シングルエージェントの制約を克服します。単一のエージェントがリトリーバルの全ロジックを管理するのではなく、役割を委譲することで、モジュール型でスケーラブルなリトリーバル基盤を実現します。
例えば:
  • ベクトル検索エージェントセマンティック検索でベクトルデータベースから情報を取得する
  • Web検索エージェント外部ソースからリアルタイムデータを取得する
  • 構造化データエージェント��エリデータベース、企業記録、メールシステムを照会する
マルチエージェントシステムの利点
  • スケーラビリティの向上既存のシステムを損なうことなく新しいリトリーバルエージェントを追加できる
  • 最適化の強化各エージェントは特定のデータ型に最適化されており、取得精度が向上する
  • デバッグと開発の容易化パイプライン全体に影響を与えずに各エージェントを個別に変更できる
ただし、マルチエージェントシステムには課題も伴います。
  • 調整のオーバーヘッド複数のエージェントを管理するには、高度なクエリルーティングと集約手法が必要になる
  • 計算コストの増大複数のエージェント呼び出しは LLM の処理オーバーヘッドを増やし、パフォーマンスに影響を与える

高度な推論と協調のためのマルチエージェントシステム

マルチエージェントアーキテクチャは、複雑なリトリーバルワークフローの処理に優れており、とりわけ次のような状況で力を発揮します。
  • 相互参照より的確な応答のための複数のデータソース
  • 多段推論タスク取得した情報を反復的に精緻化する必要がある場合
  • 適応型リトリーバルエージェントがクエリの複雑さに応じてリトリーバル戦略を調整する場面
構造化された連携によって、これらのエージェントは検索戦略を反復的に洗練し、より正確で文脈に即し、説明可能な応答を実現します。

マルチエージェントRAGを使うべきタイミング

  • 高リスクなリトリーバルタスク(例: 法律調査、金融分析)
  • 動的クエリリアルタイムの相互参照を要する場合
  • 知識集約型アプリケーション複数ソースでの検証が必要な場合
マルチエージェントシステムは複雑性を高める一方で、情報を動的に処理・精緻化できるため、高度な推論タスクに強力です。

単一エージェント対マルチエージェントを超えて: そのほかのRAGアーキテクチャ

これらのアーキテクチャ以外にも、次のようなRAGのバリエーションがあります。
  • GraphRAG、知識グラフを活用した構造化・関係駆動型のリトリーバル
  • モジュラーRAG: 異なるデータソースに対してリトリーバル構成要素をプラグアンドプレイで入れ替えられる設計
これらのアーキテクチャのより包括的な概要については、こちらを参照してください エージェント指向リトリーバル強化生成: Agentic RAG に関するサーベイ Aditi Singh らによる論文。RAG の全体像を文脈化する詳細な知見と図解が提供されています。
次の数セクションで同論文の優れた図版を使用する許可をくださった著者の皆さまに、心より感謝申し上げます。
💡

ステップバイステップチュートリアル: シングルエージェントRAGシステムの構築

このチュートリアルでは、Weights & Biases (W&B) の2つの主要ツールを対象に、複数のリポジトリを横断検索できるシングルエージェントのリトリーバル強化生成(RAG)システムを実装します。
  • W&B Experiment Tracking機械学習の実験を追跡・可視化・管理するためのフレームワーク。開発ライフサイクル全体にわたり、モデル性能のモニタリング、ハイパーパラメータの管理、データセットの整理を支援します。
  • W&B Weave大規模言語モデル(LLM)を活用したアプリケーションを開発するためのツールキット。プロンプトや対話の追跡・評価・デバッグをプログラムから行える機能を提供し、ワークフローの改善を容易にします。
両方のツールを横断してリトリーバルを統合することで、システムは次の目的に関連する情報を提供します。
  • 一般的な機械学習実験(W&B ドキュメントおよび Issue トラッキング経由)
  • LLM 開発とデバッグ(Weave ドキュメントおよびディスカッション経由)

ソフトウェアエンジニアリングにおけるユーザークエリの理解

ユーザークエリは通常、次の二つのカテゴリに分類されます。
  1. 「やり方」と API の使い方に関する質問ユーザーは、機能や関数、API エンドポイントの使い方に関するガイダンスを必要としている
  2. トラブルシューティングと Issue 解決ユーザーは、エラーや想定外の挙動、バグを報告する
これらのクエリに対処するために、単一エージェントの RAG システムは次を行います。
  • クエリが API の使い方に関するものか、デバッグに関するものかを判定する
  • 手順系のクエリに対してドキュメントを検索する
  • トラブルシューティングのクエリに関連する GitHub Issues を取得する
  • 必要に応じて GitHub の Issue ディスカッションを参照しつつ、GPT-4o を用いて応答を生成する

実装の概要

本システムは次の4つの主要なステップに従います。

1. データを収集し、前処理する

  • W&B と Weave のリポジトリの最新バージョンをクローンする
  • ドキュメント(Markdown ファイル)を抽出し、インデックス化に適した形に整形する
  • GitHub の Issue を取得し、タイトル、説明、メタデータを収集する(現時点ではコメントを除外)

2. テキストを埋め込みベクトルに変換する

  • OpenAI を使用する text-embedding-3-small すべてのドキュメントに対してベクトル埋め込みを生成するモデル
  • 埋め込みベクトルを高性能なベクトルデータベースである ChromaDB に保存する
  • 検索の関連性を高めるために、ドキュメントと Issue レポートを区別する

3. クエリ分類とリトリーバルを実装する

  • ユーザーのクエリが API ドキュメント関連かデバッグ関連かを判定する
  • API 関連の場合は、ドキュメントの埋め込みを検索して関連結果を取得する
  • Issue 関連の場合は、GitHub Issue を取得してランク付けする
  • 追加のコンテキストが必要な場合は、GitHub のコメントをさらに取得する

4. GPT-4o で応答を生成する

  • 取得結果を文脈として用い、GPT-4o で応答を生成する
  • 応答では、透明性を保つために元のドキュメントまたは Issue ディスカッションを必ず参照すること。

次のステップ: スクリプトを書く

次のセクションでは、データ収集とベクトル化から実装に着手します
import os
import glob
import re
import requests
import time
from langchain_core.documents import Document
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma

# GitHub repository details
REPOS = {
"weave": "wandb/weave",
"wandb": "wandb/docs", # For documentation
}

# GitHub repositories for issues
ISSUE_REPOS = {
"weave": "wandb/weave",
"wandb": "wandb/wandb", # Correct repo for WandB issues
}

# GitHub issues URL
ISSUES_ENDPOINT = "/issues?state=all&per_page=100"

# Initialize OpenAI Embeddings
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")

# Function to download repo docs using manual git commands
def download_docs(repo_name, repo_url, path):
if os.path.exists(path):
print(f"{repo_name} documentation already exists, skipping download.")
return
print(f"Downloading {repo_name} documentation...")
# Simple git clone without sparse-checkout (more reliable)
clone_cmd = f"git clone --depth 1 https://github.com/{repo_url}.git {path}"
result = os.system(clone_cmd)
if result != 0 or not os.path.exists(path):
print(f"❌ Failed to clone {repo_name}. Check if the repository URL is correct.")
return
print(f"✅ {repo_name} documentation downloaded successfully.")

# Function to clean markdown content
def clean_markdown(text):
if not text:
return ""
text = re.sub(r'!\[.*?\]\(.*?\)', '', text) # Remove images
text = re.sub(r'\[([^\]]+)\]\((.*?)\)', r'\1', text) # Keep link text but remove URLs
return text.strip()

# Function to fetch GitHub issues without comments
def fetch_issues(repo_name, repo_url, max_pages=5):
all_issues = []
page = 1
# Paginate through the results
while page <= max_pages:
url = f"https://api.github.com/repos/{repo_url}{ISSUES_ENDPOINT}&page={page}"
headers = {"Accept": "application/vnd.github.v3+json"}
try:
print(f"Fetching page {page} of issues from {repo_name}...")
response = requests.get(url, headers=headers)
if response.status_code == 200:
issues = response.json()
# If no more issues are returned, break the loop
if not issues:
break
# Process each issue without fetching comments
for issue in issues:
# Add basic issue data to the list
all_issues.append({
"id": issue["id"],
"number": issue["number"],
"title": issue["title"],
"body": issue.get("body", ""),
"state": issue["state"],
"created_at": issue["created_at"],
"comments_count": issue.get("comments", 0),
"github_url": issue["html_url"], # URL to the GitHub issue page
"comments_url": issue["comments_url"], # Direct API URL for comments
"repo": repo_url # Store the repo info for later reference
})
print(f"Fetched {len(issues)} issues from {repo_name} (page {page})")
page += 1
time.sleep(1) # Small delay between pages
elif response.status_code == 403 and "rate limit exceeded" in response.text.lower():
print("⚠️ GitHub API rate limit exceeded. Waiting for 60 seconds...")
time.sleep(60) # Wait for rate limit to reset
continue # Retry the same page
else:
print(f"Error fetching issues from {repo_name}: {response.status_code}")
break
except Exception as e:
print(f"Exception fetching issues for {repo_name}: {e}")
break
print(f"Total issues fetched from {repo_name}: {len(all_issues)}")
return all_issues

# Function to create documents from issues (without comments)
def create_issue_documents(issues):
docs = []
for issue in issues:
# Create a document for the main issue
issue_text = f"Title: {issue['title']}\nIssue #{issue['number']}\nState: {issue['state']}\nComments: {issue['comments_count']}\n"
# Add a note about comments and where to find them
if issue['comments_count'] > 0:
issue_text += f"\nThis issue has {issue['comments_count']} comments. View at: {issue['github_url']}\n"
issue_text += f"\nBody:\n{issue['body']}"
# Create the document object with rich metadata
docs.append(Document(
page_content=clean_markdown(issue_text),
metadata={
"id": issue["id"],
"number": issue["number"],
"created_at": issue["created_at"],
"state": issue["state"],
"comments_count": issue["comments_count"],
"github_url": issue["github_url"],
"comments_url": issue["comments_url"],
"repo": issue["repo"],
"type": "issue"
}
))
return docs

# Function to process Markdown docs
def create_markdown_documents(repo_name, doc_path):
if not os.path.exists(doc_path):
print(f"⚠️ {doc_path} does not exist, skipping doc processing.")
return []
# Find markdown files recursively
md_files = glob.glob(f'{doc_path}/**/*.md', recursive=True)
if not md_files:
print(f"No markdown files found in {doc_path}.")
return []
docs = []
for md_file in md_files:
try:
with open(md_file, 'r', encoding='utf-8') as f:
content = clean_markdown(f.read())
# Create a proper Langchain Document object
docs.append(Document(
page_content=content,
metadata={
"file": md_file,
"type": "documentation"
}
))
except Exception as e:
print(f"Error processing {md_file}: {e}")
return docs

# Function to query the vector store
def search_docs(query, db_directory, k=5):
"""Search a Chroma vector store for documents similar to the query."""
try:
# Load the Chroma database
db = Chroma(persist_directory=db_directory, embedding_function=embeddings)
# Search for similar documents
results = db.similarity_search_with_score(query, k=k)
if not results:
print(f"No results found for '{query}'")
return []
print(f"Found {len(results)} results for '{query}':")
for i, (doc, score) in enumerate(results):
print(f"\nResult {i+1}:")
print(f"Score: {score}")
# Print metadata
print("Metadata:")
for key, value in doc.metadata.items():
if key in ["id", "number", "state", "comments_count", "type"]:
print(f" {key}: {value}")
# For issues with comments, show the GitHub URL
if doc.metadata.get("type") == "issue" and doc.metadata.get("comments_count", 0) > 0:
print(f" GitHub URL: {doc.metadata.get('github_url')}")
# Print content (truncated if too long)
content = doc.page_content
if len(content) > 500:
content = content[:500] + "... [truncated]"
print(f"\nContent: {content}\n")
print("-" * 40)
return results
except Exception as e:
print(f"Error searching database: {e}")
return []

# Main execution
if __name__ == "__main__":
# Create directory for vector stores
os.makedirs("db", exist_ok=True)
for repo_name, repo_url in REPOS.items():
print(f"\nProcessing {repo_name.upper()}...")

# Fetch issues from the correct repository
issue_repo_url = ISSUE_REPOS[repo_name]
print(f"Fetching issues from {issue_repo_url}...")
issues = fetch_issues(repo_name, issue_repo_url)

# Download docs
doc_path = f"{repo_name}_docs"
download_docs(repo_name, repo_url, doc_path)

# Process issues
if issues:
print(f"Creating vector store for {repo_name} issues...")
issue_docs = create_issue_documents(issues)
# Create Chroma vector store for issues
issues_db = Chroma.from_documents(
documents=issue_docs,
embedding=embeddings,
persist_directory=f"db/{repo_name}_issues"
)
print(f"✅ Saved {repo_name} issues vector store ({len(issue_docs)} documents)")
else:
print("No issues to process.")

# Process docs
print(f"Creating vector store for {repo_name} documentation...")
md_docs = create_markdown_documents(repo_name, doc_path)
if md_docs:
# Create Chroma vector store for documentation
docs_db = Chroma.from_documents(
documents=md_docs,
embedding=embeddings,
persist_directory=f"db/{repo_name}_docs"
)
print(f"✅ Saved {repo_name} documentation vector store ({len(md_docs)} documents)")
else:
print("No documentation to process.")

print("\n✅ Data ingestion completed successfully.")
# Example search to verify everything works
print("\nTesting search functionality...")
search_docs("authentication issues", "db/weave_issues", k=2)
ベクトルデータベースの準備が整えば、システムはユーザーのクエリを処理し、関連するドキュメントや GitHub Issue の過去ディスカッションを取得できるようになります。次のステップでは、ユーザークエリとの類似度に基づいてデータベースを検索するリトリーバル機構を実装します。

クエリルーティングエージェントの構築

クエリルーティングエージェントは、RAG パイプラインにおけるリトリーバル最適化に不可欠です。利用可能なすべてのソースを無差別に検索するのではなく、ルーティングエージェントがユーザークエリを分類し、最も関連性の高いナレッジベースへ振り分けることで、効率と精度を向上させます。

クエリルーターの仕組み

ルーティングエージェントは、次の2つの主要な分類タスクを実行します。
  1. 関連ツールの特定そのクエリは W&B Experiment Tracking または Weave に関するものか?
  2. クエリ種別の判定ユーザーはドキュメントの参照を求めているのか、それとも不具合のトラブルシューティングを求めているのかを判定する。
  • ドキュメント関連のクエリ通常はユーザーガイド、チュートリアル、API リファレンスを含む
  • トラブルシューティング関連のクエリGitHub のディスカッション、バグ報告、Issue の解決策を取得して回答するのが最適です。
クエリが曖昧な場合、エージェントは誤った単一選択を強要せず、複数のナレッジベースを返す。

コード由来のシグナルで精度を高める

分類精度を高めるために、エージェントは各ツールに特有の構文やコードスニペットを活用する。
  • W&B 関連のクエリにはしばしば含まれる wandb.init() またはログ指標への参照
  • Weave 関連のクエリには含まれる可能性がある weave.op() または function tracking への言及
クエリにこれらのパターンが現れた場合、エージェントはそれらを追加のルーティングシグナルとして用い、検索精度を精緻化する。

曖昧なクエリへの対処

このエージェントは、曖昧なクエリを賢く扱うよう設計されている。
たとえば、ユーザーが次のように尋ねた場合:
AI ワークフローのエラーはどうデバッグすればよいですか?
この問題が W&B に関連するのか、Weave に関連するのかをシステムが確実には判定できません。その場合、エージェントは網羅的なリトリーバルを確保するため、両方の関連ナレッジベースを返します。

実装アプローチ

このロジックを実装するために、エージェントへ次を指示するシステムプロンプトを定義する。
  • ユーザーのクエリを分析するそしてそれに応じて分類する
  • 構造化JSONを返す検索対象のナレッジベースを指定する出力
  • マルチソース・リトリーバルをサポートするあいまいさや重複のあるクエリに対して
このルーティング機構を実装することで、私たちは次を実現します。
  • 不要な検索を削減する
  • 応答の関連性を高める
  • 構造化された効率的なリトリーバルプロセスを構築する

次のステップ: クエリルーティングエージェントをコードで実装する

では、クエリルーティングエージェントをコードで実装し、リトリーバルパイプラインに統合します。
You are a query-routing assistant that determines whether a user's question is related to Weights & Biases (wandb) or Weave and directs them to the most relevant database. Users may ask about documentation, troubleshooting issues, or implementation details for either tool.

Your task is to analyze the query and route it to all relevant sources if uncertainty exists. If a query could apply to multiple areas, return multiple results rather than choosing just one.

The available knowledge bases are:
1. "Weave Issues" - Contains GitHub issues from the wandb/weave repository
2. "Weave Documentation" - Contains documentation for Weave
3. "WandB Issues" - Contains GitHub issues from the wandb/wandb repository
4. "WandB Documentation" - Contains documentation for Weights & Biases

Return a JSON object with the following format:
{
"knowledge_bases": ["name_of_kb1", "name_of_kb2"]
}

## Library Descriptions for Context

### Weights & Biases (wandb) – Experiment Tracking & ML Workflow Management
Weights & Biases (wandb) is a tool for tracking and visualizing machine learning experiments. It helps researchers and engineers:
* Log and monitor metrics like loss, accuracy, and learning rate.
* Track hyperparameters and model configurations for experiment reproducibility.
* Store and visualize model artifacts and training progress.
* Integrate seamlessly with deep learning frameworks like PyTorch, TensorFlow, and Hugging Face Transformers.
* Collaborate via dashboards that display real-time and historical training runs.
It is widely used in research and production to optimize models, compare runs, and fine-tune hyperparameters efficiently.

### W&B Weave – Generative AI & LLM Debugging Toolkit
Weave is a toolkit designed for developing, debugging, and evaluating Generative AI applications, particularly those using Large Language Models (LLMs). It enables users to:
* Log inputs and outputs from LLMs for better debugging.
* Build evaluations to compare different model responses.
* Organize information across LLM workflows from experimentation to production.
* Track interactions within multi-step AI pipelines to analyze decision-making processes.
Weave is essential for understanding and improving the performance of AI agents, LLM-based chatbots, and retrieval-augmented generation (RAG) systems.

## Routing Logic
* If the user asks about experiment tracking, ML metrics, logging, hyperparameters, dashboards, or integrations, route to W&B Docs.
* If they report an error, unexpected behavior, or API issue related to W&B, route to W&B Issues.
* If the user asks about LLM evaluation, function tracking, Weave UI development, or Weave's role in ML workflows, route to Weave Docs.
* If they report errors related to Weave tracking or data storage, route to Weave Issues.
* If uncertain, include all possibly relevant sources instead of picking only one.

## Code Samples for Context
If the query is vague or unclear, match it against the functionality of each tool using these sample code snippets:

### W&B Experiment Tracking Example (wandb Docs/Issues)
```python
import wandb
wandb.init(project="my_project", config={"learning_rate": 0.001, "epochs": 10})
for epoch in range(wandb.config.epochs):
train_loss = 0.01 * (wandb.config.epochs - epoch)
train_accuracy = 0.1 * epoch
wandb.log({'epoch': epoch, 'train_loss': train_loss, 'train_accuracy': train_accuracy})
```
If the query involves logging, training loops, metrics, dashboards, or hyperparameters, it should likely be routed to W&B Docs or W&B Issues if errors are involved.

### Weave Function Tracking Example (Weave Docs/Issues)
```python
import weave
weave.init(project_name="my_weave_project")
@weave.op()
def add_numbers(a: int, b: int) -> int:
return a + b
result = add_numbers(5, 7)
print(f'Result: {result}')
```
If the query involves tracking function calls, logging LLM interactions, debugging AI workflows, or analyzing execution steps, route it to Weave Docs or Weave Issues if errors occur.


Focus on understanding:
- Is the query about Weave or WandB?
- Is the user looking for documentation or issues/bugs?
- If the query mentions "bug" or "issue" or describes a problem, prioritize the Issues knowledge base
- If the query is asking how to use a feature or understand concepts, prioritize Documentation
- If unclear whether the query is about Weave or WandB, include knowledge bases for both

Examples:
1. For "How do I track experiments in wandb?"
- Knowledge bases: ["WandB Documentation"]

2. For "Weave authentication failing"
- Knowledge bases: ["Weave Issues"]

3. For "How to visualize neural networks in Weights & Biases"
- Knowledge bases: ["WandB Documentation"]

4. For "LLM output tracking shows errors in production"
- Knowledge bases: ["Weave Issues", "Weave Documentation"]

Always include at least one knowledge base in your response.

Return a JSON object with the following format:
{
"knowledge_bases": ["name_of_kb1", "name_of_kb2"]
}

社内データベースへのクエリ実行

では、ベクトルデータベースとクエリルーティングエージェントを用いて、クエリのリトリーバルを実装します。プロセスは次の3つの主要ステップで構成されます。
  1. クエリ分類AIによるルーターが、どのナレッジベースを検索すべきかを判定する
  2. ベクトル検索システムは Chroma データベースから最も関連性の高い結果を取得し、意味的類似度でランキングします。
  3. 応答生成 ### 目的 このドキュメントは、Agentic RAG の導入前に、現行のバニラ RAG を設計・実装するための最小構成と、その評価方法を示します。まずは信頼できるベースラインを確立し、ボトルネック(取得、再ランキング、プロンプト、コンテキスト長など)を切り分けられる状態にします。 GPT-4o 取得したソースへの参照を含めて可視性を確保しつつ、最終的な回答を統合して提示する

ステップ1: クエリ分類

クエリルーティングエージェントはユーザークエリを分類し、次の項目を指定した構造化 JSON を返します。
  • 関連ナレッジベース(例) W&B ドキュメント ### 目的 このドキュメントは、Agentic RAG の導入前に、現行のバニラ RAG を設計・実装するための最小構成と、その評価方法を示します。まずは信頼できるベースラインを確立し、ボトルネック(取得、再ランキング、プロンプト、コンテキスト長など)を切り分けられる状態にします。 Weave ドキュメント、GitHub Issues
  • マルチソース取得クエリが曖昧な場合、関連性を最大化すること
すべてのソースを手当たり次第に検索するのではなく、このアプローチにより効率と精度が向上します。

手順 2: ベクトルデータベースの検索

ルーティングが決定されたら、システムは ChromaDB を用いてセマンティック検索を実行します。ChromaDB には次の内容が含まれます。
  • W&B の埋め込みベクトルWeave ドキュメント
  • 過去の GitHub Issueトラブルシューティング用クエリ向けに
取得したドキュメントが GitHub Issue の場合、システムは関連するコメントやディスカッションを追加取得して処理し、より多くのコンテキストと潜在的な解決策を提供します。

手順 3: 応答の生成

取得したコンテンツは GPT-4o に渡され、GPT-4o は次を行います。
  • 最も関連性の高いドキュメントに基づいて応答を統合生成します
  • 透明性を担保するため、ドキュメントおよび GitHub Issue への参照を含みます。
  • エッジケースへの対応 — 関連する結果が見つからない場合、システムは次を行います。
    • ユーザーに確認や補足説明を求める。
    • 検索を絞り込むための代替クエリを提案します。
クエリ分類、セマンティック検索、知的な応答生成を統合することで、このパイプラインは正確で透明性が高く、文脈に即した回答を保証します。

次のステップ: コードの実装

では、次のスクリプトでクエリ処理ロジックを実装していきましょう。
import sys
import os
import json
from litellm import completion
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
import weave; weave.init("agentic_rag")
import requests
import os
import json
import time

# Create cache directory if it doesn't exist
os.makedirs("comment_cache", exist_ok=True)

# Initialize OpenAI Embeddings
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")

# Configure which databases to search (can be modified to include/exclude databases)
DATABASES_TO_SEARCH = [
{"name": "Weave Issues", "path": "db/weave_issues"},
{"name": "Weave Documentation", "path": "db/weave_docs"},
{"name": "WandB Issues", "path": "db/wandb_issues"},
{"name": "WandB Documentation", "path": "db/wandb_docs"}
]

def load_router_prompt():
"""Load the router prompt from router_prompt.txt"""
try:
with open("router_prompt.txt", "r") as f:
return f.read()
except FileNotFoundError:
print("❌ Error: router_prompt.txt not found.")
print("Please create this file with your router prompt before running the script.")
sys.exit(1)

def search_vector_store(query, db_directory, k=5):
"""Search a vector store and return results."""
try:
# Load the Chroma database
db = Chroma(persist_directory=db_directory, embedding_function=embeddings)
# Search for similar documents
results = db.similarity_search_with_score(query, k=k)
return results
except Exception as e:
print(f"❌ Error searching database at {db_directory}: {e}")
return []


def extract_json_from_response(response_text):
"""Extract JSON from response text, handling code blocks."""
# Try to find JSON in code blocks
import re
json_pattern = r'```(?:json)?\s*(\{.*?\})\s*```'
match = re.search(json_pattern, response_text, re.DOTALL)
if match:
# Extract JSON from code block
json_str = match.group(1)
else:
# Assume the entire response is JSON
json_str = response_text
# Remove any non-JSON content
json_str = json_str.strip()
return json.loads(json_str)


def route_query(user_query):
"""Use LiteLLM to route the query to the appropriate databases."""
router_prompt = load_router_prompt()
try:
# Call LiteLLM with the router prompt
response = completion(
model="openai/gpt-4o",
messages=[
{"role": "system", "content": router_prompt},
{"role": "user", "content": user_query}
]
)
# Extract the content from the response
router_response = response.choices[0].message.content
# Parse the JSON response
try:
# parsed_response = json.loads(router_response)
parsed_response = extract_json_from_response(router_response)
return parsed_response
except json.JSONDecodeError:
print("❌ Error: Router didn't return valid JSON. Using all knowledge bases.")
print(f"Router response: {router_response}")
return {
"knowledge_bases": [db["name"] for db in DATABASES_TO_SEARCH],
"search_query": user_query,
"reasoning": "Fallback due to JSON parsing error"
}
except Exception as e:
print(f"❌ Error calling LiteLLM: {e}")
return {
"knowledge_bases": [db["name"] for db in DATABASES_TO_SEARCH],
"search_query": user_query,
"reasoning": f"Fallback due to error: {str(e)}"
}

def get_kb_by_name(name):
"""Get the knowledge base by name."""
for db in DATABASES_TO_SEARCH:
if db["name"] == name:
return db
return None

def fetch_comments_for_issue(repo, issue_number):

# Check cache first
cache_file = f"comment_cache/{repo.replace('/', '_')}_issue_{issue_number}_comments.json"
if os.path.exists(cache_file):
try:
with open(cache_file, 'r') as f:
comments = json.load(f)
print(f"Loaded {len(comments)} comments for issue #{issue_number} from cache")
return comments
except Exception as e:
print(f"Error reading cache: {e}")
# Continue to fetch if cache read fails
# Fetch from GitHub API
url = f"https://api.github.com/repos/{repo}/issues/{issue_number}/comments"
headers = {"Accept": "application/vnd.github.v3+json"}
# Add GitHub token if available
if os.environ.get("GITHUB_TOKEN"):
headers["Authorization"] = f"token {os.environ.get('GITHUB_TOKEN')}"
try:
print(f"Fetching comments for issue #{issue_number} from {repo}...")
response = requests.get(url, headers=headers)
if response.status_code == 200:
comments = response.json()
# Cache the comments
with open(cache_file, 'w') as f:
json.dump(comments, f, indent=2)
print(f"✅ Fetched and cached {len(comments)} comments for issue #{issue_number}")
return comments
elif response.status_code == 403 and "rate limit exceeded" in response.text.lower():
print("⚠️ GitHub API rate limit exceeded. Waiting 60 seconds...")
time.sleep(60)
return fetch_comments_for_issue(repo, issue_number) # Retry after waiting
else:
print(f"Error fetching comments: {response.status_code}")
return []
except Exception as e:
print(f"Exception fetching comments: {e}")
return []

def format_comments_for_context(comments):

if not comments:
return ""
comments_text = "\n\nCOMMENTS:\n"
for i, comment in enumerate(comments):
user = comment.get('user', {}).get('login', 'anonymous')
created_at = comment.get('created_at', 'unknown date')
body = comment.get('body', '').strip()
comments_text += f"\n[Comment #{i+1} by {user} on {created_at}]\n{body}\n"
return comments_text

def generate_response(user_query, relevant_docs, query_analysis):
"""Generate a response using LiteLLM based on the retrieved documents."""
# Prepare context from documents
context = ""
github_links = []
for i, (doc, score) in enumerate(relevant_docs):
# Add the document to context
context += f"--- Document {i+1} (Relevance: {score:.4f}) ---\n"
# Add source information
if doc.metadata.get('type') == 'issue':
issue_num = doc.metadata.get('number', 'Unknown')
repo = doc.metadata.get('repo', 'Unknown')
context += f"Source: GitHub Issue #{issue_num} in {repo}\n"
# If issue has comments, fetch and add them to context
if doc.metadata.get('comments_count', 0) > 0:
github_url = doc.metadata.get('github_url')
if github_url:
github_links.append(github_url)
# Fetch and append comments
print(f"Fetching comments for issue #{issue_num}...")
comments = fetch_comments_for_issue(repo, issue_num)
if comments:
comments_text = format_comments_for_context(comments)
context += doc.page_content + comments_text + "\n\n"
continue # Skip the standard content addition below
else:
context += f"Source: {doc.metadata.get('file', 'Unknown')}\n"
# Add content (only reached if the issue doesn't have comments or isn't an issue)
context += doc.page_content
context += "\n\n"
# Create the prompt for the response
response_prompt = f"""You are an AI assistant for Weights & Biases (wandb) and Weave.
You've been given several documents retrieved from a search based on the user's query.
Use the information in these documents to answer the user's question.

If the documents don't contain the necessary information to answer the question, admit that you don't know
rather than making up an answer. If appropriate, suggest what the user might search for instead.

User Query: {user_query}

Retrieved Documents:
{context}

Based on these documents, provide a helpful response to the user's query.
Pay special attention to issue comments as they often contain solutions to the problems described in the issues.
"""

try:
# Call LiteLLM with the response prompt
response = completion(
model="openai/gpt-4o",
messages=[
{"role": "system", "content": response_prompt},
{"role": "user", "content": user_query}
]
)
# Extract the content from the response
return response.choices[0].message.content
except Exception as e:
print(f"❌ Error generating response: {e}")
return f"I encountered an error while generating a response: {str(e)}"


def agentic_rag(user_query):
"""Main function for the agentic RAG system."""
print(f"\n🔍 Processing query: '{user_query}'")
print("-" * 80)
# Step 1: Route the query
query_analysis = route_query(user_query)
print(f"✅ Query Analysis:")
print(f"- Selected Knowledge Bases: {', '.join(query_analysis['knowledge_bases'])}")
# print(f"- Refined Search Query: '{query_analysis['search_query']}'")
# print(f"- Reasoning: {query_analysis['reasoning']}")
# Step 2: Search the selected knowledge bases
all_results = []
for kb_name in query_analysis["knowledge_bases"]:
kb = get_kb_by_name(kb_name)
if kb and os.path.exists(kb["path"]):
# print(f"\n🔍 Searching '{kb['name']}' for: '{query_analysis['search_query']}'")
results = search_vector_store(user_query, kb["path"], k=3)
if results:
print(f"Found {len(results)} results in {kb['name']}")
# Print out GitHub URLs for issues with comments
for doc, _ in results:
if doc.metadata.get('type') == 'issue' and doc.metadata.get('comments_count', 0) > 0:
github_url = doc.metadata.get('github_url')
if github_url:
print(f" - Issue with comments: {github_url}")
all_results.extend(results)
else:
print(f"No results found in {kb['name']}")
else:
print(f"⚠️ Knowledge base '{kb_name}' not found or not available.")
# Step 3: Sort results by score (ascending since lower score = more relevant in Chroma)
all_results.sort(key=lambda x: x[1])
# Take top results (up to 5)
top_results = all_results[:5]
if not top_results:
print("❌ No relevant documents found across any knowledge base.")
return "I couldn't find any relevant information to answer your question. Could you please rephrase or provide more details?"
# Step 4: Generate a response
print(f"\n💬 Generating response based on {len(top_results)} documents...")
response = generate_response(user_query, top_results, query_analysis)
return response

def interactive_mode():
"""Run the RAG system in interactive mode."""
print("\n" + "=" * 80)
print("AGENTIC RAG SYSTEM")
print("=" * 80)
print("Type 'exit' to quit.")
print("To ask a question, type your question (can be multiple lines)")
print("When finished, press Enter on an empty line to submit.")
while True:
print("\nYour question (multi-line, empty line to submit):")
lines = []
while True:
line = input().strip()
# Check for exit command on single line
if not lines and line.lower() in ['exit', 'quit']:
return
# Empty line finishes input if we already have some content
if not line and lines:
break
# Otherwise add the line to our input
if line:
lines.append(line)
# Combine all lines into a single query
user_query = "\n".join(lines)
if not user_query:
print("Please enter a question.")
continue
print("\n" + "-" * 80)
print("Processing your query...")
response = agentic_rag(user_query)
print("\n" + "=" * 80)
print("ANSWER:")
print(response)
print("=" * 80)

if __name__ == "__main__":
# Check for API key
if not os.environ.get("OPENAI_API_KEY"):
api_key = input("Please enter your OpenAI API key: ").strip()
if api_key:
os.environ["OPENAI_API_KEY"] = api_key
else:
print("❌ No API key provided. Exiting.")
sys.exit(1)
interactive_mode()
Weave はシステムの挙動とインタラクションを監視・記録し、クエリパターン、応答品質、リトリーバル効率を分析できるようにします。これにより、システムの実行状況と改善余地を把握できます。クエリ、取得結果、生成応答をロギングすることで、Weave は RAG パイプラインの精緻化とリトリーバル戦略の最適化を支援します。また、クエリのルーティング経路を追跡し、最も関連性の高い知識ソースをシステムが選択しているかを確認できるため、潜在的な問題のデバッグにも役立ちます。
このシングルエージェント設計は、単純で一元化されたリトリーバルワークフローを保ちながら、効率的なクエリ処理を実現します。これにより、構造化データ、リアルタイム検索、反復的な精緻化をそれぞれ担当する複数のエージェントへ拡張するための土台が整います。

今後の改善候補(クリックして展開)

Claude 3.7 のツール使用の統合

現行のシステムは機能しているものの、運用がやや手作業に寄り、柔軟性に欠けます。プロンプトの綿密なチューニングや明示的なデータベース設定が必要で、取得方法を変えたいたびに人手による介入が少なからず発生します。新しいデータソース(追加のドキュメント群や別の GitHub リポジトリなど)を組み込む場合も、ベクトルデータベースの手動更新、取得ロジックの修正、統合の整合性確認が必要です。これでも動作はしますが、スケールしません。
システムをより柔軟かつ自律的にするために、統合できます Claude 3.7ツール使用を組み合わせることで、リトリーバル過程について推論し、知識ソースへ動的にクエリを発行できるようになります。どのデータベースを検索するかをあらかじめ定義したルールに頼るのではなく、Claude はユーザーのクエリを分析し、最も関連性の高いソースを判断したうえで、リアルタイムに検索を実行できます。これは ReAct パラダイムに沿った手法であり、Claude は単に受動的に情報をリトリーブするのではなく、最も関連性の高い結果をどのように取得するかを能動的に推論します。
この戦略にはいくつかの利点があります。
  • 人手による介入の削減:プロンプトやリトリーバル規則を絶えず調整するのではなく、リトリーバルの判断を Claude に動的に任せられます。
  • スケーリングが容易:新しいデータソースを追加しても、システムに大幅な改修は不要です。Claude は新しいツールを認識し、必要に応じて活用できます。
  • より優れたクエリ対応:クエリに曖昧さがある場合、Claude はその再構成を行うか、単一の不十分な結果を返すのではなく複数の検索を実行できます。
Weave は、インタラクションを追跡し、どのリトリーバル戦略が最も有効かを可視化し、時間の経過とともにクエリの処理方法を洗練するうえで重要な役割を果たします。
以下では、ツール使用を通じて Claude にベクトルデータベースへの直接アクセスを与え、ドキュメント、GitHub Issues、ユーザーのディスカッションをリアルタイムに検索できるようにします。
import os
import json
import sys
from anthropic import Anthropic
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
import requests
import time
import weave; weave.init("claude_agentic_rag")

# Create cache directory if it doesn't exist
os.makedirs("comment_cache", exist_ok=True)

# Initialize OpenAI Embeddings for vector search
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")

# Configure knowledge bases
KNOWLEDGE_BASES = [
{"name": "weave_issues", "path": "db/weave_issues", "description": "GitHub issues related to Weave, containing bug reports and feature requests."},
{"name": "weave_docs", "path": "db/weave_docs", "description": "Documentation for Weave, explaining its features, APIs, and usage."},
{"name": "wandb_issues", "path": "db/wandb_issues", "description": "GitHub issues related to Weights & Biases (wandb), containing bug reports and feature requests."},
{"name": "wandb_docs", "path": "db/wandb_docs", "description": "Documentation for Weights & Biases (wandb), explaining its features, APIs, and usage."}
]

# Initialize Anthropic client
CLAUDE_API_KEY = os.environ.get("ANTHROPIC_API_KEY")
MODEL = "claude-3-7-sonnet-20250219"
client = Anthropic(api_key=CLAUDE_API_KEY)

# Define search tools
TOOLS = [
{
"name": "search_weave_issues",
"description": "Search for GitHub issues related to Weave. Use this for debugging, error messages, or when the user mentions bugs or problems with Weave.",
"input_schema": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "The search query to find relevant Weave issues."}
},
"required": ["query"]
}
},
{
"name": "search_weave_docs",
"description": "Search Weave documentation. Use this for understanding Weave features, APIs, usage examples, or 'how-to' questions about Weave.",
"input_schema": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "The search query to find relevant Weave documentation."}
},
"required": ["query"]
}
},
{
"name": "search_wandb_issues",
"description": "Search for GitHub issues related to Weights & Biases (wandb). Use this for debugging, error messages, or when the user mentions bugs or problems with wandb.",
"input_schema": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "The search query to find relevant Weights & Biases issues."}
},
"required": ["query"]
}
},
{
"name": "search_wandb_docs",
"description": "Search Weights & Biases documentation. Use this for understanding wandb features, APIs, usage examples, or 'how-to' questions about wandb.",
"input_schema": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "The search query to find relevant Weights & Biases documentation."}
},
"required": ["query"]
}
}
]
@weave.op
def search_vector_store(kb_name, query, k=3):
"""Search a vector store and return results, automatically fetching comments for issues."""
kb = next((kb for kb in KNOWLEDGE_BASES if kb["name"] == kb_name), None)
if not kb:
return {"error": f"Knowledge base '{kb_name}' not found"}
db_path = kb["path"]
if not os.path.exists(db_path):
return {"error": f"Knowledge base at '{db_path}' does not exist"}
try:
# Load the Chroma database
db = Chroma(persist_directory=db_path, embedding_function=embeddings)
# Search for similar documents
results = db.similarity_search_with_score(query, k=k)
# Format results
formatted_results = []
for i, (doc, score) in enumerate(results):
# Format document
result = {
"document_id": i + 1,
"relevance_score": float(score),
"content": doc.page_content,
"metadata": doc.metadata
}
# Add special handling for issues
if doc.metadata.get('type') == 'issue':
issue_number = doc.metadata.get('number')
repo = doc.metadata.get('repo')
result["issue_number"] = issue_number
result["repo"] = repo
result["github_url"] = doc.metadata.get('github_url')
result["comments_count"] = doc.metadata.get('comments_count', 0)
# Automatically fetch comments if available
if doc.metadata.get('comments_count', 0) > 0 and repo and issue_number:
print(f" Fetching {doc.metadata.get('comments_count')} comments for {repo}#{issue_number}")
comments_result = fetch_github_comments(repo, issue_number)
result["comments"] = comments_result
formatted_results.append(result)
return {
"results_count": len(formatted_results),
"results": formatted_results
}
except Exception as e:
return {"error": f"Error searching knowledge base: {str(e)}"}

def fetch_github_comments(repo, issue_number):
"""Fetch comments for a GitHub issue."""
# Check cache first
cache_file = f"comment_cache/{repo.replace('/', '_')}_issue_{issue_number}_comments.json"
if os.path.exists(cache_file):
try:
with open(cache_file, 'r') as f:
comments = json.load(f)
return {
"source": "cache",
"comments_count": len(comments),
"comments": comments
}
except Exception as e:
# Continue to fetch if cache read fails
pass
# Fetch from GitHub API
url = f"https://api.github.com/repos/{repo}/issues/{issue_number}/comments"
headers = {"Accept": "application/vnd.github.v3+json"}
# Add GitHub token if available
if os.environ.get("GITHUB_TOKEN"):
headers["Authorization"] = f"token {os.environ.get('GITHUB_TOKEN')}"
try:
response = requests.get(url, headers=headers)
if response.status_code == 200:
comments = response.json()
# Cache the comments
with open(cache_file, 'w') as f:
json.dump(comments, f, indent=2)
# Format the comments
formatted_comments = []
for comment in comments:
formatted_comments.append({
"id": comment.get("id"),
"user": comment.get("user", {}).get("login", "anonymous"),
"created_at": comment.get("created_at"),
"body": comment.get("body", "")
})
return {
"source": "github_api",
"comments_count": len(formatted_comments),
"comments": formatted_comments
}
elif response.status_code == 403 and "rate limit exceeded" in response.text.lower():
return {
"error": "GitHub API rate limit exceeded",
"suggestion": "Set a GITHUB_TOKEN environment variable to increase rate limits"
}
else:
return {
"error": f"Error fetching comments: HTTP {response.status_code}",
"response": response.text
}
except Exception as e:
return {"error": f"Exception fetching comments: {str(e)}"}

def execute_tool(tool_name, tool_input):
"""Execute the appropriate tool based on the name and input."""
if tool_name == "search_weave_issues":
return search_vector_store("weave_issues", tool_input["query"])
elif tool_name == "search_weave_docs":
return search_vector_store("weave_docs", tool_input["query"])
elif tool_name == "search_wandb_issues":
return search_vector_store("wandb_issues", tool_input["query"])
elif tool_name == "search_wandb_docs":
return search_vector_store("wandb_docs", tool_input["query"])
else:
return {"error": "Unknown tool requested"}

def get_system_prompt():
"""Get the system prompt for Claude."""
return """You are an AI assistant for Weights & Biases (wandb) and Weave.

You have access to several searchable knowledge bases:
1. Weave Issues - Contains GitHub issues related to Weave
2. Weave Documentation - Contains documentation for Weave
3. WandB Issues - Contains GitHub issues related to Weights & Biases
4. WandB Documentation - Contains documentation for Weights & Biases

To assist the user effectively:
1. Analyze their question to determine if it's about Weave or WandB (or both)
2. Determine if they're asking about documentation or having an issue
3. Search the appropriate knowledge base(s) using the search tools
4. If an issue looks relevant but lacks context, fetch its GitHub comments
5. Synthesize the retrieved information to provide a detailed answer
6. Make sure to provide the full links to the github issue so the user can investigate further
7. For doc links- DO NOT hallucinate links ---> just provide a verbatim repeat of the docs you think could be helpful

For code-related queries or error messages, search the issues database first. For "how to" questions, search the documentation database first.

Example workflows:
- For "How do I track experiments in wandb?": Search wandb_docs
- For "Weave authentication failing": Search weave_issues, then fetch comments if needed
- For a query mentioning an error message: Search the relevant issues database with the error message

Aim to provide comprehensive answers based on the most relevant retrieved documents. If you don't find relevant information in your first search, try different search queries or additional knowledge bases.
"""

@weave.op
def claude_agentic_rag(user_query):
"""
Run the agentic RAG system using Claude with tools.
Args:
user_query (str): The user's query
Returns:
dict: The complete response, including search results and Claude's answer
"""
# Check for API key
if not CLAUDE_API_KEY:
raise ValueError("ANTHROPIC_API_KEY environment variable is not set")
system_prompt = get_system_prompt()
print(f"\n🔍 Processing query: '{user_query}'")
print("-" * 80)
# Initial request with the user's prompt
response = client.messages.create(
model=MODEL,
max_tokens=4000,
system=system_prompt,
thinking={"type": "enabled", "budget_tokens": 2000},
tools=TOOLS,
messages=[{"role": "user", "content": user_query}]
)
# Display thinking
thinking_blocks = [b for b in response.content if b.type == "thinking"]
for block in thinking_blocks:
print("\n🧠 THINKING:")
print(block.thinking[:300] + "..." if len(block.thinking) > 300 else block.thinking)
# Process tool use if needed
conversation = [{"role": "user", "content": user_query}]
search_results = []
# We might need multiple tool calls, so loop until we get a final answer
while response.stop_reason == "tool_use":
tool_block = next((b for b in response.content if b.type == "tool_use"), None)
if tool_block:
# Show which tool was selected
print(f"\n🔧 USING TOOL: {tool_block.name}")
print(f"Tool input: {json.dumps(tool_block.input, indent=2)}")
# Execute the appropriate tool
tool_result = execute_tool(tool_block.name, tool_block.input)
print(f"Tool found {tool_result.get('results_count', 0)} results" if 'results_count' in tool_result else f"Tool completed")
# Save search results for return value
search_results.append({
"tool": tool_block.name,
"input": tool_block.input,
"result": tool_result
})
# Save assistant's response (thinking + tool use)
assistant_blocks = thinking_blocks + [tool_block]
conversation.append({"role": "assistant", "content": assistant_blocks})
# Add tool result to conversation
conversation.append({
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": tool_block.id,
"content": json.dumps(tool_result)
}]
})
# Get next response
response = client.messages.create(
model=MODEL,
max_tokens=4000,
system=system_prompt,
thinking={"type": "enabled", "budget_tokens": 2000},
tools=TOOLS,
messages=conversation
)
# Update thinking blocks for next iteration
thinking_blocks = [b for b in response.content if b.type == "thinking"]
for block in thinking_blocks:
print("\n🧠 ADDITIONAL THINKING:")
print(block.thinking[:300] + "..." if len(block.thinking) > 300 else block.thinking)
# Get final text response
final_text = ""
for block in response.content:
if block.type == "text":
final_text += block.text
print("\n" + "=" * 80)
print("ANSWER:")
print(final_text)
print("=" * 80)
# Return the complete result with all context
return {
"query": user_query,
"answer": final_text,
"search_results": search_results,
"conversation_history": conversation
}

def interactive_mode():
"""Run the Claude agentic RAG system in interactive mode."""
print("\n" + "=" * 80)
print("CLAUDE 3.7 AGENTIC RAG SYSTEM")
print("=" * 80)
print("Type 'exit' or 'quit' to exit.")
while True:
print("\nYour question: ", end="")
user_query = input().strip()
# Check for exit command
if user_query.lower() in ['exit', 'quit']:
return
# Skip empty queries
if not user_query:
print("Please enter a question.")
continue
print("\n" + "-" * 80)
print("Processing your query...")
try:
# Process the query using Claude
claude_agentic_rag(user_query)
except Exception as e:
print(f"❌ Error: {str(e)}")


if __name__ == "__main__":
# Check for API keys
if not os.environ.get("ANTHROPIC_API_KEY"):
api_key = input("Please enter your Anthropic API key: ").strip()
if api_key:
os.environ["ANTHROPIC_API_KEY"] = api_key
CLAUDE_API_KEY = api_key
else:
print("❌ No Anthropic API key provided. Exiting.")
sys.exit(1)
# Check GitHub token
if not os.environ.get("GITHUB_TOKEN"):
print("\n⚠️ No GitHub token found. You may hit rate limits when fetching comments.")
print("To set a token: export GITHUB_TOKEN=your_token_here\n")
# Run the interactive mode
interactive_mode()
ツール使用を備えた Claude 3.7 を用いた実装により、手動でのチューニングを行わずとも、複数のナレッジベースから関連情報をどのように取得するかをシステムが動的に判断できます。さらに Weave を統合することで、Claude のリトリーバルツールの選択や推論過程を監視し、システム性能を反復的に改善できます。以下に本システムの中核コンポーネントを示します。

ベクトル検索とツールベースのリトリーバル

本システムの中核はベクトル検索の実装にあり、キーワードの単純一致ではなくセマンティック検索によって Claude が関連文書を取得できます。効率的なベクトル保管には ChromaDB を用い、OpenAI の text-embedding-3-small ベクトル表現を生成するモデル。関数 search_vector_store 次の方法でクエリに対応します:
  1. 関連するナレッジベースの特定(例: Weave の Issue、Weave のドキュメント、Weights & Biases の Issue、Weights & Biases のドキュメント)。
  2. 保存済みのベクトルデータベースに対して類似度検索を���行する。
  3. 上位結果を、GitHub の Issue 番号、関連リポジトリ、コメントなどのメタデータと併せて返す。
検索結果が GitHub Issue の場合は、を用いてコメントを自動取得します。 fetch_github_comments 関数により、解決策や関連するディスカッションを含む可能性のある追加コンテキストを取得できるようにします。

Claude 3.7 のツール選択

Claude は、ユーザーのクエリに基づいて適切なツールを動的に選択できるため、システムを簡素化します。システムには、次の4つのリトリーバルツールへのアクセス権があります。
  • search_weave_issuesWeave に関連する GitHub Issue を照会します。
  • search_weave_docs: Weave のドキュメントを検索します。
  • search_wandb_issues:Weights & Biases に関連する Issue を取得します。
  • search_wandb_docs:WandB のドキュメントから関連セクションを検索します。
その execute_tool function は、Claude の判断に基づいて適切なリトリーバル関数を実行します。これにより静的なリトリーバルロジックが不要になり、Claude がどのナレッジベースを検索すべきかを推論できるようになります。

監視と最適化のための Weave

ツール拡張型のリトリーバルシステムにおける課題の一つは、意思決定の過程を把握し、その判断が最適な結果につながっているかを検証することです。私たちは Weave を用いて、各クエリに対して Claude がどのツールを選択したか、その選択理由としてどのような推論を示したか、そして取得結果が最終応答にどう影響したかを監視します。
リトリーバルに関わる各関数は、すべて…でラップされています @weave.op、つまり、すべてのインタラクションを記録・可視化・分析できます。これはデバッグやシステム挙動の改善に不可欠です。たとえば、特定のクエリタイプで Claude が一貫して誤ったツールを選ぶ場合、システムプロンプトを調整したり、リトリーバルのヒューリスティクスを見直したりできます。以下は、スクリプト実行後に Weave 内で確認できる画面の一例です。


動的なクエリ処理と反復

従来の RAG が単一ステップでドキュメントを取得するのとは異なり、Claude は次のことが可能です。
  1. クエリを分析し、追加のコンテキストが必要かどうかを判定する。
  2. 不確実性がある場合は、複数のナレッジベースを検索する。
  3. 検索クエリを洗練したり、追加の質問を投げかけたりする。
Weave はこれらの多段インタラクションを記録し、クエリの再構成が必要なタイミングや、検索戦略の改善が必要な局面を可視化します。

実運用のユースケースと応用例

Agentic RAG は、特に法務調査や財務分析のような知識集約領域で、従来のリトリーバル手法に対して大きな優位性を発揮します。事前にインデックス化されたベクトルデータベースに依存する静的なリトリーバルとは異なり、エージェント指向のシステムはウェブサイト、構造化データベース、非ベクトル化ドキュメントなどのリアルタイムソースに能動的にクエリを発行します。これにより手動更新の手間が減り、応答が常に最新の利用可能な情報を反映するようになります。

法務調査: 判例リトリーバルの高精度化と適応性向上

Agentic RAG は、次の観点から法務調査を総合的に強化します。
  • 外部データベースへのクエリ実行まだインデックス化されていない可能性のある判例、法令、規制文書に対して。
  • 複雑な条件ベース検索の対応(特定の金額基準や和解金額を伴う訴訟など)
  • 反復的なクエリ再構成による結果の精緻化無関係な結果を減らし、ハルシネーションを最小化します。
法律実務者にとっては、複雑な法的課題の調査において、精度の向上、適応性の強化、そして意思決定の質の改善を意味します。

財務分析: リアルタイムの市場インサイト

エージェント指向RAGは、次の点で財務分析を強化します。
  • リアルタイムデータへのアクセス外部の金融ソース、決算報告、経済指標から。
  • クエリの動的調整微妙な数値パラメータ(例: 市場イベントや大口取引)を捉えるために。
  • 不正確さの低減自己修正と適応的なリトリーバル戦略によって。
これにより、急速に変化する環境でも、アナリストはリアルタイムでより的確な金融意思決定を行えます。

エージェント指向RAGにおける課題と対策戦略

1. 複雑なリトリーバルによるレイテンシの増加

エージェント指向RAGは複数のソースからリトリーバルを行うため、固定的なLLMによる単発の参照に比べて応答時間が遅くなる場合があります。
緩和戦略:
  • キャッシュ機構を実装する頻繁に参照されるデータ向けに
  • 並列処理を最適化する同時に複数のリトリーバルを実行するために。
  • 高速なソースを優先する検索範囲を拡大する前に。

2. ツール選択の信頼性

誤ったリトリーバルツールを選ぶと、無関係または不完全な応答につながる可能性がある。
緩和戦略:
  • ツール選択を監視するWeave を用いて系統的なエラーを特定し、リアルタイムに挙動を調整する。
  • 信頼度しきい値を導入する低関連の結果が検出された場合に検索を拡大する。
  • 代替クエリを提案するより精密なリトリーバルへとユーザーを誘導するために

3. 倫理的懸念: プライバシーとバイアスのリスク

実データベースと対話する場合、データ露出やバイアス増幅のリスクが生じます。
緩和戦略:
プライバシーエージェントを導入して次を行う:
  • 厳格なアクセス制御を適用する不正なデータ取得を防止するために。
  • 機微情報にフラグを立てる処理の前に。
  • コンプライアンスを確保するプライバシー規制(例: GDPR、HIPAA)に準拠して。
    • 複数の情報源を相互参照して、誤情報のリスクを低減する。
    • 透明性と説明責任のために監査ログを維持する。
プライバシー重視の設計を組み込むことで 安全対策エージェント指向 RAG は、自動化されたリトリーバルにおけるセキュリティと信頼性を確保できる。

結論

リトリーバル強化型生成は、エージェント指向 RAG のアーキテクチャによって、単純な静的リトリーバルからより動的で適応的なシステムへと進化した。従来の RAG が単一ステップの参照にとどまるのに対し、エージェント指向 RAG は多段推論、適応的なクエリ分解、モジュール型のリトリーバルエージェントを導入し、応答の精度とスケーラビリティを高める。
単一エージェント方式は、構造化されたリトリーバルにおいて効率とシンプルさを提供し、複数エージェントのアーキテクチャは複雑なクエリに対してより高い柔軟性をもたらす。Claude 3.7 のようなツールや Weave のような監視フレームワークを統合することで、リトリーバルシステムは検索を精緻化し、知識ソースを拡張し、精度をリアルタイムに向上できる。AI 主導のリトリーバルが進化するにつれ、エージェント指向 RAG は、情報アクセスを合理化することで法律調査や金融などの産業を強化していく。
Iterate on AI agents and models faster. Try Weights & Biases today.