Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env.template
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@ CSV_LOADER_DATA_DIR_PATH="./data"

# Qdrant Settings
QDRANT_URL="http://localhost:6333"

# Elasticsearch Settings
ELASTICSEARCH_URL="http://localhost:9200"
71 changes: 71 additions & 0 deletions data/docs_kabuto.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# KABUTOシステム仕様書

## 第1章 KABUTOシステム概要

本仕様書は、独自技術に基づき開発された高次元演算処理システム「KABUTO」のシステム構成、動作原理、および特殊機能について記述する。KABUTOは、従来のコンピューティングパラダイムとは一線を画す、生体情報および精神波形との同期を基盤としたシステムであり、その動作は多岐にわたる特殊なプロトコルによって制御される。システムの各モジュールは、固有の名称と機能を持ち、相互に複雑な作用を及ぼし合う。

---

## 第2章 ハードウェア・インターフェース

### 2.1 電源・起動システム

KABUTOの電源ランプは、通常の動作状態を以下のパターンで表示する。待機状態では赤色点滅、稼働状態では青色点滅が繰り返される。このパターンにない緑色の点滅は、システムが「**禅モード**」に移行していることを示す。禅モードでは、パフォーマンスが最小限に抑えられ、バックグラウンドでのシステム自己修復が実行される。このモードからの強制離脱は、KABUTO本体に設置された「**龍玉**」ボタンを5秒以上長押しすることで可能となる。また、電源OFF後も微細な電流が流れ続ける「**夢の海**」と呼ばれる待機モードが存在し、これにより次回の起動高速化が図られる。この際、本体から微かな波の音が聞こえるが、これは正常な動作である。

### 2.2 映像出力システム

KABUTOの映像出力モジュール「**玉手箱**」は、接続されたディスプレイの固有情報(「魂」)を認識し、適切なカラー表示を行う。この認識に失敗した場合、画面は白黒表示となる。この事象は、接続ケーブルの抜き差し、または特殊な両端同形状ケーブルの接続向きを逆にして再接続することで解消される場合がある。

### 2.3 冷却システム

システム冷却は、特殊な流体冷却機構「**風魔の術**」によって制御される。特定の演算処理を伴うアプリケーション「鬼灯」の実行中は、冷却システムが一時的に停止し、無音となる。これは内部エネルギーの流れを最適化するための仕様であり、異常ではない。ただし、この無音状態が15分以上継続する場合は、過熱の可能性があるため、アプリケーションの強制終了およびシステムの再起動が推奨される。

---

## 第3章 ソフトウェア・オペレーション

### 3.1 起動プロトコル

KABUTOの起動シーケンスは、「**シノビ・プロトコル**」に基づき実行される。このプロトコルの初期化フェーズで、内部クロックと接続された外部周辺機器のクロックとの同期に失敗した場合、画面全体が紫色に点滅し、システムがフリーズする。この問題は、KABUTO本体の再起動前に、すべての周辺機器を5秒以上電源から抜くことで、外部クロックのキャッシュがクリアされ、同期が正常に行われる可能性が高まる。

### 3.2 セキュリティ・認証システム

ログイン認証は、パスワード入力に加え、セキュリティモジュール「**百鬼夜行**」によるログイン試行者の「**精神波形**」解析を伴う。パスワードが正しく入力された場合でも、精神波形が登録されたユーザーと一致しないと判断された場合、「**虚無**」エラーが表示され、ログインは拒否される。これは疲労やストレスによる精神状態の変化が原因である可能性があり、深呼吸による精神状態の安定化が解決策となる。

### 3.3 ネットワーク・通信

ネットワークモジュール「**隠れ蓑**」は、外部からの不必要なスキャンを回避するため、デフォルトでステルスモードで動作する。これにより、ネットワーク上の他のデバイスから認識されない状態となる。KABUTOをネットワーク上で可視化させるには、ネットワーク設定画面から「**忍びの道**」オプションを有効にする必要がある。

### 3.4 演算コア

KABUTOの演算コア「**稲荷**」は、膨大な演算処理能力を持つ。しかし、システム負荷が極端に高い場合、演算結果の一部が視覚的なノイズとして画面の隅に不規則な光の点(「**狐火**」)として現れることがある。これはシステム負荷の過大を示す兆候であり、不要なバックグラウンドアプリケーションを終了させるか、システムを再起動することで解消される。

---

## 第4章 ユーザー・インターフェース機能

### 4.1 ファイルシステム

ファイルシステム「**竜宮城**」は、特定の文字を「**時空の歪み**」として認識し、文字コードの誤読が発生することがある。これにより、漢字で表示されている特定のフォルダ名が、ひらがなとして読み取られる現象が発生する。この問題を解消するには、システム設定にて「**読み方優先モード**」を一時的に無効化する必要がある。

### 4.2 入力・AIアシスタント

内蔵AIアシスタント「**ヤタガラス**」は、ユーザーの入力内容を予測し、システムの基幹言語である「**KABUTO語**」に最適化しようとする機能を持つ。これにより、特定の文字が自動的にカタカナに変換される現象が発生する。この機能を一時的に停止するには、入力中に`Ctrl`キーと`Shift`キーを同時に押しながら「KABUTO」と入力する。

### 4.3 スクリーンショット機能

スクリーンショット機能「**万華鏡**」は、ユーザーの「**視点**」を認識し、見ている領域のみを円形に切り取る「**集中モード**」を自動で適用する場合がある。画面全体を保存したい場合は、スクリーンショット実行時にマウスカーソルを画面の隅に移動させてから操作する必要がある。

### 4.4 映像処理フィルター

映像処理モジュール「**葛飾北斎**」は、動画形式を自動で判断し、独自の「**浮世絵**」フィルターを適用する場合がある。この機能が有効化されると、映像が「水墨画」のようにモノクロで、筆で描かれたような動きになる。この機能を無効にするには、動画再生中に右クリックメニューから「葛飾北斎」オプションをオフにする。

### 4.5 システム・パーソナライゼーション

高度なAI機能「**天照**」は、ユーザーの無意識下での情報を収集し、デスクトップの背景画像をユーザーの最後に見た「夢」の内容に変更する機能を持つ。この機能を停止するには、システム設定内の「**天照の予知夢**」オプションを無効化する。ただし、この設定の無効化は、KABUTOの全体的なパフォーマンスの一部低下を招く可能性がある。

---

## 第5章 バグ・特殊現象

ごく稀に、システムクロックが過去の時間に一時的に戻ることで発生する「**天狗のいたずら**」と呼ばれる現象が発生する。この現象では、デスクトップ上のアイコンがすべて逆さまに表示される。この状態はパフォーマンスには影響しないが、龍玉ボタンを音量を最大にした状態で3回連続で押すことで、システムクロックが強制的に再設定され、現象が解消される。
Binary file added data/docs_kabuto.pdf
Binary file not shown.
12 changes: 12 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,15 @@ services:
- "6333:6333" # Dashboard: http://localhost:6333/dashboard
volumes:
- ./assets/qdrant_data:/qdrant/storage
elasticsearch:
build:
dockerfile: elasticsearch.Dockerfile
container_name: elasticsearch
ports:
- "9200:9200"
- "9300:9300"
environment:
- discovery.type=single-node
- xpack.security.enabled=false
volumes:
- ./assets/es_data:/usr/share/elasticsearch/data
10 changes: 10 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
## Operations

```bash
# Start Docker containers
docker compose up -d

# Delete collection from Qdrant
uv run python -m template_langgraph.tasks.delete_qdrant_collection

Expand All @@ -12,8 +15,15 @@ uv run python -m template_langgraph.tasks.add_documents_to_qdrant
# Search Qdrant
uv run python -m template_langgraph.tasks.search_documents_on_qdrant

# Add documents to Elasticsearch
uv run python -m template_langgraph.tasks.add_documents_to_elasticsearch

# Search Elasticsearch
uv run python -m template_langgraph.tasks.search_documents_on_elasticsearch

# Run Kabuto Helpdesk Agent
uv run python -m template_langgraph.tasks.run_kabuto_helpdesk_agent "KABUTOの起動時に、画面全体が紫色に点滅し、システムがフリーズします。"
uv run python -m template_langgraph.tasks.run_kabuto_helpdesk_agent "KABUTOのマニュアルから禅モードに関する情報を教えて下さい"
```

## References
Expand Down
4 changes: 4 additions & 0 deletions elasticsearch.Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
FROM docker.elastic.co/elasticsearch/elasticsearch:9.1.0

RUN bin/elasticsearch-plugin install analysis-kuromoji
RUN bin/elasticsearch-plugin install analysis-icu
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ description = "A GitHub template repository for Python"
readme = "README.md"
requires-python = ">=3.10"
dependencies = [
"elasticsearch>=9.1.0",
"langchain-community>=0.3.27",
"langchain-openai>=0.3.28",
"langchain-text-splitters>=0.3.9",
"langgraph>=0.6.2",
"openai>=1.98.0",
"pydantic-settings>=2.9.1",
"pypdf>=5.9.0",
"python-dotenv>=1.1.0",
"qdrant-client>=1.15.1",
"typer>=0.16.0",
Expand Down
8 changes: 7 additions & 1 deletion template_langgraph/agents/kabuto_helpdesk_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from template_langgraph.llms.azure_openais import AzureOpenAiWrapper
from template_langgraph.loggers import get_logger
from template_langgraph.tools.elasticsearch_tool import search_elasticsearch
from template_langgraph.tools.qdrants import search_qdrant

logger = get_logger(__name__)
Expand All @@ -10,7 +11,12 @@
class KabutoHelpdeskAgent:
def __init__(self, tools=None):
if tools is None:
tools = [search_qdrant] # Default tool for searching Qdrant
# Default tool for searching Qdrant
tools = [
search_qdrant,
search_elasticsearch,
# Add other tools as needed
]
self.agent = create_react_agent(
model=AzureOpenAiWrapper().chat_model,
tools=tools,
Expand Down
35 changes: 35 additions & 0 deletions template_langgraph/tasks/add_documents_to_elasticsearch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import logging

from template_langgraph.loggers import get_logger
from template_langgraph.tools.elasticsearch_tool import ElasticsearchClientWrapper
from template_langgraph.tools.pdf_loaders import PdfLoaderWrapper

logger = get_logger(__name__)
logger.setLevel(logging.DEBUG)
COLLECTION_NAME = "docs_kabuto"

if __name__ == "__main__":
# Create Elasticsearch index
es = ElasticsearchClientWrapper()
logger.info(f"Creating Elasticsearch index: {COLLECTION_NAME}")
result = es.create_index(
index_name=COLLECTION_NAME,
)
if result:
logger.info(f"Created Elasticsearch index: {COLLECTION_NAME}")
else:
logger.warning(f"Index {COLLECTION_NAME} already exists.")

# Load documents from PDF files
documents = PdfLoaderWrapper().load_pdf_docs()
logger.info(f"Loaded {len(documents)} documents from PDF.")

# Add documents to Elasticsearch index
result = es.add_documents(
index_name=COLLECTION_NAME,
documents=documents,
)
if result:
logger.info(f"Added {len(documents)} documents to Elasticsearch index: {COLLECTION_NAME}")
else:
logger.error(f"Failed to add documents to Elasticsearch index: {COLLECTION_NAME}")
23 changes: 23 additions & 0 deletions template_langgraph/tasks/search_documents_on_elasticsearch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import logging

from template_langgraph.loggers import get_logger
from template_langgraph.tools.elasticsearch_tool import ElasticsearchClientWrapper

logger = get_logger(__name__)
logger.setLevel(logging.INFO)
COLLECTION_NAME = "docs_kabuto"

if __name__ == "__main__":
query = "禅モード"
es = ElasticsearchClientWrapper()

results = es.search(
index_name=COLLECTION_NAME,
query=query,
)
logger.info(f"Found {len(results)} results for the question: {query}")
for i, result in enumerate(results, start=1):
logger.info(f"Result {i}:")
logger.info(f"File Name: {result.metadata['source']}")
logger.info(f"Content: {result.page_content}")
logger.info("-" * 40)
172 changes: 172 additions & 0 deletions template_langgraph/tools/elasticsearch_tool.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import os
from functools import lru_cache

from elasticsearch import Elasticsearch, helpers
from langchain.tools import tool
from langchain_core.documents import Document
from pydantic import BaseModel, Field
from pydantic_settings import BaseSettings, SettingsConfigDict


class Settings(BaseSettings):
elasticsearch_url: str = "http://localhost:9200"

model_config = SettingsConfigDict(
env_file=".env",
env_ignore_empty=True,
extra="ignore",
)


@lru_cache
def get_elasticsearch_settings() -> Settings:
"""Get Elasticsearch settings."""
return Settings()


class ElasticsearchClientWrapper:
def __init__(
self,
settings: Settings = None,
):
if settings is None:
settings = get_elasticsearch_settings()
print(f"Elasticsearch URL: {settings.elasticsearch_url}")
self.client = Elasticsearch(
settings.elasticsearch_url,
)
self.mapping = {
# ドキュメントのマッピング設定を定義
"mappings": {
# ドキュメント内の各フィールドのプロパティを定義
"properties": {
# 'content' フィールドを定義
"content": {
# 'content' は全文検索用のフィールド
"type": "text", # テキスト検索用のフィールド
# 日本語用のカスタムアナライザー 'kuromoji_analyzer' を使用
"analyzer": "kuromoji_analyzer", # 日本語用のアナライザーを指定
}
},
},
# インデックスの設定(アナライザーなど)を定義
"settings": {
# インデックスの分析設定
"analysis": {
# 使用するアナライザーを定義
"analyzer": {
# 'kuromoji_analyzer' というカスタムアナライザーを定義
"kuromoji_analyzer": {
# カスタムアナライザーであることを指定
"type": "custom",
# ICU正規化(文字の正規化処理)を適用
"char_filter": ["icu_normalizer"],
# Kuromojiトークナイザー(形態素解析用)を使用
"tokenizer": "kuromoji_tokenizer",
# トークンに対するフィルタのリストを定義
"filter": [
# 動詞や形容詞の基本形に変換
"kuromoji_baseform",
# 品詞に基づいたフィルタリング
"kuromoji_part_of_speech",
# 日本語のストップワード(不要な単語)を除去
"ja_stop",
# 数字の正規化を行う
"kuromoji_number",
# 日本語の語幹(ルート形)を抽出
"kuromoji_stemmer",
],
}
}
}
},
}

def create_index(self, index_name: str) -> bool:
"""Create an index in Elasticsearch."""
if not self.client.indices.exists(index=index_name):
result = self.client.indices.create(index=index_name, body=self.mapping)
if result:
return True
return False

def add_documents(
self,
index_name: str,
documents: list[Document],
) -> bool:
"""Add documents to an Elasticsearch index."""
actions = [
{
"_index": index_name,
"_source": {
"filename": os.path.basename(doc.metadata.get("source", "unknown")),
"content": doc.page_content,
},
}
for doc in documents
]
success, _ = helpers.bulk(self.client, actions)
return success > 0

def search(
self,
index_name: str,
query: str,
max_results: int = 10,
) -> list[Document]:
"""Search documents in an Elasticsearch index."""
search_query = {
"query": {
"match": {
"content": query,
}
},
"size": max_results,
}
response = self.client.search(
index=index_name,
body=search_query,
)
return [
Document(
page_content=hit["_source"]["content"],
metadata={
"source": hit["_source"]["filename"],
},
)
for hit in response["hits"]["hits"]
]


class ElasticsearchInput(BaseModel):
keywords: str = Field(description="Keywords to search")


class ElasticsearchOutput(BaseModel):
file_name: str = Field(description="The file name")
content: str = Field(description="The content of the file")


@tool(args_schema=ElasticsearchInput)
def search_elasticsearch(
keywords: str,
) -> list[ElasticsearchOutput]:
"""
空想上のシステム「KABUTO」のマニュアルから、関連する情報を取得します。
"""
wrapper = ElasticsearchClientWrapper()
results = wrapper.search(
index_name="docs_kabuto",
query=keywords,
max_results=3,
)
outputs = []
for result in results:
outputs.append(
ElasticsearchOutput(
file_name=result.metadata["source"],
content=result.page_content,
),
)
return outputs
Loading