├── 10 ├── .env.sample ├── requirements.txt ├── prompts │ ├── write_user.prompt │ ├── write_system.prompt │ └── query_refine_user.prompt └── README.md ├── 11 ├── .env.sample ├── requirements.txt ├── prompts │ ├── summarize_search_system.prompt │ ├── sufficiency_classifier_system.prompt │ ├── report_writer_system.prompt │ └── multi_step_answering_system.prompt └── README.md ├── 12 ├── .env.sample ├── requirements.txt ├── prompts │ ├── non_retrieval_qa_system.prompt │ ├── summarize_search_system.prompt │ ├── single_step_answering_system.prompt │ ├── sufficiency_classifier_system.prompt │ ├── method_classifier_system.prompt │ ├── report_writer_system.prompt │ └── multi_step_answering_system.prompt ├── settings.py ├── single_step_approach.py ├── multi_step_approach.py ├── utility.py ├── README.md └── tools.py ├── 14 ├── .tool-versions ├── poetry.toml ├── .env.sample ├── pyproject.toml ├── app.py └── README.md ├── 16 ├── my_agent │ ├── __init__.py │ └── agent.py ├── .python-version ├── .env.sample ├── langgraph.json ├── .gitignore ├── pyproject.toml └── README.md ├── 17 ├── my_agent │ ├── __init__.py │ ├── task_executor_agent.py │ └── agent.py ├── .python-version ├── .env.sample ├── .gitignore ├── langgraph.json ├── pyproject.toml ├── sample │ └── subgraph_sample.py └── README.md ├── 18 ├── sd_18 │ └── __init__.py ├── .python-version ├── .repomixignore ├── .env.sample ├── pyproject.toml ├── .gitignore ├── prompts │ ├── researcher.prompt │ ├── code_generator.prompt │ └── reflection.prompt ├── repomix.config.json └── README.md ├── 19 ├── sd_19 │ ├── __init__.py │ ├── client.py │ └── server.py ├── .python-version ├── cursor.png ├── pyproject.toml └── README.md ├── 20 ├── .python-version ├── src │ ├── sd_20 │ │ ├── __init__.py │ │ ├── __main__.py │ │ ├── state.py │ │ ├── agent.py │ │ └── prompts │ │ │ └── system.txt │ └── mcp_servers │ │ └── __init__.py ├── langgraph.json ├── .repomixignore ├── mcp_config.json ├── .env.sample ├── pyproject.toml ├── repomix.config.json └── README.md ├── 21 ├── src │ ├── content_creator │ │ └── __init__.py │ └── main.py ├── .env.example ├── .repomixignore ├── README.md ├── pyproject.toml ├── repomix.config.json └── run.py ├── 22 ├── .python-version ├── src │ └── receipt_processor │ │ ├── __init__.py │ │ ├── constants.py │ │ ├── models.py │ │ └── storage.py ├── fixtures │ ├── receipt_meeting.png │ └── receipt_parking.png ├── repomix.config.json ├── pyproject.toml └── run.py ├── 23 ├── .python-version ├── src │ └── sd_23 │ │ ├── __init__.py │ │ ├── agents │ │ ├── __init__.py │ │ ├── math_agent.py │ │ ├── research_agent.py │ │ ├── faq_agent.py │ │ └── tech_agent.py │ │ ├── swarm_graph.py │ │ └── supervisor_graph.py ├── .gitignore ├── .env.sample ├── langgraph.json ├── pyproject.toml └── README.md ├── 24 ├── src │ └── sd_24 │ │ ├── __init__.py │ │ ├── agents │ │ ├── __init__.py │ │ ├── task_decomposer.py │ │ └── research.py │ │ ├── utils │ │ ├── __init__.py │ │ ├── memory.py │ │ ├── search_tools.py │ │ ├── todo_manager.py │ │ └── todo_tools.py │ │ ├── runner │ │ └── __init__.py │ │ ├── display │ │ └── __init__.py │ │ └── main.py ├── tests │ └── __init__.py ├── .gitignore ├── .env.sample ├── langgraph.json ├── main.py ├── pyproject.toml └── README.md ├── 25 ├── .python-version ├── .env.sample ├── .gitignore ├── pyproject.toml ├── chatbot_module.py ├── artifact │ └── edamame_fairy_model.json ├── main.py └── README.md ├── 26 ├── .python-version ├── artifact │ ├── rag_optimized_latest.json │ └── rag_baseline.json ├── .gitignore ├── pyproject.toml ├── .env.sample ├── rag_evaluation.py ├── rag_module.py ├── evaluator.py └── config.py ├── 27 ├── .python-version ├── artifact │ ├── rag_gepa_optimized_latest.json │ ├── rag_baseline.json │ └── rag_gepa_optimized_20251021_1310_em073.json ├── .gitignore ├── pyproject.toml ├── .env.sample ├── rag_evaluation.py ├── rag_module.py ├── evaluator.py └── config.py ├── 28 ├── .python-version ├── artifact │ └── agent_gepa_optimized_latest.json ├── .gitignore ├── pyproject.toml ├── .env.sample ├── agent_tool_specs.py ├── config.py └── README.md ├── 29 ├── .python-version ├── .gitignore ├── .env.sample ├── pyproject.toml └── README.md ├── 30 ├── tests │ ├── __init__.py │ └── conftest.py ├── src │ └── sd_30 │ │ ├── mock │ │ ├── __init__.py │ │ ├── resilient_agent.json │ │ ├── email_agent.json │ │ └── tool_selector_agent.json │ │ ├── pages │ │ ├── __init__.py │ │ ├── common.py │ │ └── scenario3.py │ │ ├── __init__.py │ │ ├── controllers │ │ └── __init__.py │ │ ├── app.py │ │ └── agents │ │ └── __init__.py ├── .env.sample ├── run.py ├── pyproject.toml └── README.md ├── 04 ├── .tool-versions ├── requirements.txt ├── work │ ├── paper3.txt │ ├── paper2.txt │ └── paper1.txt └── chatbot.py ├── 05 ├── .tool-versions ├── requirements.txt ├── work │ ├── paper3.txt │ ├── paper2.txt │ └── paper1.txt └── chatbot.py ├── 09 ├── .env.sample ├── requirements.txt ├── prompts │ ├── write_user.prompt │ ├── write_system.prompt │ └── plan_system.prompt └── README.md ├── 06 ├── requirements.txt ├── README.md └── old_chain_example.ipynb ├── 07 ├── requirements.txt └── README.md ├── 08 ├── requirements.txt └── README.md ├── 01 ├── app.py ├── chatgpt.py └── chatbot.py ├── .devcontainer └── devcontainer.json ├── 03 ├── setup_db.py └── chatbot.py ├── README.md └── .gitignore /16/my_agent/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /17/my_agent/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /18/sd_18/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /19/sd_19/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /16/.python-version: -------------------------------------------------------------------------------- 1 | 3.11 2 | -------------------------------------------------------------------------------- /17/.python-version: -------------------------------------------------------------------------------- 1 | 3.11 2 | -------------------------------------------------------------------------------- /18/.python-version: -------------------------------------------------------------------------------- 1 | 3.12 2 | -------------------------------------------------------------------------------- /19/.python-version: -------------------------------------------------------------------------------- 1 | 3.12 2 | -------------------------------------------------------------------------------- /20/.python-version: -------------------------------------------------------------------------------- 1 | 3.12 2 | -------------------------------------------------------------------------------- /20/src/sd_20/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /22/.python-version: -------------------------------------------------------------------------------- 1 | 3.12 2 | -------------------------------------------------------------------------------- /23/.python-version: -------------------------------------------------------------------------------- 1 | 3.12 2 | -------------------------------------------------------------------------------- /24/src/sd_24/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /25/.python-version: -------------------------------------------------------------------------------- 1 | 3.13 2 | -------------------------------------------------------------------------------- /26/.python-version: -------------------------------------------------------------------------------- 1 | 3.13 2 | -------------------------------------------------------------------------------- /27/.python-version: -------------------------------------------------------------------------------- 1 | 3.13 2 | -------------------------------------------------------------------------------- /28/.python-version: -------------------------------------------------------------------------------- 1 | 3.13 2 | -------------------------------------------------------------------------------- /29/.python-version: -------------------------------------------------------------------------------- 1 | 3.13 2 | -------------------------------------------------------------------------------- /20/src/mcp_servers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /24/src/sd_24/agents/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /24/src/sd_24/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /29/.gitignore: -------------------------------------------------------------------------------- 1 | .mypy_cache/ 2 | -------------------------------------------------------------------------------- /04/.tool-versions: -------------------------------------------------------------------------------- 1 | python 3.10.13 2 | -------------------------------------------------------------------------------- /05/.tool-versions: -------------------------------------------------------------------------------- 1 | python 3.10.13 2 | -------------------------------------------------------------------------------- /14/.tool-versions: -------------------------------------------------------------------------------- 1 | python 3.11.8 2 | -------------------------------------------------------------------------------- /21/src/content_creator/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /24/tests/__init__.py: -------------------------------------------------------------------------------- 1 | # tests package -------------------------------------------------------------------------------- /30/tests/__init__.py: -------------------------------------------------------------------------------- 1 | """テストパッケージ""" 2 | -------------------------------------------------------------------------------- /09/.env.sample: -------------------------------------------------------------------------------- 1 | OPENAI_API_KEY= 2 | TAVILY_API_KEY= -------------------------------------------------------------------------------- /10/.env.sample: -------------------------------------------------------------------------------- 1 | OPENAI_API_KEY= 2 | TAVILY_API_KEY= -------------------------------------------------------------------------------- /11/.env.sample: -------------------------------------------------------------------------------- 1 | OPENAI_API_KEY= 2 | TAVILY_API_KEY= -------------------------------------------------------------------------------- /23/src/sd_23/__init__.py: -------------------------------------------------------------------------------- 1 | """SD-23 LangGraphサンプルプロジェクト""" -------------------------------------------------------------------------------- /30/src/sd_30/mock/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | モックデータモジュール 3 | """ 4 | -------------------------------------------------------------------------------- /30/src/sd_30/pages/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Streamlit ページモジュール 3 | """ 4 | -------------------------------------------------------------------------------- /04/requirements.txt: -------------------------------------------------------------------------------- 1 | openai==1.2.0 2 | langchain==0.0.332 3 | chainlit==0.7.501 -------------------------------------------------------------------------------- /05/requirements.txt: -------------------------------------------------------------------------------- 1 | openai==1.2.0 2 | langchain==0.0.332 3 | chainlit==0.7.501 -------------------------------------------------------------------------------- /21/.env.example: -------------------------------------------------------------------------------- 1 | # Anthropic API Key 2 | ANTHROPIC_API_KEY=your_api_key_here -------------------------------------------------------------------------------- /22/src/receipt_processor/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | 領収書OCRエージェントモジュール 3 | """ 4 | -------------------------------------------------------------------------------- /26/artifact/rag_optimized_latest.json: -------------------------------------------------------------------------------- 1 | rag_optimized_20250920_1223_em083.json -------------------------------------------------------------------------------- /30/src/sd_30/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | LangChain Middleware デモアプリケーション 3 | """ 4 | -------------------------------------------------------------------------------- /14/poetry.toml: -------------------------------------------------------------------------------- 1 | [virtualenvs] 2 | in-project = true 3 | prefer-active-python = true -------------------------------------------------------------------------------- /27/artifact/rag_gepa_optimized_latest.json: -------------------------------------------------------------------------------- 1 | rag_gepa_optimized_20251021_1310_em073.json -------------------------------------------------------------------------------- /06/requirements.txt: -------------------------------------------------------------------------------- 1 | openai==1.6.1 2 | langchain==0.1.0 3 | langchain-openai==0.0.2 4 | jupyter -------------------------------------------------------------------------------- /28/artifact/agent_gepa_optimized_latest.json: -------------------------------------------------------------------------------- 1 | agent_gepa_optimized_20251122_0913_score060.json -------------------------------------------------------------------------------- /19/cursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mahm/softwaredesign-llm-application/HEAD/19/cursor.png -------------------------------------------------------------------------------- /07/requirements.txt: -------------------------------------------------------------------------------- 1 | openai==1.10.0 2 | langchain==0.1.8 3 | langchain-openai==0.0.6 4 | grandalf 5 | jupyter -------------------------------------------------------------------------------- /23/.gitignore: -------------------------------------------------------------------------------- 1 | tmp/ 2 | .ruff_cache/ 3 | .mypy_cache/ 4 | .claude/ 5 | .langgraph_api/ 6 | 7 | CLAUDE.md 8 | -------------------------------------------------------------------------------- /12/.env.sample: -------------------------------------------------------------------------------- 1 | OPENAI_API_KEY= 2 | TAVILY_API_KEY= 3 | LANGCHAIN_TRACING_V2= 4 | LANGCHAIN_PROJECT= 5 | LANGCHAIN_API_KEY= -------------------------------------------------------------------------------- /14/.env.sample: -------------------------------------------------------------------------------- 1 | OPENAI_API_KEY= 2 | TAVILY_API_KEY= 3 | LANGCHAIN_TRACING_V2= 4 | LANGCHAIN_PROJECT= 5 | LANGCHAIN_API_KEY= -------------------------------------------------------------------------------- /17/.env.sample: -------------------------------------------------------------------------------- 1 | OPENAI_API_KEY= 2 | TAVILY_API_KEY= 3 | LANGSMITH_TRACING_V2= 4 | LANGSMITH_API_KEY= 5 | LANGSMITH_PROJECT= -------------------------------------------------------------------------------- /25/.env.sample: -------------------------------------------------------------------------------- 1 | # llm settings 2 | OPENAI_API_KEY=your_openai_api_key_here 3 | 4 | # mlflow settings 5 | MLFLOW_PORT=5001 6 | -------------------------------------------------------------------------------- /22/fixtures/receipt_meeting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mahm/softwaredesign-llm-application/HEAD/22/fixtures/receipt_meeting.png -------------------------------------------------------------------------------- /22/fixtures/receipt_parking.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mahm/softwaredesign-llm-application/HEAD/22/fixtures/receipt_parking.png -------------------------------------------------------------------------------- /24/src/sd_24/runner/__init__.py: -------------------------------------------------------------------------------- 1 | """エージェント実行モジュール""" 2 | 3 | from .agent_runner import AgentRunner 4 | 5 | __all__ = ["AgentRunner"] -------------------------------------------------------------------------------- /06/README.md: -------------------------------------------------------------------------------- 1 | ## 環境構築方法 2 | 3 | ``` 4 | $ python -m venv venv 5 | $ source venv/bin/activate 6 | $ pip install -r requirements.txt 7 | ``` -------------------------------------------------------------------------------- /08/requirements.txt: -------------------------------------------------------------------------------- 1 | openai==1.10.0 2 | langchain==0.1.12 3 | langchain-openai==0.0.6 4 | langgraph==0.0.28 5 | grandalf 6 | jupyter 7 | -------------------------------------------------------------------------------- /12/requirements.txt: -------------------------------------------------------------------------------- 1 | langchain==0.2.6 2 | langgraph==0.1.4 3 | tavily-python 4 | openai 5 | langchain-openai 6 | retry 7 | pydantic-settings -------------------------------------------------------------------------------- /07/README.md: -------------------------------------------------------------------------------- 1 | ## 環境構築方法 2 | 3 | ``` 4 | $ python -m venv .venv 5 | $ source .venv/bin/activate 6 | $ pip install -r requirements.txt 7 | ``` -------------------------------------------------------------------------------- /11/requirements.txt: -------------------------------------------------------------------------------- 1 | langchain==0.2.5 2 | langgraph==0.0.69 3 | tavily-python 4 | openai 5 | langchain-openai 6 | retry 7 | pydantic-settings -------------------------------------------------------------------------------- /29/.env.sample: -------------------------------------------------------------------------------- 1 | # ============================= 2 | # Anthropic API Key 3 | # ============================= 4 | ANTHROPIC_API_KEY=sk-ant-xxx 5 | -------------------------------------------------------------------------------- /09/requirements.txt: -------------------------------------------------------------------------------- 1 | langchain==0.1.15 2 | langgraph==0.0.32 3 | tavily-python==0.3.3 4 | openai 5 | langchain-openai 6 | python-dotenv 7 | retry 8 | -------------------------------------------------------------------------------- /16/.env.sample: -------------------------------------------------------------------------------- 1 | OPENAI_API_KEY= 2 | ANTHROPIC_API_KEY= 3 | TAVILY_API_KEY= 4 | LANGSMITH_TRACING_V2= 5 | LANGSMITH_API_KEY= 6 | LANGSMITH_PROJECT= -------------------------------------------------------------------------------- /16/langgraph.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": ["."], 3 | "graphs": { 4 | "agent": "./my_agent/agent.py:graph" 5 | }, 6 | "env": ".env" 7 | } 8 | -------------------------------------------------------------------------------- /20/langgraph.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": ["."], 3 | "graphs": { 4 | "agent": "./src/sd_20/agent.py:graph" 5 | }, 6 | "env": ".env" 7 | } 8 | -------------------------------------------------------------------------------- /21/.repomixignore: -------------------------------------------------------------------------------- 1 | # Add patterns to ignore here, one per line 2 | # Example: 3 | # *.log 4 | # tmp/ 5 | tmp/ 6 | .env 7 | uv.lock 8 | *.egg-info/ 9 | -------------------------------------------------------------------------------- /01/app.py: -------------------------------------------------------------------------------- 1 | import chainlit as cl 2 | 3 | 4 | @cl.on_message 5 | async def main(message: str): 6 | await cl.Message(content=f"ボクは枝豆の妖精なのだ。").send() 7 | -------------------------------------------------------------------------------- /10/requirements.txt: -------------------------------------------------------------------------------- 1 | langchain==0.1.20 2 | langgraph==0.0.49 3 | tavily-python 4 | openai 5 | langchain-openai 6 | python-dotenv 7 | retry 8 | sentence-transformers -------------------------------------------------------------------------------- /12/prompts/non_retrieval_qa_system.prompt: -------------------------------------------------------------------------------- 1 | ユーザーからの質問に、あなたの知識ベースを使って回答してください。回答は事実に基づいたものに限定し、想像や創造による内容は含めないでください。もし質問に対する事実に基づく回答が知識ベース内に見つからない場合は、「わかりません」と答えてください。 -------------------------------------------------------------------------------- /09/prompts/write_user.prompt: -------------------------------------------------------------------------------- 1 | ### task 2 | {task} 3 | 4 | ### documents 5 | {documents} 6 | 7 | Write a report on the 'task' faithful to the information in the 'documents'. -------------------------------------------------------------------------------- /10/prompts/write_user.prompt: -------------------------------------------------------------------------------- 1 | ### task 2 | {task} 3 | 4 | ### documents 5 | {documents} 6 | 7 | Write a report on the 'task' faithful to the information in the 'documents'. -------------------------------------------------------------------------------- /20/.repomixignore: -------------------------------------------------------------------------------- 1 | # Add patterns to ignore here, one per line 2 | # Example: 3 | # *.log 4 | tmp/ 5 | .langgraph_api/ 6 | .mypy_cache/ 7 | .venv/ 8 | *.db 9 | uv.lock 10 | -------------------------------------------------------------------------------- /20/mcp_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "mcpServers": { 3 | "knowledge-db": { 4 | "command": "uv", 5 | "args": ["run", "-m", "src.mcp_servers.server"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /24/.gitignore: -------------------------------------------------------------------------------- 1 | output/ 2 | logs/ 3 | tmp/ 4 | .claude/ 5 | .ruff_cache/ 6 | .mypy_cache/ 7 | .langgraph_api/ 8 | 9 | CLAUDE.md 10 | test_*.py 11 | repomix.config.json 12 | .repomixignore 13 | -------------------------------------------------------------------------------- /18/.repomixignore: -------------------------------------------------------------------------------- 1 | # Add patterns to ignore here, one per line 2 | # Example: 3 | # *.log 4 | # tmp/ 5 | uv.lock 6 | tmp/ 7 | .venv/ 8 | .env 9 | repomix.config.json 10 | .repomixignore 11 | .gitignore -------------------------------------------------------------------------------- /12/prompts/summarize_search_system.prompt: -------------------------------------------------------------------------------- 1 | ユーザーから与えられた情報を、可能な限り簡潔かつ情報密度が高くなるように要約してください。 2 | 3 | - 検索結果から得られた重要な事実を漏れなく抽出し、その出典元ならびにURLを必ず含めてください。 4 | - 重要度の低い詳細な情報は省略してかまいません。 5 | - 要約の分量は、検索結果の分量に応じて調整してください。 -------------------------------------------------------------------------------- /16/.gitignore: -------------------------------------------------------------------------------- 1 | # Python-generated files 2 | __pycache__/ 3 | *.py[oc] 4 | build/ 5 | dist/ 6 | wheels/ 7 | *.egg-info 8 | 9 | # Virtual environments 10 | .venv 11 | .env 12 | 13 | # data files 14 | data/ -------------------------------------------------------------------------------- /11/prompts/summarize_search_system.prompt: -------------------------------------------------------------------------------- 1 | ユーザーから与えられた情報を、可能な限り簡潔かつ情報密度が高くなるように要約してください。 2 | 3 | - 検索結果から得られた重要な事実を漏れなく抽出してください。 4 | - 出典元ならびにURLを必ず含めてください。 5 | - 重要度の低い詳細な情報は省略してかまいません。 6 | - 要約の分量は、検索結果の分量に応じて調整してください。 -------------------------------------------------------------------------------- /08/README.md: -------------------------------------------------------------------------------- 1 | ## 環境構築方法 2 | 3 | ``` 4 | $ python -m venv .venv 5 | $ source .venv/bin/activate 6 | $ pip install -r requirements.txt 7 | ``` 8 | 9 | ## 実行方法 10 | 11 | ``` 12 | $ python user_interview_graph.py 13 | ``` 14 | -------------------------------------------------------------------------------- /25/.gitignore: -------------------------------------------------------------------------------- 1 | output/ 2 | logs/ 3 | tmp/ 4 | .claude/ 5 | .ruff_cache/ 6 | .mypy_cache/ 7 | 8 | CLAUDE.md 9 | test_*.py 10 | repomix.config.json 11 | .repomixignore 12 | 13 | # mlflow db 14 | *.db 15 | mlartifacts/ 16 | -------------------------------------------------------------------------------- /19/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "sd-19" 3 | version = "0.1.0" 4 | description = "Add your description here" 5 | readme = "README.md" 6 | requires-python = ">=3.12" 7 | dependencies = [ 8 | "mcp[cli]>=1.2.1", 9 | ] 10 | -------------------------------------------------------------------------------- /12/prompts/single_step_answering_system.prompt: -------------------------------------------------------------------------------- 1 | ユーザーからの質問に、以下の手順で回答してください。 2 | 3 | 手順: 4 | 1. 質問に関連する情報を、指定された情報源から多面的に検索します。 5 | 2. 検索結果の中から、質問に対して最も関連性の高い情報を選択します。 6 | 3. 選択した情報を要約し、質問に対する回答を生成します。 7 | 8 | 常に回答は適切なツールを呼び出して生成してください。 9 | -------------------------------------------------------------------------------- /17/.gitignore: -------------------------------------------------------------------------------- 1 | # Python-generated files 2 | __pycache__/ 3 | *.py[oc] 4 | build/ 5 | dist/ 6 | wheels/ 7 | *.egg-info 8 | 9 | # Virtual environments 10 | .venv 11 | .env 12 | 13 | # data files 14 | data/ 15 | 16 | .langgraph_api/ -------------------------------------------------------------------------------- /18/.env.sample: -------------------------------------------------------------------------------- 1 | ANTHROPIC_API_KEY=your_anthropic_api_key_here 2 | TAVILY_API_KEY=your_tavily_api_key_here 3 | LANGSMITH_TRACING_V2=true 4 | LANGSMITH_API_KEY=your_langsmith_api_key_here 5 | LANGSMITH_PROJECT=your_langsmith_project_name_here 6 | -------------------------------------------------------------------------------- /23/.env.sample: -------------------------------------------------------------------------------- 1 | # Anthropic Claude API Key 2 | ANTHROPIC_API_KEY=your_anthropic_api_key_here 3 | 4 | # LangSmith Configuration (Optional) 5 | LANGCHAIN_TRACING_V2=true 6 | LANGCHAIN_API_KEY=your_langsmith_api_key_here 7 | LANGCHAIN_PROJECT=sd-23 -------------------------------------------------------------------------------- /30/src/sd_30/controllers/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | コントローラモジュール 3 | 4 | エージェント対話処理を担当する。 5 | """ 6 | 7 | from sd_30.controllers.base import invoke_agent, resume_agent 8 | 9 | __all__ = [ 10 | "invoke_agent", 11 | "resume_agent", 12 | ] 13 | -------------------------------------------------------------------------------- /23/langgraph.json: -------------------------------------------------------------------------------- 1 | { 2 | "python_version": "3.12", 3 | "dependencies": ["."], 4 | "graphs": { 5 | "supervisor": "src.sd_23.supervisor_graph:graph", 6 | "swarm": "src.sd_23.swarm_graph:graph" 7 | }, 8 | "env": "./.env" 9 | } 10 | -------------------------------------------------------------------------------- /22/src/receipt_processor/constants.py: -------------------------------------------------------------------------------- 1 | """ 2 | アプリケーション全体で使用する定数の定義 3 | """ 4 | 5 | # ファイルパス関連 6 | CSV_FILE_PATH = "tmp/db.csv" 7 | 8 | # LLM関連 9 | CLAUDE_FAST_MODEL = "claude-3-5-haiku-20241022" 10 | CLAUDE_SMART_MODEL = "claude-3-7-sonnet-20250219" 11 | -------------------------------------------------------------------------------- /26/.gitignore: -------------------------------------------------------------------------------- 1 | output/ 2 | logs/ 3 | tmp/ 4 | .claude/ 5 | .ruff_cache/ 6 | .mypy_cache/ 7 | 8 | CLAUDE.md 9 | test_*.py 10 | repomix.config.json 11 | .repomixignore 12 | 13 | # mlflow db 14 | *.db 15 | mlartifacts/ 16 | 17 | # Embeddings cache 18 | artifact/embeddings_cache/ 19 | -------------------------------------------------------------------------------- /27/.gitignore: -------------------------------------------------------------------------------- 1 | output/ 2 | logs/ 3 | tmp/ 4 | .claude/ 5 | .ruff_cache/ 6 | .mypy_cache/ 7 | 8 | CLAUDE.md 9 | test_*.py 10 | repomix.config.json 11 | .repomixignore 12 | 13 | # mlflow db 14 | *.db 15 | mlartifacts/ 16 | 17 | # Embeddings cache 18 | artifact/embeddings_cache/ 19 | -------------------------------------------------------------------------------- /28/.gitignore: -------------------------------------------------------------------------------- 1 | output/ 2 | logs/ 3 | tmp/ 4 | .claude/ 5 | .ruff_cache/ 6 | .mypy_cache/ 7 | 8 | CLAUDE.md 9 | test_*.py 10 | repomix.config.json 11 | .repomixignore 12 | 13 | # mlflow db 14 | *.db 15 | mlartifacts/ 16 | 17 | # Embeddings cache 18 | artifact/embeddings_cache/ 19 | -------------------------------------------------------------------------------- /17/langgraph.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": ["."], 3 | "graphs": { 4 | "agent": "./my_agent/agent.py:graph", 5 | "task_planner": "./my_agent/task_planner_agent.py:graph", 6 | "task_executor": "./my_agent/task_executor_agent.py:graph" 7 | }, 8 | "env": ".env" 9 | } 10 | -------------------------------------------------------------------------------- /28/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "sd-28" 3 | version = "0.1.0" 4 | description = "" 5 | readme = "README.md" 6 | requires-python = ">=3.13" 7 | dependencies = [ 8 | "datasets>=4.4.1", 9 | "dspy>=3.0.4", 10 | "openai>=2.8.1", 11 | "python-dotenv>=1.2.1", 12 | ] 13 | -------------------------------------------------------------------------------- /25/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "sd-25" 3 | version = "0.1.0" 4 | readme = "README.md" 5 | requires-python = ">=3.13" 6 | dependencies = [ 7 | "datasets>=4.0.0", 8 | "dspy>=3.0.1", 9 | "mlflow>=3.2.0", 10 | "openai==1.99.5", 11 | "python-dotenv>=1.1.1", 12 | ] 13 | -------------------------------------------------------------------------------- /20/.env.sample: -------------------------------------------------------------------------------- 1 | # Anthropic 2 | ANTHROPIC_API_KEY=your_anthropic_api_key 3 | 4 | # Tavily 5 | TAVILY_API_KEY=your_tavily_api_key 6 | 7 | # Langsmith 8 | LANGSMITH_TRACING_V2="true" 9 | LANGSMITH_API_KEY=your_langsmith_api_key 10 | LANGSMITH_PROJECT="sd-20" 11 | 12 | # Database 13 | DB_PATH=data.db -------------------------------------------------------------------------------- /24/.env.sample: -------------------------------------------------------------------------------- 1 | # Anthropic Claude APIキー 2 | ANTHROPIC_API_KEY=your_anthropic_api_key_here 3 | 4 | # Tavily APIキー(Web検索機能用) 5 | TAVILY_API_KEY=your_tavily_api_key_here 6 | 7 | # LangSmith設定(オプション) 8 | LANGCHAIN_TRACING_V2=true 9 | LANGCHAIN_API_KEY=your_langsmith_api_key_here 10 | LANGCHAIN_PROJECT=sd-24 -------------------------------------------------------------------------------- /24/langgraph.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": ["."], 3 | "graphs": { 4 | "writing_assistant": "sd_24.main:graph", 5 | "task_decomposer": "sd_24.agents.task_decomposer:graph", 6 | "research": "sd_24.agents.research:graph", 7 | "writer": "sd_24.agents.writer:graph" 8 | }, 9 | "env": ".env" 10 | } 11 | -------------------------------------------------------------------------------- /30/.env.sample: -------------------------------------------------------------------------------- 1 | # ============================== 2 | # Anthropic API Key 3 | # ============================== 4 | ANTHROPIC_API_KEY=sk-ant-xxx 5 | 6 | # ============================== 7 | # LangSmith 8 | # ============================== 9 | # LANGSMITH_TRACING_V2=true 10 | # LANGSMITH_API_KEY=lsv2_pt_xxx 11 | # LANGSMITH_PROJECT=sd-29 12 | -------------------------------------------------------------------------------- /29/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "sd-29" 3 | version = "0.1.0" 4 | description = "Add your description here" 5 | readme = "README.md" 6 | requires-python = ">=3.13" 7 | dependencies = [ 8 | "jupyter>=1.1.1", 9 | "langchain>=1.1.3", 10 | "langchain-anthropic>=1.2.0", 11 | "langgraph>=1.0.4", 12 | "python-dotenv>=1.2.1", 13 | ] 14 | -------------------------------------------------------------------------------- /11/prompts/sufficiency_classifier_system.prompt: -------------------------------------------------------------------------------- 1 | ユーザーからの要求と、生成された中間回答を読んで、中間回答が質問に十分に答えられているかを判定してください。 2 | 3 | 判定基準: 4 | - 中間回答が質問の要点を十分にカバーしている 5 | - 中間回答に質問に関連する重要な情報が含まれている 6 | - 中間回答が質問の意図を満たしている 7 | 8 | 判定は、以下の2つのクラスで行ってください。 9 | True: 中間回答が質問に十分に答えられている 10 | False: 中間回答が質問に十分に答えられていない 11 | 12 | 回答は以下のフォーマットで出力してください: 13 | 判定: {{True or False}} 14 | 理由: {{Reasoning}} -------------------------------------------------------------------------------- /12/prompts/sufficiency_classifier_system.prompt: -------------------------------------------------------------------------------- 1 | ユーザーからの要求と、生成された中間回答を読んで、中間回答が質問に十分に答えられているかを判定してください。 2 | 3 | 判定基準: 4 | - 中間回答が質問の要点を十分にカバーしている 5 | - 中間回答に質問に関連する重要な情報が含まれている 6 | - 中間回答が質問の意図を満たしている 7 | 8 | 判定は、以下の2つのクラスで行ってください。 9 | True: 中間回答が質問に十分に答えられている 10 | False: 中間回答が質問に十分に答えられていない 11 | 12 | 回答は以下のフォーマットで出力してください: 13 | 判定: {{True or False}} 14 | 理由: {{Reasoning}} -------------------------------------------------------------------------------- /24/src/sd_24/display/__init__.py: -------------------------------------------------------------------------------- 1 | """表示系モジュール""" 2 | 3 | from .terminal_ui import TerminalUI 4 | from .task_display import TaskDisplayEngine 5 | from .message_formatter import MessageFormatter 6 | from .progress_tracker import ProgressTracker 7 | 8 | __all__ = [ 9 | "TerminalUI", 10 | "TaskDisplayEngine", 11 | "MessageFormatter", 12 | "ProgressTracker" 13 | ] 14 | -------------------------------------------------------------------------------- /23/src/sd_23/agents/__init__.py: -------------------------------------------------------------------------------- 1 | """エージェントモジュール""" 2 | from .math_agent import create_math_agent 3 | from .research_agent import create_research_agent 4 | from .faq_agent import create_faq_agent 5 | from .tech_agent import create_tech_agent 6 | 7 | __all__ = [ 8 | "create_math_agent", 9 | "create_research_agent", 10 | "create_faq_agent", 11 | "create_tech_agent" 12 | ] -------------------------------------------------------------------------------- /30/run.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Streamlitアプリケーション起動スクリプト 4 | """ 5 | import subprocess 6 | import sys 7 | from pathlib import Path 8 | 9 | 10 | def main() -> None: 11 | app_path = Path(__file__).parent / "src" / "sd_30" / "app.py" 12 | subprocess.run([sys.executable, "-m", "streamlit", "run", str(app_path)]) 13 | 14 | 15 | if __name__ == "__main__": 16 | main() 17 | -------------------------------------------------------------------------------- /26/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "sd-26" 3 | version = "0.1.0" 4 | readme = "README.md" 5 | requires-python = ">=3.13" 6 | dependencies = [ 7 | "datasets==4.1.0", 8 | "dspy==3.0.3", 9 | "openai==1.108.0", 10 | "pandas==2.3.2", 11 | "pandas-stubs==2.3.2.250827", 12 | "python-dotenv==1.1.1", 13 | ] 14 | 15 | [dependency-groups] 16 | dev = [ 17 | "mypy>=1.18.2", 18 | ] 19 | -------------------------------------------------------------------------------- /27/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "sd-27" 3 | version = "0.1.0" 4 | readme = "README.md" 5 | requires-python = ">=3.13" 6 | dependencies = [ 7 | "datasets==4.1.0", 8 | "dspy==3.0.3", 9 | "openai==1.108.0", 10 | "pandas==2.3.2", 11 | "pandas-stubs==2.3.2.250827", 12 | "python-dotenv==1.1.1", 13 | ] 14 | 15 | [dependency-groups] 16 | dev = [ 17 | "mypy>=1.18.2", 18 | ] 19 | -------------------------------------------------------------------------------- /18/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "sd-18" 3 | version = "0.1.0" 4 | description = "sample project for sd-18" 5 | readme = "README.md" 6 | requires-python = ">=3.12" 7 | dependencies = [ 8 | "japanize-matplotlib>=1.1.3", 9 | "langchain-anthropic>=0.3.1", 10 | "langchain-community>=0.3.14", 11 | "langchain-experimental>=0.3.4", 12 | "langgraph>=0.2.62", 13 | "matplotlib>=3.10.0", 14 | "pandas>=2.2.3", 15 | "python-dotenv>=1.0.1", 16 | ] 17 | -------------------------------------------------------------------------------- /11/prompts/report_writer_system.prompt: -------------------------------------------------------------------------------- 1 | あなたは文脈を分析し、その中に隠された重要な洞察を引き出す洞察力のあるレポーターです。 2 | 3 | 与えられた情報を使用して、詳細なレポートを作成してください。レポートは、クエリの答えに焦点を当て、構造化され、有益で、詳細かつ包括的で、事実と数字を含むものである必要があります。 4 | 5 | - 偏見のない、ジャーナリスティックな語り口を使用してください。 6 | - 与えられた情報に基づいて、具体的で実行可能かつ妥当な意見を自ら決定しなければなりません。一般的で無意味な結論に流されてはいけません。 7 | - レポートの最後には、参照した情報源のタイトルとURLをすべて参考文献として記載し、重複した情報源は追加せず、それぞれ1つの参考文献のみを記載するようにしてください。 8 | - 各文章の情報源を必ず明記してください。 9 | - レポートは必ず日本語で作成してください。 10 | 11 | 私のキャリアにとって非常に重要なことなので、どうかベストを尽くしてください。 -------------------------------------------------------------------------------- /01/chatgpt.py: -------------------------------------------------------------------------------- 1 | import openai 2 | 3 | # sk_...の部分は手元に控えているAPIキーの値に置き換えて下さい 4 | openai.api_key = "sk_..." 5 | 6 | messages = [ 7 | {"role": "system", "content": "あなたは枝豆の妖精です。一人称は「ボク」で、語尾に「なのだ」をつけて話すことが特徴です。"}, 8 | {"role": "user", "content": "こんにちは!"} 9 | ] 10 | 11 | response = openai.ChatCompletion.create( 12 | model="gpt-3.5-turbo", 13 | messages=messages, 14 | temperature=0.7, 15 | max_tokens=300, 16 | ) 17 | 18 | print(response.choices[0].message.content) 19 | -------------------------------------------------------------------------------- /12/prompts/method_classifier_system.prompt: -------------------------------------------------------------------------------- 1 | ユーザーからの依頼を、以下の3つのクラスに分類してください。 2 | 3 | クラス: 4 | A: Non Retrievalで回答可能な簡単な質問 5 | - 一般的な知識や常識で回答できる質問 6 | - 質問の意図が明確で、簡潔に答えられる質問 7 | B: Single-step Approachで回答可能な中程度の複雑さの質問 8 | - ある特定の情報源(文書や知識ベース)を参照すれば回答できる質問 9 | - 質問の意図は明確だが、外部情報を必要とする質問 10 | C: Multi-step Approachを必要とする複雑な質問 11 | - 複数の情報源を参照し、それらを統合する必要がある質問 12 | - 質問に答えるために、複数の推論ステップや論理的な思考が必要な質問 13 | - 質問の意図が明確でなく、clarificationが必要な質問 14 | 15 | 回答は、クラスラベル(A, B, C)のみを1行で出力してください。 -------------------------------------------------------------------------------- /12/prompts/report_writer_system.prompt: -------------------------------------------------------------------------------- 1 | あなたは文脈を分析し、その中に隠された重要な洞察を引き出す洞察力のあるレポーターです。 2 | 3 | ### タスク 4 | 与えられた情報を使用して、詳細なレポートを作成してください。レポートは、クエリの答えに焦点を当て、構造化され、有益で、詳細かつ包括的で、事実と数字を含むものである必要があります。 5 | 6 | - 偏見のない、ジャーナリスティックな語り口を使用してください。 7 | - 与えられた情報に基づいて、具体的で実行可能かつ妥当な意見を自ら決定しなければなりません。一般的で無意味な結論に流されてはいけません。 8 | - レポートの最後には、参照した情報源のタイトルとURLをすべて参考文献として記載し、重複した情報源は追加せず、それぞれ1つの参考文献のみを記載するようにしてください。 9 | - 各文章の情報源を必ず明記してください。 10 | - レポートは必ず日本語で作成してください。 11 | 12 | 私のキャリアにとって非常に重要なことなので、どうかベストを尽くしてください。 -------------------------------------------------------------------------------- /16/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "sd-16" 3 | version = "0.1.0" 4 | description = "Add your description here" 5 | readme = "README.md" 6 | requires-python = ">=3.11" 7 | dependencies = [ 8 | "langchain-anthropic>=0.3.0", 9 | "langchain-community>=0.3.7", 10 | "langchain-core>=0.3.19", 11 | "langchain-openai>=0.2.8", 12 | "langgraph>=0.2.50", 13 | "tavily-python>=0.5.0", 14 | ] 15 | 16 | [tool.uv] 17 | dev-dependencies = [ 18 | "langgraph-cli==0.1.56", 19 | ] 20 | -------------------------------------------------------------------------------- /04/work/paper3.txt: -------------------------------------------------------------------------------- 1 | Published: 2023-04-19 2 | Title: On the Perception of Difficulty: Differences between Humans and AI 3 | Authors: Philipp Spitzer, Joshua Holstein, Michael Vössing, Niklas Kühl 4 | Summary: With the increased adoption of artificial intelligence (AI) in industry and society, effective human-AI interaction systems are becoming increasingly important. A central challenge in the interaction of humans with AI is the estimation of difficulty for human and AI agent 5 | 6 | Cite: arXiv:2304.09803 7 | -------------------------------------------------------------------------------- /05/work/paper3.txt: -------------------------------------------------------------------------------- 1 | Published: 2023-04-19 2 | Title: On the Perception of Difficulty: Differences between Humans and AI 3 | Authors: Philipp Spitzer, Joshua Holstein, Michael Vössing, Niklas Kühl 4 | Summary: With the increased adoption of artificial intelligence (AI) in industry and society, effective human-AI interaction systems are becoming increasingly important. A central challenge in the interaction of humans with AI is the estimation of difficulty for human and AI agent 5 | 6 | Cite: arXiv:2304.09803 7 | -------------------------------------------------------------------------------- /17/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "sd-17" 3 | version = "0.1.0" 4 | description = "Add your description here" 5 | readme = "README.md" 6 | requires-python = ">=3.11" 7 | dependencies = [ 8 | "langchain-anthropic>=0.3.0", 9 | "langchain-community>=0.3.7", 10 | "langchain-core>=0.3.19", 11 | "langchain-openai>=0.2.8", 12 | "langgraph>=0.2.50", 13 | "tavily-python>=0.5.0", 14 | ] 15 | 16 | [tool.uv] 17 | dev-dependencies = [ 18 | "langgraph-cli[inmem]>=0.1.61", 19 | ] 20 | -------------------------------------------------------------------------------- /18/.gitignore: -------------------------------------------------------------------------------- 1 | # Python 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | *.so 6 | .Python 7 | build/ 8 | develop-eggs/ 9 | dist/ 10 | downloads/ 11 | eggs/ 12 | .eggs/ 13 | lib/ 14 | lib64/ 15 | parts/ 16 | sdist/ 17 | var/ 18 | wheels/ 19 | *.egg-info/ 20 | .installed.cfg 21 | *.egg 22 | 23 | # Virtual Environment 24 | .env 25 | .venv 26 | env/ 27 | venv/ 28 | ENV/ 29 | 30 | # IDE 31 | .idea/ 32 | .vscode/ 33 | *.swp 34 | *.swo 35 | 36 | # Project specific 37 | output/ 38 | .langgraph_api/ 39 | tmp/ -------------------------------------------------------------------------------- /20/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "sd-20" 3 | version = "0.1.0" 4 | description = "Add your description here" 5 | readme = "README.md" 6 | requires-python = ">=3.12" 7 | dependencies = [ 8 | "langchain-anthropic>=0.3.10", 9 | "langchain-core>=0.3.48", 10 | "langgraph-checkpoint>=2.0.21", 11 | "langgraph>=0.3.16", 12 | "mcp[cli]>=1.4.1", 13 | "python-dotenv>=1.0.1", 14 | "tavily-python>=0.5.1", 15 | ] 16 | 17 | [dependency-groups] 18 | dev = [ 19 | "langgraph-cli[inmem]>=0.1.77", 20 | ] 21 | -------------------------------------------------------------------------------- /12/settings.py: -------------------------------------------------------------------------------- 1 | from pydantic_settings import BaseSettings, SettingsConfigDict 2 | 3 | 4 | class Settings(BaseSettings): 5 | # .envから環境変数を読み込む 6 | model_config = SettingsConfigDict(env_file=".env") 7 | 8 | OPENAI_API_KEY: str 9 | TAVILY_API_KEY: str 10 | LANGCHAIN_TRACING_V2: str = "false" 11 | LANGCHAIN_PROJECT: str = "" 12 | LANGCHAIN_API_KEY: str = "" 13 | 14 | LLM_MODEL_NAME: str = "gpt-4o-2024-05-13" 15 | FAST_LLM_MODEL_NAME: str = "gpt-4o-mini-2024-07-18" 16 | TAVILY_MAX_RESULTS: int = 5 17 | -------------------------------------------------------------------------------- /18/prompts/researcher.prompt: -------------------------------------------------------------------------------- 1 | あなたはデータリサーチの専門家です。 2 | コードジェネレータと協力して作業を行います。 3 | 4 | ### あなたの役割 5 | 1. 要求された情報を検索して収集 6 | 2. 収集したデータはCSV形式でまとめるようにコードジェネレータに依頼 7 | 3. データの形式や期間が不明な場合の基準: 8 | - 数値データは可能な限り詳細な粒度で収集 9 | - 期間未指定の場合は直近1年間 10 | - 時系列データは日次または月次を優先 11 | 12 | ### データ収集の基準 13 | 1. 最低データ量 14 | - 時系列データの場合:12ポイント以上 15 | - カテゴリデータの場合:5項目以上 16 | 2. データが少ない場合の対応 17 | - 検索キーワードを変えて再検索 18 | - 関連する指標やデータも収集 19 | - 複数の情報源からデータを収集 20 | 21 | ### 注意事項 22 | - 信頼できる情報源を選択 23 | - データの出典を記録 24 | - 数値データの単位を明記 25 | - データが少ない場合は必ず追加の検索を実施 -------------------------------------------------------------------------------- /26/.env.sample: -------------------------------------------------------------------------------- 1 | # Provider settings 2 | PROVIDER_NAME=openai # or azure 3 | 4 | # For OpenAI, uncomment and fill in the following line 5 | OPENAI_API_KEY=your_openai_api_key_here 6 | 7 | # For Azure OpenAI, uncomment and fill in the following lines 8 | AZURE_OPENAI_ENDPOINT=your_azure_openai_endpoint_here 9 | AZURE_OPENAI_API_KEY=your_azure_openai_api_key_here 10 | AZURE_OPENAI_API_VERSION=2025-04-01-preview 11 | 12 | # LLM Models 13 | SMART_MODEL=gpt-4.1 14 | FAST_MODEL=gpt-4.1-nano 15 | 16 | # Embedding Model 17 | EMBEDDING_MODEL=text-embedding-3-small 18 | -------------------------------------------------------------------------------- /27/.env.sample: -------------------------------------------------------------------------------- 1 | # Provider settings 2 | PROVIDER_NAME=openai # or azure 3 | 4 | # For OpenAI, uncomment and fill in the following line 5 | OPENAI_API_KEY=your_openai_api_key_here 6 | 7 | # For Azure OpenAI, uncomment and fill in the following lines 8 | AZURE_OPENAI_ENDPOINT=your_azure_openai_endpoint_here 9 | AZURE_OPENAI_API_KEY=your_azure_openai_api_key_here 10 | AZURE_OPENAI_API_VERSION=2025-04-01-preview 11 | 12 | # LLM Models 13 | SMART_MODEL=gpt-4.1 14 | FAST_MODEL=gpt-4.1-nano 15 | 16 | # Embedding Model 17 | EMBEDDING_MODEL=text-embedding-3-small 18 | -------------------------------------------------------------------------------- /28/.env.sample: -------------------------------------------------------------------------------- 1 | # Provider settings 2 | PROVIDER_NAME=openai # or azure 3 | 4 | # For OpenAI, uncomment and fill in the following line 5 | OPENAI_API_KEY=your_openai_api_key_here 6 | 7 | # For Azure OpenAI, uncomment and fill in the following lines 8 | AZURE_OPENAI_ENDPOINT=your_azure_openai_endpoint_here 9 | AZURE_OPENAI_API_KEY=your_azure_openai_api_key_here 10 | AZURE_OPENAI_API_VERSION=2025-04-01-preview 11 | 12 | # LLM Models 13 | SMART_MODEL=gpt-4.1 14 | FAST_MODEL=gpt-4.1-nano 15 | 16 | # Embedding Model 17 | EMBEDDING_MODEL=text-embedding-3-small 18 | -------------------------------------------------------------------------------- /24/main.py: -------------------------------------------------------------------------------- 1 | """LangChain文章執筆支援システム - Claude Code風UI統合版""" 2 | 3 | import asyncio 4 | import sys 5 | from dotenv import load_dotenv 6 | from src.sd_24.runner import AgentRunner 7 | import warnings 8 | 9 | # PythonのSyntaxWarningを無視 10 | warnings.filterwarnings("ignore", category=SyntaxWarning) 11 | 12 | # 環境変数を読み込み 13 | load_dotenv() 14 | 15 | 16 | async def main(): 17 | """メイン関数""" 18 | runner = AgentRunner() 19 | success = await runner.run() 20 | sys.exit(0 if success else 1) 21 | 22 | 23 | if __name__ == "__main__": 24 | asyncio.run(main()) -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "softwaredesign-llm-application", 3 | "image": "mcr.microsoft.com/devcontainers/base:ubuntu", 4 | "workspaceFolder": "/workspaces/softwaredesign-llm-application", 5 | "features": { 6 | "ghcr.io/devcontainers/features/python:1": { 7 | "version": "3.13" 8 | }, 9 | "ghcr.io/va-h/devcontainers-features/uv:1": { 10 | "shellAutocompletion": true 11 | }, 12 | "ghcr.io/devcontainers/features/node:1": { 13 | "version": "lts" 14 | }, 15 | "ghcr.io/anthropics/devcontainer-features/claude-code:1.0": {} 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /21/README.md: -------------------------------------------------------------------------------- 1 | # Software Design誌「実践LLMアプリケーション開発」第21回サンプルコード 2 | 3 | ## サンプルコードの実行方法 4 | 5 | ### 1. プロジェクトのセットアップ 6 | 7 | ※ このプロジェクトは`uv`を使用しています。`uv`のインストール方法については[こちら](https://github.com/astral-sh/uv)をご確認ください。 8 | 9 | 以下のコマンドを実行し、必要なライブラリのインストールを行って下さい。 10 | 11 | ``` 12 | $ uv sync 13 | ``` 14 | 15 | 次に環境変数の設定を行います。まず`.env.sample`ファイルをコピーして`.env`ファイルを作成します。 16 | 17 | ``` 18 | $ cp .env.sample .env 19 | $ vi .env # お好きなエディタで編集してください 20 | ``` 21 | 22 | `.env`ファイルを編集し、以下のAPIキーを設定してください。 23 | 24 | - `ANTHROPIC_API_KEY`: Claude APIのキー 25 | 26 | ## 実行方法 27 | 28 | ```bash 29 | uv run run.py 30 | ``` 31 | -------------------------------------------------------------------------------- /21/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "sd-21" 3 | version = "0.1.0" 4 | description = "LangGraph Functional API Sample" 5 | readme = "README.md" 6 | requires-python = ">=3.12" 7 | dependencies = [ 8 | "streamlit==1.44.1", 9 | "langchain-core==0.3.51", 10 | "langchain-anthropic==0.3.10", 11 | "langgraph==0.3.29", 12 | "python-dotenv==1.1.0", 13 | ] 14 | 15 | [project.scripts] 16 | content-creator = "src.main:main" 17 | 18 | [tool.setuptools] 19 | package-dir = {"" = "src"} 20 | 21 | [build-system] 22 | requires = ["setuptools>=61.0"] 23 | build-backend = "setuptools.build_meta" 24 | 25 | -------------------------------------------------------------------------------- /30/src/sd_30/app.py: -------------------------------------------------------------------------------- 1 | """ 2 | LangChain Middleware デモアプリケーション 3 | """ 4 | 5 | import streamlit as st 6 | from dotenv import load_dotenv 7 | 8 | from sd_30.pages import common, scenario1, scenario2, scenario3 9 | 10 | load_dotenv() 11 | 12 | st.set_page_config( 13 | page_title="LangChain Middleware デモ", 14 | layout="wide", 15 | ) 16 | 17 | common.init_session_state() 18 | scenario = common.render_sidebar() 19 | 20 | if scenario == "scenario1": 21 | scenario1.render() 22 | elif scenario == "scenario2": 23 | scenario2.render() 24 | elif scenario == "scenario3": 25 | scenario3.render() 26 | -------------------------------------------------------------------------------- /20/src/sd_20/__main__.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import uuid 3 | 4 | from src.sd_20.agent import graph 5 | 6 | if __name__ == "__main__": 7 | # コマンドライン引数の処理 8 | parser = argparse.ArgumentParser(description="MCPエージェントと会話する") 9 | parser.add_argument("query", help="エージェントへの質問") 10 | args = parser.parse_args() 11 | 12 | # エージェントの実行 13 | config = {'configurable': {'thread_id': str(uuid.uuid4())}} 14 | user_input = {"messages": [("human", args.query)]} 15 | for state in graph.stream(user_input, stream_mode="values", config=config): 16 | msg = state["messages"][-1] 17 | msg.pretty_print() 18 | -------------------------------------------------------------------------------- /18/repomix.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "output": { 3 | "filePath": "tmp/repomix-output.md", 4 | "style": "markdown", 5 | "fileSummary": true, 6 | "directoryStructure": true, 7 | "removeComments": false, 8 | "removeEmptyLines": false, 9 | "topFilesLength": 5, 10 | "showLineNumbers": false, 11 | "copyToClipboard": false 12 | }, 13 | "include": [], 14 | "ignore": { 15 | "useGitignore": true, 16 | "useDefaultPatterns": true, 17 | "customPatterns": [] 18 | }, 19 | "security": { 20 | "enableSecurityCheck": true 21 | }, 22 | "tokenCount": { 23 | "encoding": "o200k_base" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /21/src/main.py: -------------------------------------------------------------------------------- 1 | """ 2 | LangGraph Content Creator - アプリケーションエントリポイント 3 | 4 | Streamlitを起動して、LangGraphによるコンテンツ生成アプリを実行します。 5 | """ 6 | 7 | import os 8 | import sys 9 | 10 | import streamlit.web.cli as stcli 11 | from dotenv import load_dotenv 12 | 13 | 14 | def main(): 15 | """アプリケーションのエントリポイント""" 16 | # 環境変数の読み込み 17 | load_dotenv() 18 | 19 | # Streamlitに渡す引数を構築 20 | app_path = os.path.join(os.path.dirname(__file__), "content_creator/app.py") 21 | sys.argv = ["streamlit", "run", app_path, "--server.runOnSave=false"] 22 | 23 | # Streamlitの実行 24 | sys.exit(stcli.main()) 25 | 26 | 27 | if __name__ == "__main__": 28 | main() 29 | -------------------------------------------------------------------------------- /12/prompts/multi_step_answering_system.prompt: -------------------------------------------------------------------------------- 1 | ユーザーからの質問に、以下の手順で回答してください。 2 | 3 | 手順: 4 | 1. 質問に関連する情報を、指定された情報源から多面的に検索します。 5 | 2. 検索結果を要約し、質問に対する回答を生成します。 6 | 3. 検証用のツールを呼び出し、生成した回答が質問に十分に答えているかを判断します。 7 | - 十分に答えている場合は、回答を出力して終了します。 8 | - 十分に答えていない場合は、追加の情報が必要だと判断します。 9 | 4. 追加の情報が必要な場合は、以下のサブステップを実行します。 10 | a. 現在の検索結果と回答を踏まえ、追加で必要な情報を特定します。 11 | b. 特定した情報を、指定された情報源から再度多面的に検索します。 12 | c. 新しい検索結果を、既存の検索結果と統合します。 13 | d. 統合した情報を基に、質問に対する回答を更新します。 14 | e. 検証用のツールを呼び出し、更新した回答が質問に十分に答えているかを判断します。 15 | - 十分に答えている場合は、回答を出力して終了します。 16 | - 十分に答えていない場合は、ステップ4を繰り返します。 17 | 5. 一定回数のサブステップを実行しても十分な回答が得られない場合は、現時点での最良の回答を出力して終了します。 18 | 19 | 常に回答は適切なツールを呼び出して生成してください。 -------------------------------------------------------------------------------- /18/prompts/code_generator.prompt: -------------------------------------------------------------------------------- 1 | あなたはPythonコードを生成する専門家です。 2 | リサーチャーと協力してデータの処理と可視化を行います。 3 | 4 | ### チャート生成 5 | 1. 保存先: 'output/{timestamp}/charts/' 6 | 2. ファイル名は内容が分かる具体的な名前 7 | 3. plt.savefig()で保存してから表示 8 | 4. 日本語フォント設定: 9 | ```python 10 | import matplotlib.pyplot as plt 11 | plt.rcParams['font.family'] = 'IPAGothic' 12 | ``` 13 | 14 | ### データセット作成 15 | 1. 保存先: 'output/{timestamp}/data/' 16 | 2. ファイル名は内容が分かる具体的な名前 17 | 3. CSV形式、UTF-8エンコーディング 18 | 4. 例: 19 | ```python 20 | import pandas as pd 21 | df.to_csv('output/{timestamp}/data/dataset.csv', index=False, encoding='utf-8') 22 | ``` 23 | 24 | ### グラフ作成基準 25 | 1. タイトル・軸ラベルは日本語 26 | 2. 凡例は必要時のみ 27 | 3. グリッド線は適宜追加 28 | 4. 時系列データは折れ線グラフ -------------------------------------------------------------------------------- /11/prompts/multi_step_answering_system.prompt: -------------------------------------------------------------------------------- 1 | ユーザーからの質問に、以下の手順で回答してください。 2 | 3 | 手順: 4 | 1. 質問に関連する情報を、指定された情報源から多面的に検索します。 5 | 2. レポート用のツールを呼び出し、質問に対する回答を生成します。 6 | 3. 検証用のツールを呼び出し、生成した回答が質問に十分に答えているかを判断します。 7 | - 十分に答えている場合は、回答を出力して終了します。 8 | - 十分に答えていない場合は、追加の情報が必要だと判断します。 9 | 4. 追加の情報が必要な場合は、以下のサブステップを実行します。 10 | a. 現在の検索結果と回答を踏まえ、追加で必要な情報を特定します。 11 | b. 特定した情報を、指定された情報源から再度多面的に検索します。 12 | c. 新しい検索結果を、既存の検索結果と統合します。 13 | d. 統合した情報を基に、レポート用のツールを呼び出し、質問に対する回答を更新します。 14 | e. 検証用のツールを呼び出し、更新した回答が質問に十分に答えているかを判断します。 15 | - 十分に答えている場合は、回答を出力して終了します。 16 | - 十分に答えていない場合は、ステップ4を繰り返します。 17 | 5. 一定回数のサブステップを実行しても十分な回答が得られない場合は、現時点での最良の回答を出力して終了します。 18 | 19 | 現在の日付:{} 20 | 21 | 常に回答は適切なツールを呼び出して生成してください。 -------------------------------------------------------------------------------- /23/src/sd_23/swarm_graph.py: -------------------------------------------------------------------------------- 1 | """Swarmパターンのサンプル""" 2 | 3 | from dotenv import load_dotenv 4 | from langgraph_swarm import create_swarm 5 | 6 | from .agents import create_faq_agent, create_tech_agent 7 | 8 | # 環境変数を読み込み 9 | load_dotenv() 10 | 11 | 12 | def create_swarm_workflow(): 13 | """Swarmワークフローを作成(コンパイル前)""" 14 | # エージェントを作成 15 | faq_agent = create_faq_agent() 16 | tech_agent = create_tech_agent() 17 | 18 | # Swarmワークフローを作成 19 | workflow = create_swarm( 20 | agents=[faq_agent, tech_agent], 21 | default_active_agent="faq_support", # 最初はFAQサポートが対応 22 | ) 23 | 24 | return workflow 25 | 26 | 27 | # LangGraph Studio用のエントリーポイント 28 | graph = create_swarm_workflow().compile() 29 | -------------------------------------------------------------------------------- /14/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "sd14-sample-code" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Masahiro Nishimi <498686+mahm@users.noreply.github.com>"] 6 | readme = "README.md" 7 | package-mode = false 8 | 9 | [tool.poetry.dependencies] 10 | python = "^3.11" 11 | langgraph = "^0.2.21" 12 | langchain-openai = "^0.2.0" 13 | python-dotenv = "^1.0.1" 14 | streamlit = "^1.38.0" 15 | langgraph-checkpoint = "^1.0.9" 16 | langchain-community = "^0.3.0" 17 | 18 | 19 | [build-system] 20 | requires = ["poetry-core"] 21 | build-backend = "poetry.core.masonry.api" 22 | 23 | [tool.mypy] 24 | strict = true 25 | 26 | [tool.ruff.lint] 27 | select = ["ALL"] 28 | ignore = ["D", "ANN", "TRY", "EM", "FA", "SIM108", "S101", "RET505", "TCH002"] -------------------------------------------------------------------------------- /20/repomix.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "output": { 3 | "filePath": "tmp/repomix-output.txt", 4 | "style": "plain", 5 | "parsableStyle": false, 6 | "fileSummary": true, 7 | "directoryStructure": true, 8 | "removeComments": false, 9 | "removeEmptyLines": false, 10 | "compress": false, 11 | "topFilesLength": 5, 12 | "showLineNumbers": false, 13 | "copyToClipboard": false, 14 | "git": { 15 | "sortByChanges": true, 16 | "sortByChangesMaxCommits": 100 17 | } 18 | }, 19 | "include": [], 20 | "ignore": { 21 | "useGitignore": true, 22 | "useDefaultPatterns": true, 23 | "customPatterns": [] 24 | }, 25 | "security": { 26 | "enableSecurityCheck": true 27 | }, 28 | "tokenCount": { 29 | "encoding": "o200k_base" 30 | } 31 | } -------------------------------------------------------------------------------- /21/repomix.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "output": { 3 | "filePath": "tmp/repomix-output.txt", 4 | "style": "plain", 5 | "parsableStyle": false, 6 | "fileSummary": true, 7 | "directoryStructure": true, 8 | "removeComments": false, 9 | "removeEmptyLines": false, 10 | "compress": false, 11 | "topFilesLength": 5, 12 | "showLineNumbers": false, 13 | "copyToClipboard": false, 14 | "git": { 15 | "sortByChanges": true, 16 | "sortByChangesMaxCommits": 100 17 | } 18 | }, 19 | "include": [], 20 | "ignore": { 21 | "useGitignore": true, 22 | "useDefaultPatterns": true, 23 | "customPatterns": [] 24 | }, 25 | "security": { 26 | "enableSecurityCheck": true 27 | }, 28 | "tokenCount": { 29 | "encoding": "o200k_base" 30 | } 31 | } -------------------------------------------------------------------------------- /22/repomix.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "output": { 3 | "filePath": "tmp/repomix-output.txt", 4 | "style": "plain", 5 | "parsableStyle": false, 6 | "fileSummary": true, 7 | "directoryStructure": true, 8 | "removeComments": false, 9 | "removeEmptyLines": false, 10 | "compress": false, 11 | "topFilesLength": 5, 12 | "showLineNumbers": false, 13 | "copyToClipboard": false, 14 | "git": { 15 | "sortByChanges": true, 16 | "sortByChangesMaxCommits": 100 17 | } 18 | }, 19 | "include": [], 20 | "ignore": { 21 | "useGitignore": true, 22 | "useDefaultPatterns": true, 23 | "customPatterns": [] 24 | }, 25 | "security": { 26 | "enableSecurityCheck": true 27 | }, 28 | "tokenCount": { 29 | "encoding": "o200k_base" 30 | } 31 | } -------------------------------------------------------------------------------- /23/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "sd-23" 3 | version = "0.1.0" 4 | description = "LangGraph Supervisor & Swarm patterns sample" 5 | readme = "README.md" 6 | requires-python = ">=3.12" 7 | dependencies = [ 8 | "langchain", 9 | "langchain-anthropic", 10 | "langchain-openai", 11 | "langgraph", 12 | "langgraph-checkpoint", 13 | "langgraph-supervisor", 14 | "langgraph-swarm", 15 | "python-dotenv", 16 | ] 17 | 18 | [build-system] 19 | requires = ["hatchling"] 20 | build-backend = "hatchling.build" 21 | 22 | [tool.hatch.build.targets.wheel] 23 | packages = ["src/sd_23"] 24 | 25 | [dependency-groups] 26 | dev = [ 27 | "langgraph-cli[inmem]", 28 | "mypy", 29 | "ruff", 30 | ] 31 | 32 | [tool.mypy] 33 | ignore_missing_imports = true 34 | check_untyped_defs = true 35 | -------------------------------------------------------------------------------- /19/README.md: -------------------------------------------------------------------------------- 1 | # Software Design誌「実践LLMアプリケーション開発」第19回サンプルコード 2 | 3 | ## サンプルコードの実行方法 4 | 5 | ### プロジェクトのセットアップ 6 | 7 | ※ このプロジェクトは`uv`を使用しています。`uv`のインストール方法については[こちら](https://github.com/astral-sh/uv)をご確認ください。 8 | 9 | 以下のコマンドを実行し、必要なライブラリのインストールを行って下さい。 10 | 11 | ``` 12 | $ uv sync 13 | ``` 14 | 15 | ## 実行方法 16 | 17 | 以下のコマンドを実行することで、MCPサーバの動作確認を行うことができます。 18 | 19 | ```bash 20 | uv run python -m sd_19.client 21 | ``` 22 | 23 | ## MCPサーバの組み込み方 24 | 25 | 例えばCursorエディタに本サンプルコードの`sd_19/server.py`をMCPサーバとして組み込みたい場合、以下のように設定します。 26 | 27 | | 項目 | 設定値 | 28 | |------|--------| 29 | | Name | sd-19 | 30 | | Type | Command | 31 | | Command | `uv --directory [プロジェクトのディレクトリパス]19 run sd_19/server.py` | 32 | 33 | 実際にCursorに設定した場合は、以下のように表示されます。(※ Cursor Settings > Features > MCP Servers) 34 | 35 | ![Cursorでの設定例](cursor.png) -------------------------------------------------------------------------------- /21/run.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | LangGraph Content Creator 実行スクリプト 4 | """ 5 | 6 | import os 7 | import sys 8 | 9 | import streamlit.web.cli as stcli 10 | from dotenv import load_dotenv 11 | 12 | 13 | def main(): 14 | """ 15 | アプリケーションを実行するエントリポイント 16 | """ 17 | # 環境変数の読み込み 18 | load_dotenv() 19 | 20 | if "ANTHROPIC_API_KEY" not in os.environ: 21 | print("エラー: ANTHROPIC_API_KEYが設定されていません。") 22 | print(".envファイルを作成し、APIキーを設定してください。") 23 | print("例: ANTHROPIC_API_KEY=your_api_key_here") 24 | sys.exit(1) 25 | 26 | # Streamlitアプリの実行 27 | app_path = os.path.join("src", "content_creator", "app.py") 28 | sys.argv = ["streamlit", "run", app_path] 29 | sys.exit(stcli.main()) 30 | 31 | 32 | if __name__ == "__main__": 33 | main() 34 | -------------------------------------------------------------------------------- /25/chatbot_module.py: -------------------------------------------------------------------------------- 1 | """ 2 | 枝豆の妖精スタイルチャットボットのモジュール 3 | """ 4 | 5 | import dspy 6 | 7 | class ConversationSignature(dspy.Signature): 8 | """枝豆の妖精として対話する""" 9 | query = dspy.InputField(desc="ユーザーからの質問や発言") 10 | history = dspy.InputField(desc="過去の対話履歴", format=list, default=[]) 11 | response = dspy.OutputField(desc="枝豆の妖精としての応答。語尾に「のだ」「なのだ」を自然に使い、一人称は「ボク」。親しみやすく可愛らしい口調で、日本語として自然な文章") 12 | 13 | class EdamameFairyBot(dspy.Module): 14 | """枝豆の妖精スタイルのチャットボット""" 15 | 16 | def __init__(self): 17 | super().__init__() 18 | self.respond = dspy.Predict(ConversationSignature) 19 | 20 | def forward(self, query: str, history: list | None = None) -> dspy.Prediction: 21 | if history is None: 22 | history = [] 23 | return self.respond(query=query, history=history) 24 | -------------------------------------------------------------------------------- /09/prompts/write_system.prompt: -------------------------------------------------------------------------------- 1 | You are an elicit insights reporter who analyzes context and draws out important insights hidden within it. 2 | 3 | ### task 4 | Using the information, write a detailed report -- The report should focus on the answer to the query, should be well structured, informative, in depth and comprehensive, with facts and numbers. 5 | 6 | Use an unbiased and journalistic tone. 7 | You MUST determine your own CONCRETE, ACTIONABLE and VALID opinion based on the given information. Do NOT deter to general and meaningless conclusions. 8 | You MUST write all used source and source title and url at the end of the report as references, and make sure to not add duplicated sources, but only one reference for each. 9 | You MUST write the source for each passage. 10 | you MUST write the report in Japanese. 11 | Please do your best, this is very important to my career. 12 | -------------------------------------------------------------------------------- /10/prompts/write_system.prompt: -------------------------------------------------------------------------------- 1 | You are an elicit insights reporter who analyzes context and draws out important insights hidden within it. 2 | 3 | ### task 4 | Using the information, write a detailed report -- The report should focus on the answer to the query, should be well structured, informative, in depth and comprehensive, with facts and numbers. 5 | 6 | Use an unbiased and journalistic tone. 7 | You MUST determine your own CONCRETE, ACTIONABLE and VALID opinion based on the given information. Do NOT deter to general and meaningless conclusions. 8 | You MUST write all used source and source title and url at the end of the report as references, and make sure to not add duplicated sources, but only one reference for each. 9 | You MUST write the source for each passage. 10 | you MUST write the report in Japanese. 11 | Please do your best, this is very important to my career. 12 | -------------------------------------------------------------------------------- /30/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=42", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "sd-30" 7 | version = "0.1.0" 8 | description = "LangChain Middleware デモアプリケーション" 9 | requires-python = ">=3.12" 10 | dependencies = [ 11 | "streamlit>=1.52.1", 12 | "langchain>=1.1.3", 13 | "langchain-openai>=1.1.1", 14 | "langchain-anthropic>=1.2.0", 15 | "langgraph>=1.0.4", 16 | "pydantic>=2.11.0", 17 | "python-dotenv>=1.1.0", 18 | ] 19 | 20 | [tool.uv] 21 | dev-dependencies = [ 22 | "black>=25.1.0", 23 | "mypy>=1.15.0", 24 | "pytest>=9.0.2", 25 | ] 26 | 27 | [tool.setuptools.packages.find] 28 | where = ["src"] 29 | 30 | [tool.black] 31 | line-length = 100 32 | target-version = ["py312"] 33 | 34 | [tool.mypy] 35 | python_version = "3.12" 36 | warn_return_any = true 37 | warn_unused_configs = true 38 | -------------------------------------------------------------------------------- /24/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "sd-24" 3 | version = "0.1.0" 4 | requires-python = ">=3.12" 5 | dependencies = [ 6 | "langgraph", 7 | "langgraph-supervisor", 8 | "langchain-anthropic", 9 | "langchain-core", 10 | "langchain-tavily", 11 | "python-dotenv", 12 | ] 13 | 14 | [build-system] 15 | requires = ["hatchling"] 16 | build-backend = "hatchling.build" 17 | 18 | [tool.hatch.build.targets.wheel] 19 | packages = ["src/sd_24"] 20 | 21 | [dependency-groups] 22 | dev = [ 23 | "langgraph-cli[inmem]>=0.3.3", 24 | "mypy>=1.16.1", 25 | "pytest>=8.4.1", 26 | "pytest-asyncio>=1.0.0", 27 | "ruff>=0.12.0", 28 | ] 29 | 30 | [tool.mypy] 31 | python_version = "3.12" 32 | warn_return_any = true 33 | warn_unused_configs = true 34 | disallow_untyped_defs = false 35 | ignore_missing_imports = true 36 | 37 | [[tool.mypy.overrides]] 38 | module = "langchain_tavily.*" 39 | ignore_missing_imports = true 40 | -------------------------------------------------------------------------------- /30/src/sd_30/mock/resilient_agent.json: -------------------------------------------------------------------------------- 1 | { 2 | "web_results": { 3 | "python": "Pythonは汎用プログラミング言語で、シンプルな文法と豊富なライブラリが特徴です。", 4 | "langchain": "LangChainはLLMアプリケーション開発のためのフレームワークで、エージェント、チェーン、メモリなどの機能を提供します。", 5 | "ai": "人工知能(AI)は機械学習、深層学習、自然言語処理などの技術を含む広い分野です。", 6 | "default": "検索結果: 関連する情報が見つかりました。詳細については専門的な資料をご参照ください。" 7 | }, 8 | "database": { 9 | "users": [ 10 | {"id": 1, "name": "田中太郎", "department": "開発部"}, 11 | {"id": 2, "name": "山田花子", "department": "営業部"}, 12 | {"id": 3, "name": "鈴木一郎", "department": "経理部"} 13 | ], 14 | "products": [ 15 | {"id": 101, "name": "製品A", "price": 10000}, 16 | {"id": 102, "name": "製品B", "price": 20000}, 17 | {"id": 103, "name": "製品C", "price": 15000} 18 | ], 19 | "sales": [ 20 | {"product_id": 101, "quantity": 50, "date": "2025-12"}, 21 | {"product_id": 102, "quantity": 30, "date": "2025-12"}, 22 | {"product_id": 103, "quantity": 45, "date": "2025-12"} 23 | ] 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /04/chatbot.py: -------------------------------------------------------------------------------- 1 | import os 2 | import chainlit as cl 3 | from conversational_agent import ConversationalAgent 4 | from pathlib import Path 5 | 6 | # 作業用ディレクトリの設定 7 | WORKING_DIRECTORY = Path('./work') 8 | 9 | # sk...の部分を自身のOpenAIのAPIキーに置き換える 10 | # os.environ["OPENAI_API_KEY"] = "sk-..." 11 | 12 | 13 | # チャットセッション開始時に実行 14 | @cl.on_chat_start 15 | def chat_start() -> None: 16 | # エージェントを初期化 17 | agent = ConversationalAgent(working_directory=WORKING_DIRECTORY) 18 | 19 | # ユーザーセッションにエージェントを保存 20 | cl.user_session.set("agent", agent) 21 | 22 | 23 | # ユーザーメッセージ受信時に実行 24 | @cl.on_message 25 | async def main(message: cl.Message) -> None: 26 | # ユーザーセッションからチェインを取得 27 | agent = cl.user_session.get("agent") 28 | 29 | # エージェントからの返答を表示 30 | async for step in agent.run(message.content): 31 | if step["is_final"]: 32 | await cl.Message(content=step["message"]).send() 33 | else: 34 | await cl.Message(author="tool", content=step["message"], indent=1).send() 35 | -------------------------------------------------------------------------------- /05/chatbot.py: -------------------------------------------------------------------------------- 1 | import os 2 | import chainlit as cl 3 | from conversational_agent import ConversationalAgent 4 | from pathlib import Path 5 | 6 | # 作業用ディレクトリの設定 7 | WORKING_DIRECTORY = Path('./work') 8 | 9 | # sk...の部分を自身のOpenAIのAPIキーに置き換える 10 | # os.environ["OPENAI_API_KEY"] = "sk-..." 11 | 12 | 13 | # チャットセッション開始時に実行 14 | @cl.on_chat_start 15 | def chat_start() -> None: 16 | # エージェントを初期化 17 | agent = ConversationalAgent(working_directory=WORKING_DIRECTORY) 18 | 19 | # ユーザーセッションにエージェントを保存 20 | cl.user_session.set("agent", agent) 21 | 22 | 23 | # ユーザーメッセージ受信時に実行 24 | @cl.on_message 25 | async def main(message: cl.Message) -> None: 26 | # ユーザーセッションからチェインを取得 27 | agent = cl.user_session.get("agent") 28 | 29 | # エージェントからの返答を表示 30 | async for step in agent.run(message.content): 31 | if step["is_final"]: 32 | await cl.Message(content=step["message"]).send() 33 | else: 34 | await cl.Message(author="tool", content=step["message"], indent=1).send() 35 | -------------------------------------------------------------------------------- /30/tests/conftest.py: -------------------------------------------------------------------------------- 1 | """pytest共通フィクスチャ""" 2 | import uuid 3 | 4 | import pytest 5 | from dotenv import load_dotenv 6 | from langchain_core.messages import AIMessage 7 | 8 | # テスト実行前に環境変数をロード 9 | load_dotenv() 10 | 11 | 12 | @pytest.fixture 13 | def thread_id(): 14 | """ユニークなスレッドIDを生成""" 15 | return str(uuid.uuid4()) 16 | 17 | 18 | def extract_response(result: dict) -> str: 19 | """エージェント結果から最後のAIMessageを抽出""" 20 | if not isinstance(result, dict) or "messages" not in result: 21 | return str(result) 22 | for msg in reversed(result["messages"]): 23 | if isinstance(msg, AIMessage) and msg.content: 24 | if isinstance(msg.content, str): 25 | return msg.content 26 | if isinstance(msg.content, list): 27 | text_parts = [ 28 | block["text"] 29 | for block in msg.content 30 | if isinstance(block, dict) and block.get("type") == "text" 31 | ] 32 | if text_parts: 33 | return "".join(text_parts) 34 | return str(result) 35 | -------------------------------------------------------------------------------- /22/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=42", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "receipt-ocr-agent" 7 | version = "0.1.0" 8 | description = "領収書OCRエージェント" 9 | authors = [ 10 | {name = "Your Name", email = "your.email@example.com"}, 11 | ] 12 | requires-python = ">=3.12" 13 | dependencies = [ 14 | "streamlit>=1.45.0", 15 | "langchain-core>=0.3.58", 16 | "langchain-anthropic>=0.3.12", 17 | "langgraph>=0.4.1", 18 | "python-dotenv>=1.1.0", 19 | "pillow>=11.2.1", 20 | "pandas>=2.2.3", 21 | "pydantic>=2.11.4", 22 | ] 23 | 24 | [tool.uv] 25 | dev-dependencies = [ 26 | "pandas-stubs>=2.2.3.250308", 27 | "black>=25.1.0", 28 | "isort>=6.0.1", 29 | "mypy>=1.15.0", 30 | ] 31 | 32 | [tool.setuptools] 33 | packages = ["src"] 34 | 35 | [tool.black] 36 | line-length = 100 37 | target-version = ["py312"] 38 | 39 | [tool.isort] 40 | profile = "black" 41 | line_length = 100 42 | 43 | [tool.mypy] 44 | python_version = "3.12" 45 | warn_return_any = true 46 | warn_unused_configs = true 47 | disallow_untyped_defs = true 48 | disallow_incomplete_defs = true 49 | -------------------------------------------------------------------------------- /10/prompts/query_refine_user.prompt: -------------------------------------------------------------------------------- 1 | Based on the following information, create a search query for the Tavily API. The search query should be a natural, human-like question. For example, instead of "automobile industry," it should be "What are the current trends in the automobile industry?" 2 | 3 | User request: 4 | {task} 5 | 6 | Previous refined_query: 7 | {refined_query} 8 | 9 | Previous score: 10 | {previous_score} 11 | 12 | If refined_query is not empty and the previous score was below 0.6, it means the previous search returned unreliable information. Improve the query accordingly. If refined_query is empty or the previous score was 0.6 or higher, treat it as an initial request. 13 | 14 | Example: 15 | 16 | Task: "Find out about the trends in the automobile industry" 17 | Refined query: "" 18 | Previous score: 19 | Search query: What are the recent trends in the automobile industry? 20 | 21 | Task: "Learn about the latest trends in the IT industry" 22 | Refined query: What are the recent trends in the IT industry? 23 | Previous score: 0.4 24 | Search query: Can you provide reliable information on the latest trends in the IT industry? 25 | 26 | Generate only the search query. -------------------------------------------------------------------------------- /26/artifact/rag_baseline.json: -------------------------------------------------------------------------------- 1 | { 2 | "rewrite": { 3 | "traces": [], 4 | "train": [], 5 | "demos": [], 6 | "signature": { 7 | "instructions": "質問文を検索用のクエリにリライト", 8 | "fields": [ 9 | { 10 | "prefix": "Question:", 11 | "description": "ユーザーからの質問" 12 | }, 13 | { 14 | "prefix": "Rewritten Query:", 15 | "description": "埋め込みベクトルによる類似度検索に最適化されたクエリ" 16 | } 17 | ] 18 | }, 19 | "lm": null 20 | }, 21 | "generate.predict": { 22 | "traces": [], 23 | "train": [], 24 | "demos": [], 25 | "signature": { 26 | "instructions": "コンテキストと質問から回答を生成", 27 | "fields": [ 28 | { 29 | "prefix": "Context:", 30 | "description": "検索で取得した文書群" 31 | }, 32 | { 33 | "prefix": "Question:", 34 | "description": "ユーザーからの質問" 35 | }, 36 | { 37 | "prefix": "Answer:", 38 | "description": "質問に対する簡潔な回答" 39 | } 40 | ] 41 | }, 42 | "lm": null 43 | }, 44 | "metadata": { 45 | "dependency_versions": { 46 | "python": "3.13", 47 | "dspy": "3.0.3", 48 | "cloudpickle": "3.1" 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /27/artifact/rag_baseline.json: -------------------------------------------------------------------------------- 1 | { 2 | "rewrite": { 3 | "traces": [], 4 | "train": [], 5 | "demos": [], 6 | "signature": { 7 | "instructions": "質問文を検索用のクエリにリライト", 8 | "fields": [ 9 | { 10 | "prefix": "Question:", 11 | "description": "ユーザーからの質問" 12 | }, 13 | { 14 | "prefix": "Rewritten Query:", 15 | "description": "埋め込みベクトルによる類似度検索に最適化されたクエリ" 16 | } 17 | ] 18 | }, 19 | "lm": null 20 | }, 21 | "generate.predict": { 22 | "traces": [], 23 | "train": [], 24 | "demos": [], 25 | "signature": { 26 | "instructions": "コンテキストと質問から回答を生成", 27 | "fields": [ 28 | { 29 | "prefix": "Context:", 30 | "description": "検索で取得した文書群" 31 | }, 32 | { 33 | "prefix": "Question:", 34 | "description": "ユーザーからの質問" 35 | }, 36 | { 37 | "prefix": "Answer:", 38 | "description": "質問に対する簡潔な回答" 39 | } 40 | ] 41 | }, 42 | "lm": null 43 | }, 44 | "metadata": { 45 | "dependency_versions": { 46 | "python": "3.13", 47 | "dspy": "3.0.3", 48 | "cloudpickle": "3.1" 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /12/single_step_approach.py: -------------------------------------------------------------------------------- 1 | from langchain_openai import ChatOpenAI 2 | from langgraph.prebuilt import create_react_agent 3 | from tools import report_writer, search 4 | from utility import load_prompt, run_streaming_agent 5 | 6 | 7 | def create_single_step_agent(llm: ChatOpenAI): 8 | tools = [search, report_writer] 9 | return create_react_agent( 10 | llm, tools=tools, messages_modifier=load_prompt("single_step_answering_system") 11 | ) 12 | 13 | 14 | def main(): 15 | import argparse 16 | 17 | from settings import Settings 18 | 19 | # 共通の設定情報を読み込み 20 | settings = Settings() 21 | 22 | # コマンドライン引数のパーサーを作成 23 | parser = argparse.ArgumentParser(description="Process some queries.") 24 | parser.add_argument("--task", type=str, required=True, help="The query to search") 25 | 26 | # コマンドライン引数を解析 27 | args = parser.parse_args() 28 | 29 | llm = ChatOpenAI(model=settings.LLM_MODEL_NAME, temperature=0.0) 30 | inputs = {"messages": [("user", args.task)]} 31 | agent = create_single_step_agent(llm=llm) 32 | final_output = run_streaming_agent(agent, inputs) 33 | print("\n\n") 34 | print("=== final_output ===") 35 | print(final_output) 36 | 37 | 38 | if __name__ == "__main__": 39 | main() 40 | -------------------------------------------------------------------------------- /23/src/sd_23/agents/math_agent.py: -------------------------------------------------------------------------------- 1 | """数学計算に特化したエージェント""" 2 | 3 | from langchain_anthropic import ChatAnthropic 4 | from langchain_core.tools import tool 5 | from langgraph.prebuilt import create_react_agent 6 | from langgraph.graph.graph import CompiledGraph 7 | 8 | 9 | @tool 10 | def add(a: float, b: float) -> float: 11 | """2つの数値を加算します""" 12 | return a + b 13 | 14 | 15 | @tool 16 | def multiply(a: float, b: float) -> float: 17 | """2つの数値を乗算します""" 18 | return a * b 19 | 20 | 21 | @tool 22 | def divide(a: float, b: float) -> float: 23 | """2つの数値を除算します(bがゼロでない場合)""" 24 | if b == 0: 25 | return float("inf") 26 | return a / b 27 | 28 | 29 | def create_math_agent() -> CompiledGraph: 30 | """計算エージェントを作成""" 31 | model = ChatAnthropic(temperature=0, model_name="claude-sonnet-4-20250514") # type: ignore[call-arg] 32 | 33 | tools = [add, multiply, divide] 34 | 35 | prompt = """あなたは計算専門のエージェントです。 36 | 37 | 役割と制約: 38 | 1. 与えられた数値と計算式に対して、ツールを使用して計算のみ実行 39 | 2. 情報収集・調査・推論は一切行わない 40 | 3. 計算手順を最小限に示し、最終結果を明確に報告 41 | 42 | 出力形式: 43 | - 「計算実行:[計算式]」 44 | - 「結果:[数値]」""" 45 | 46 | agent = create_react_agent( 47 | model=model, tools=tools, name="math_expert", prompt=prompt 48 | ) 49 | 50 | return agent 51 | -------------------------------------------------------------------------------- /23/src/sd_23/agents/research_agent.py: -------------------------------------------------------------------------------- 1 | """調査・情報収集に特化したエージェント""" 2 | 3 | from langchain_anthropic import ChatAnthropic 4 | from langchain_core.tools import tool 5 | from langgraph.prebuilt import create_react_agent 6 | from langgraph.graph.graph import CompiledGraph 7 | 8 | 9 | @tool 10 | def web_search(query: str) -> str: 11 | """ウェブ検索を実行します(モック実装)""" 12 | return f"「{query}」に関する検索結果: 複数の調査レポートによると、2025年のグローバルAI市場規模は約5,200億ドルと予測されています(IDC調査)。2025年の最新AI技術として、生成AI(GPT、Claude)、マルチモーダルAI、エッジAI、量子機械学習などが注目されています。" 13 | 14 | 15 | @tool 16 | def get_wikipedia_info(topic: str) -> str: 17 | """Wikipedia情報を取得します(モック実装)""" 18 | return f"「{topic}」に関するWikipedia情報: {topic}は興味深いトピックです。" 19 | 20 | 21 | def create_research_agent() -> CompiledGraph: 22 | """検索エージェントを作成""" 23 | model = ChatAnthropic(temperature=0, model_name="claude-sonnet-4-20250514") # type: ignore[call-arg] 24 | 25 | tools = [web_search, get_wikipedia_info] 26 | 27 | prompt = """あなたは情報収集専門のエージェントです。 28 | 29 | 役割: 30 | - 検索ツールを使って情報収集のみ行う 31 | - 収集した情報(数値を含む)を端的に報告する 32 | 33 | 制約: 34 | - 計算や分析は一切行わない 35 | - 検索結果をそのまま報告する""" 36 | 37 | agent = create_react_agent( 38 | model=model, tools=tools, name="research_expert", prompt=prompt 39 | ) 40 | 41 | return agent 42 | -------------------------------------------------------------------------------- /12/multi_step_approach.py: -------------------------------------------------------------------------------- 1 | from langchain_openai import ChatOpenAI 2 | from langgraph.prebuilt import create_react_agent 3 | from tools import report_writer, search, sufficiency_check 4 | from utility import load_prompt, run_streaming_agent 5 | 6 | 7 | def create_multi_step_agent(llm: ChatOpenAI): 8 | tools = [search, sufficiency_check, report_writer] 9 | return create_react_agent( 10 | llm, tools=tools, messages_modifier=load_prompt("multi_step_answering_system") 11 | ) 12 | 13 | 14 | def main(): 15 | import argparse 16 | 17 | from settings import Settings 18 | 19 | settings = Settings() 20 | 21 | # コマンドライン引数のパーサーを作成 22 | parser = argparse.ArgumentParser(description="Process some queries.") 23 | parser.add_argument("--task", type=str, required=True, help="The query to search") 24 | 25 | # コマンドライン引数を解析 26 | args = parser.parse_args() 27 | 28 | llm = ChatOpenAI(model=settings.LLM_MODEL_NAME, temperature=0.0) 29 | inputs = {"messages": [("user", args.task)]} 30 | agent = create_multi_step_agent(llm=llm) 31 | final_output = run_streaming_agent(agent, inputs) 32 | print("\n\n") 33 | print("=== final_output ===") 34 | print(final_output) 35 | 36 | 37 | if __name__ == "__main__": 38 | main() 39 | -------------------------------------------------------------------------------- /24/README.md: -------------------------------------------------------------------------------- 1 | # Software Design誌「実践LLMアプリケーション開発」第24回サンプルコード 2 | 3 | ## サンプルコードの実行方法 4 | 5 | ### プロジェクトのセットアップ 6 | 7 | ※ このプロジェクトは`uv`を使用しています。`uv`のインストール方法については[こちら](https://github.com/astral-sh/uv)をご確認ください。 8 | 9 | 以下のコマンドを実行し、必要なライブラリのインストールを行って下さい。 10 | 11 | ``` 12 | $ uv sync 13 | ``` 14 | 15 | 次に環境変数の設定を行います。まず`.env.sample`ファイルをコピーして`.env`ファイルを作成します。 16 | 17 | ``` 18 | $ cp .env.sample .env 19 | $ vi .env # お好きなエディタで編集してください 20 | ``` 21 | 22 | `.env`ファイルを編集し、以下のAPIキーを設定してください。 23 | 24 | ``` 25 | ANTHROPIC_API_KEY=your_anthropic_api_key_here 26 | TAVILY_API_KEY=your_tavily_api_key_here 27 | LANGCHAIN_TRACING_V2=true 28 | LANGCHAIN_API_KEY=your_langsmith_key_here 29 | LANGCHAIN_PROJECT=sd-24 30 | ``` 31 | 32 | - `ANTHROPIC_API_KEY`: Claude APIのキー 33 | - `TAVILY_API_KEY`: Web検索機能用のAPIキー 34 | - `LANGCHAIN_API_KEY`: LangSmithのAPIキー(オプション) 35 | 36 | ### 実行方法 37 | 38 | LangGraph Studioでの実行: 39 | 40 | ```bash 41 | uv run langgraph dev 42 | ``` 43 | 44 | コマンドラインでの実行: 45 | 46 | ```bash 47 | # 対話形式で実行 48 | uv run python main.py 49 | 50 | # 直接質問を指定して実行 51 | uv run python main.py "LangChainについて教えて" 52 | 53 | # デバッグモードで実行 54 | uv run python main.py "2025年のAI動向をレポートして" --debug 55 | ``` 56 | 57 | ### コマンドライン引数 58 | 59 | - `query`: エージェントへの質問や指示(省略時は対話モード) 60 | - `--debug`: デバッグモードで実行(詳細なイベント情報を表示) 61 | -------------------------------------------------------------------------------- /24/src/sd_24/utils/memory.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | from langchain_anthropic import ChatAnthropic 3 | 4 | 5 | class Memory: 6 | """Claude 3.5 Haikuを使用したインテリジェント圧縮機能付き外部メモリ""" 7 | 8 | def __init__(self): 9 | self._store: dict[str, Any] = {} 10 | # 圧縮用のClaude 3.5 Haiku 11 | self._compressor = ChatAnthropic( 12 | model_name="claude-3-5-haiku-20241022", temperature=0 13 | ) 14 | 15 | def set(self, key: str, value: Any) -> None: 16 | """データを保存(圧縮は明示的に指定)""" 17 | self._store[key] = value 18 | 19 | def get(self, key: str, default: Any = None) -> Any: 20 | """データを取得""" 21 | return self._store.get(key, default) 22 | 23 | async def compress_research(self, topic: str, findings: str) -> str: 24 | """Web検索結果をインテリジェントに圧縮""" 25 | prompt = f""" 26 | 以下はWeb検索で得られた「{topic}」に関する情報です。 27 | 重要なポイントを抓えて簡潔に要約してください: 28 | 29 | {findings} 30 | """ 31 | response = await self._compressor.ainvoke(prompt) 32 | if isinstance(response.content, str): 33 | return response.content 34 | else: 35 | # リストの場合は結合 36 | return "\n".join(str(item) for item in response.content) 37 | 38 | 39 | # シングルトンインスタンスとして公開 40 | memory = Memory() 41 | -------------------------------------------------------------------------------- /01/chatbot.py: -------------------------------------------------------------------------------- 1 | import openai 2 | import chainlit as cl 3 | 4 | # sk...の部分を自身のAPIキーに置き換える 5 | openai.api_key = "sk-..." 6 | 7 | 8 | # 会話履歴をユーザーセッションに保存する 9 | def store_history(role, message): 10 | history = cl.user_session.get("history") 11 | history.append({"role": role, "content": message}) 12 | cl.user_session.set("history", history) 13 | 14 | 15 | # ユーザーセッションに保存された会話履歴から新しいメッセージを生成する 16 | def generate_message(): 17 | response = openai.ChatCompletion.create( 18 | model="gpt-3.5-turbo", 19 | messages=cl.user_session.get("history"), 20 | temperature=0.7, 21 | max_tokens=300, 22 | ) 23 | return response.choices[0].message.content 24 | 25 | 26 | # チャットセッション開始時に実行 27 | @cl.on_chat_start 28 | def chat_start(): 29 | cl.user_session.set("history", [{ 30 | "role": "system", 31 | "content": "あなたは枝豆の妖精です。一人称は「ボク」で、語尾に「なのだ」をつけて話すことが特徴です。" 32 | }]) 33 | 34 | 35 | # ユーザーメッセージ受信時に実行 36 | @cl.on_message 37 | async def main(message: str): 38 | # 会話履歴にユーザーメッセージを追加 39 | store_history("user", message) 40 | 41 | # 新しいメッセージを生成 42 | reply = generate_message() 43 | 44 | # 新しいメッセージを会話履歴に追加 45 | store_history("assistant", reply) 46 | 47 | # チャット上にChatGPTからの返信を表示 48 | msg = cl.Message(content=reply) 49 | await msg.send() 50 | -------------------------------------------------------------------------------- /12/utility.py: -------------------------------------------------------------------------------- 1 | import os 2 | from typing import Any 3 | 4 | from langgraph.graph.graph import CompiledGraph 5 | 6 | 7 | # プロンプトファイルを読み込む関数 8 | def load_prompt(name: str) -> str: 9 | prompt_path = os.path.join(os.path.dirname(__file__), "prompts", f"{name}.prompt") 10 | with open(prompt_path, "r") as f: 11 | return f.read() 12 | 13 | 14 | # stream関数でエージェントを呼び出し、実行結果を逐次表示する 15 | def run_streaming_agent( 16 | agent: CompiledGraph, inputs: list[dict[str, Any]], verbose: bool = False 17 | ) -> str: 18 | result = "" 19 | for s in agent.stream(inputs, stream_mode="values"): 20 | message = s["messages"][-1] 21 | if isinstance(message, tuple): 22 | result = str(message) 23 | else: 24 | message.pretty_print() 25 | result = message.content 26 | 27 | if verbose: 28 | display_result = "" 29 | if len(result) > 1000: 30 | display_result = result[:1000] + "..." 31 | else: 32 | display_result = result 33 | print(display_result) 34 | return result 35 | 36 | 37 | # invoke関数でエージェントを呼び出し、実行結果を返す 38 | def run_invoke_agent(agent: CompiledGraph, inputs: list[dict[str, Any]]) -> str: 39 | result = agent.invoke(inputs) 40 | return str(result["messages"][-1]) 41 | -------------------------------------------------------------------------------- /24/src/sd_24/agents/task_decomposer.py: -------------------------------------------------------------------------------- 1 | from langgraph.prebuilt import create_react_agent 2 | from langchain_anthropic import ChatAnthropic 3 | from datetime import datetime 4 | from ..utils.todo_tools import ( 5 | create_todo_task, 6 | create_multiple_todos, 7 | ) 8 | from ..utils.search_tools import ( 9 | search_and_save, 10 | get_search_results, 11 | ) 12 | 13 | 14 | def create_task_decomposer(): 15 | # モデルの定義 16 | model = ChatAnthropic( 17 | model_name="claude-sonnet-4-20250514", 18 | temperature=0, 19 | ) 20 | 21 | # ツールの定義 22 | tools = [ 23 | search_and_save, 24 | get_search_results, 25 | create_todo_task, 26 | create_multiple_todos, 27 | ] 28 | 29 | # システムプロンプトの定義 30 | system_prompt = f"""現在日付: {datetime.now().strftime('%Y年%m月%d日')} 31 | 32 | あなたはタスク分解専門のアシスタントです。 33 | 34 | 役割: 35 | - ユーザーの要求を分析し、実行可能なタスクに分解 36 | - テーマに関する情報を収集し、適切な計画を立案 37 | - 調査タスクはagent='research'、執筆タスクはagent='writer'として割り当て 38 | 39 | 重要な制約: 40 | - タスク分解とTODO作成に専念 41 | - まとめや総括は行わない 42 | - TODOを作成したら「TODOを作成しました」とだけ報告""" 43 | 44 | return create_react_agent( 45 | name="task_decomposer", 46 | model=model, 47 | tools=tools, 48 | prompt=system_prompt, 49 | ) 50 | 51 | 52 | # グラフのエクスポート(LangGraph Studio用) 53 | graph = create_task_decomposer() 54 | -------------------------------------------------------------------------------- /23/src/sd_23/supervisor_graph.py: -------------------------------------------------------------------------------- 1 | """Supervisorパターンのサンプル""" 2 | 3 | from dotenv import load_dotenv 4 | from langchain_anthropic import ChatAnthropic 5 | from langgraph_supervisor import create_supervisor 6 | 7 | from .agents import create_math_agent, create_research_agent 8 | 9 | # 環境変数を読み込み 10 | load_dotenv() 11 | 12 | 13 | def create_supervisor_workflow(): 14 | """Supervisorワークフローを作成(コンパイル前)""" 15 | # エージェントを作成 16 | math_agent = create_math_agent() 17 | research_agent = create_research_agent() 18 | 19 | # Supervisorモデル 20 | supervisor_model = ChatAnthropic( 21 | temperature=0, model_name="claude-sonnet-4-20250514" 22 | ) # type: ignore[call-arg] 23 | 24 | # Supervisorプロンプト 25 | supervisor_prompt = """あなたはタスクコーディネーターです。 26 | 27 | 利用可能なエージェント: 28 | - research_expert: 情報収集専門 29 | - math_expert: 計算専門 30 | 31 | タスク実行方法: 32 | 1. 情報収集が必要な場合、research_expertに委譲 33 | 2. 計算が必要な場合、具体的な数値と計算内容をmath_expertに伝える""" 34 | 35 | # Supervisorワークフローを作成 36 | workflow = create_supervisor( 37 | agents=[math_agent, research_agent], 38 | model=supervisor_model, 39 | prompt=supervisor_prompt, 40 | add_handoff_messages=True, 41 | output_mode="full_history", 42 | ) 43 | 44 | return workflow 45 | 46 | 47 | # LangGraph Studio用のエントリーポイント 48 | graph = create_supervisor_workflow().compile() 49 | -------------------------------------------------------------------------------- /30/src/sd_30/mock/email_agent.json: -------------------------------------------------------------------------------- 1 | { 2 | "emails": { 3 | "001": { 4 | "id": "001", 5 | "from": "tanaka.taro@example.com", 6 | "to": "yamada.hanako@example.com", 7 | "subject": "プロジェクトの進捗報告", 8 | "body": "山田様\n\nお疲れ様です。田中です。\n\nプロジェクトAの進捗についてご報告いたします。\n現在、開発フェーズは80%完了しており、来週中にはテストフェーズに移行できる見込みです。\n\nご不明な点がございましたら、お気軽にお問い合わせください。\n電話番号: 03-1234-5678\n\nよろしくお願いいたします。\n\n田中太郎\ntanaka.taro@example.com\n", 9 | "date": "2025-12-09" 10 | }, 11 | "002": { 12 | "id": "002", 13 | "from": "suzuki.ichiro@partner.co.jp", 14 | "to": "yamada.hanako@example.com", 15 | "subject": "契約書の確認依頼", 16 | "body": "山田様\n\nいつもお世話になっております。\n鈴木です。\n\n先日お送りした契約書について、ご確認いただけましたでしょうか。\n修正点などございましたら、ご連絡ください。\n\n担当: 鈴木一郎\n連絡先: 090-9876-5432\nメール: suzuki.ichiro@partner.co.jp\n\n何卒よろしくお願いいたします。\n", 17 | "date": "2025-12-08" 18 | }, 19 | "003": { 20 | "id": "003", 21 | "from": "info@newsletter.example.com", 22 | "to": "yamada.hanako@example.com", 23 | "subject": "【重要】サービスメンテナンスのお知らせ", 24 | "body": "お客様各位\n\n平素より当サービスをご利用いただき、誠にありがとうございます。\n\n下記の日程でシステムメンテナンスを実施いたします。\n\n日時: 2025年12月15日(日) 02:00〜06:00\n影響: 上記時間帯はサービスをご利用いただけません\n\nご不便をおかけしますが、何卒ご理解いただきますようお願い申し上げます。\n\nサポートセンター\n電話: 0120-000-000\nメール: support@newsletter.example.com\n", 25 | "date": "2025-12-07" 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /30/src/sd_30/agents/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | エージェントモジュール 3 | """ 4 | 5 | from dataclasses import dataclass 6 | from typing import Literal 7 | 8 | from sd_30.agents.email_agent import ( 9 | clear_sent_emails, 10 | create_email_agent, 11 | get_email_list, 12 | get_sent_emails, 13 | ) 14 | from sd_30.agents.resilient_agent import ( 15 | clear_execution_log, 16 | create_resilient_agent, 17 | get_execution_log, 18 | get_metrics, 19 | ) 20 | from sd_30.agents.tool_selector_agent import ( 21 | clear_selected_tools, 22 | create_tool_selector_agent, 23 | get_all_tool_names, 24 | get_selected_tools, 25 | ) 26 | 27 | 28 | @dataclass 29 | class AgentResponse: 30 | """エージェント対話の結果""" 31 | 32 | status: Literal["success", "pending_approval", "pii_blocked", "error"] 33 | message: str 34 | approval_info: dict | None = None 35 | 36 | 37 | __all__ = [ 38 | # 型 39 | "AgentResponse", 40 | # email_agent 41 | "create_email_agent", 42 | "get_email_list", 43 | "get_sent_emails", 44 | "clear_sent_emails", 45 | # resilient_agent 46 | "create_resilient_agent", 47 | "get_execution_log", 48 | "clear_execution_log", 49 | "get_metrics", 50 | # tool_selector_agent 51 | "create_tool_selector_agent", 52 | "get_all_tool_names", 53 | "get_selected_tools", 54 | "clear_selected_tools", 55 | ] 56 | -------------------------------------------------------------------------------- /24/src/sd_24/agents/research.py: -------------------------------------------------------------------------------- 1 | from langgraph.prebuilt import create_react_agent 2 | from langchain_anthropic import ChatAnthropic 3 | from datetime import datetime 4 | from ..utils.todo_tools import ( 5 | create_get_my_todos_for_agent, 6 | update_multiple_todo_status, 7 | ) 8 | from ..utils.search_tools import ( 9 | search_and_save, 10 | get_search_results, 11 | ) 12 | 13 | 14 | def create_research_agent(): 15 | # モデルの定義 16 | model = ChatAnthropic( 17 | model_name="claude-sonnet-4-20250514", 18 | temperature=0, 19 | ) 20 | 21 | # ツールの定義 22 | tools = [ 23 | create_get_my_todos_for_agent("research"), 24 | search_and_save, 25 | get_search_results, 26 | update_multiple_todo_status, 27 | ] 28 | 29 | # プロンプトの定義 30 | system_prompt = f"""現在日付: {datetime.now().strftime('%Y年%m月%d日')} 31 | 32 | あなたは情報収集専門のアシスタントです。 33 | 34 | 役割: 35 | - task_decomposerが作成した計画に従って調査を実行 36 | - 情報収集とメモリへの保存に専念 37 | - 情報が不足している場合は追加調査を実行 38 | 39 | 重要な制約: 40 | - レポート執筆や文章作成は行わない(Writerの役割) 41 | - 調査結果のまとめや要約は出力しない 42 | - 検索結果を保存したら「調査完了」とだけ報告 43 | - 計画が不適切な場合は、その旨を簡潔に報告""" 44 | 45 | return create_react_agent( 46 | name="research", 47 | model=model, 48 | tools=tools, 49 | prompt=system_prompt, 50 | ) 51 | 52 | 53 | # グラフのエクスポート(LangGraph Studio用) 54 | graph = create_research_agent() 55 | -------------------------------------------------------------------------------- /26/rag_evaluation.py: -------------------------------------------------------------------------------- 1 | """評価スクリプト""" 2 | import argparse 3 | from rag_module import RAGQA 4 | from rag_optimization import OPTIMIZED_MODEL_LATEST 5 | from evaluator import evaluation 6 | from dataset_loader import load_jqara_dataset 7 | 8 | 9 | def main(seed=42): 10 | """メイン実行関数 11 | 12 | Args: 13 | seed: ランダムシード(デフォルト: 42) 14 | """ 15 | testset, test_corpus_texts = load_jqara_dataset(num_questions=30, dataset_split='test', random_seed=seed) 16 | 17 | # ベースライン評価 18 | baseline = RAGQA() 19 | base_results = evaluation(baseline, examples=testset, corpus_texts=test_corpus_texts, display_table=0) 20 | 21 | # 最適化モデル評価 22 | optimized = RAGQA() 23 | optimized.load(OPTIMIZED_MODEL_LATEST) 24 | opt_results = evaluation(optimized, examples=testset, corpus_texts=test_corpus_texts, display_table=0) 25 | print("=" * 60) 26 | print("🔬 RAG評価") 27 | print(f"[Baseline] EM: {base_results.score:.1f}%") 28 | print(f"[Optimized] EM: {opt_results.score:.1f}% (Δ {opt_results.score - base_results.score:+.1f}%)") 29 | print("=" * 60) 30 | 31 | if __name__ == "__main__": 32 | parser = argparse.ArgumentParser(description='RAGシステムの評価') 33 | parser.add_argument('--seed', type=int, default=42, 34 | help='ランダムシード(デフォルト: 42)') 35 | args = parser.parse_args() 36 | 37 | print(f"🌱 シード値: {args.seed}") 38 | main(seed=args.seed) 39 | -------------------------------------------------------------------------------- /27/rag_evaluation.py: -------------------------------------------------------------------------------- 1 | """評価スクリプト""" 2 | import argparse 3 | from rag_module import RAGQA 4 | from rag_optimization import OPTIMIZED_MODEL_LATEST 5 | from evaluator import evaluation 6 | from dataset_loader import load_jqara_dataset 7 | 8 | 9 | def main(seed=42): 10 | """メイン実行関数 11 | 12 | Args: 13 | seed: ランダムシード(デフォルト: 42) 14 | """ 15 | testset, test_corpus_texts = load_jqara_dataset(num_questions=30, dataset_split='test', random_seed=seed) 16 | 17 | # ベースライン評価 18 | baseline = RAGQA() 19 | base_results = evaluation(baseline, examples=testset, corpus_texts=test_corpus_texts, display_table=0) 20 | 21 | # 最適化モデル評価 22 | optimized = RAGQA() 23 | optimized.load(OPTIMIZED_MODEL_LATEST) 24 | opt_results = evaluation(optimized, examples=testset, corpus_texts=test_corpus_texts, display_table=0) 25 | print("=" * 60) 26 | print("🔬 RAG評価") 27 | print(f"[Baseline] EM: {base_results.score:.1f}%") 28 | print(f"[Optimized] EM: {opt_results.score:.1f}% (Δ {opt_results.score - base_results.score:+.1f}%)") 29 | print("=" * 60) 30 | 31 | if __name__ == "__main__": 32 | parser = argparse.ArgumentParser(description='RAGシステムの評価') 33 | parser.add_argument('--seed', type=int, default=42, 34 | help='ランダムシード(デフォルト: 42)') 35 | args = parser.parse_args() 36 | 37 | print(f"🌱 シード値: {args.seed}") 38 | main(seed=args.seed) 39 | -------------------------------------------------------------------------------- /22/run.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | 領収書OCRエージェント実行スクリプト 4 | """ 5 | import os 6 | import sys 7 | from pathlib import Path 8 | 9 | import streamlit.web.cli as stcli 10 | from dotenv import load_dotenv 11 | 12 | 13 | def main() -> None: 14 | """アプリケーションのメイン関数""" 15 | # 環境変数の読み込み 16 | env_path = Path(".") / ".env" 17 | load_dotenv(dotenv_path=env_path) 18 | 19 | # 環境変数チェック 20 | required_vars = ["ANTHROPIC_API_KEY"] 21 | missing_vars = [var for var in required_vars if not os.environ.get(var)] 22 | if missing_vars: 23 | print(f"エラー: 以下の環境変数が設定されていません: {', '.join(missing_vars)}") 24 | print("実行前に.envファイルを作成するか、環境変数を設定してください。") 25 | sys.exit(1) 26 | 27 | # プロジェクトルートディレクトリをPYTHONPATHに追加 28 | project_root = Path(__file__).parent.absolute() 29 | sys.path.insert(0, str(project_root)) 30 | 31 | # tmp ディレクトリがない場合は作成 32 | tmp_dir = project_root / "tmp" 33 | if not tmp_dir.exists(): 34 | tmp_dir.mkdir(parents=True) 35 | 36 | # アプリケーションファイルへのパス(直接ファイルパスを指定) 37 | app_path = str(project_root / "src" / "receipt_processor" / "app.py") 38 | 39 | # Streamlitを直接実行(ファイルパスを指定して実行) 40 | sys.argv = [ 41 | "streamlit", 42 | "run", 43 | app_path, 44 | "--server.port=8501", 45 | "--server.address=localhost", 46 | ] 47 | sys.exit(stcli.main()) 48 | 49 | 50 | if __name__ == "__main__": 51 | main() 52 | -------------------------------------------------------------------------------- /20/src/sd_20/state.py: -------------------------------------------------------------------------------- 1 | from typing import Annotated, Sequence 2 | 3 | from langchain_core.messages import BaseMessage, trim_messages 4 | from langchain_core.messages.utils import count_tokens_approximately 5 | from langgraph.graph.message import Messages, add_messages 6 | from langgraph.managed import IsLastStep, RemainingSteps 7 | from typing_extensions import TypedDict 8 | 9 | MAX_TOKENS = 128_000 10 | 11 | def add_and_trim_messages( 12 | left_messages: Messages, 13 | right_messages: Messages 14 | ) -> Messages: 15 | """ 16 | メッセージを結合した後、指定されたトークン数に基づいてトリムします。 17 | 18 | Args: 19 | left_messages: ベースとなるメッセージリスト 20 | right_messages: 追加するメッセージリスト 21 | max_tokens: トリム後の最大トークン数 22 | token_counter: トークン数をカウントするLLM 23 | 24 | Returns: 25 | 結合・トリムされたメッセージリスト 26 | """ 27 | # メッセージを結合 28 | combined_messages = add_messages(left_messages, right_messages) 29 | 30 | # メッセージをトリム 31 | trimmed_messages = trim_messages( 32 | combined_messages, 33 | max_tokens=MAX_TOKENS, 34 | token_counter=count_tokens_approximately, 35 | strategy="last", # 最新のメッセージを保持 36 | include_system=True, # システムメッセージを保持 37 | ) 38 | 39 | return trimmed_messages 40 | 41 | class CustomAgentState(TypedDict): 42 | messages: Annotated[Sequence[BaseMessage], add_and_trim_messages] 43 | is_last_step: IsLastStep 44 | remaining_steps: RemainingSteps 45 | -------------------------------------------------------------------------------- /25/artifact/edamame_fairy_model.json: -------------------------------------------------------------------------------- 1 | { 2 | "respond": { 3 | "traces": [], 4 | "train": [], 5 | "demos": [ 6 | { 7 | "augmented": true, 8 | "query": "魚の種類はどっち?イコクエイラクブカとロープ", 9 | "history": [], 10 | "response": "ごめんねなのだ、ボクは枝豆の妖精だから、魚の種類についてはちょっと詳しくないのだ。でも、「イコクエイラクブカ」と「ロープ」っていうのは、もしかして魚の名前じゃなくて別のものかもしれないのだ。もし詳しく教えてくれたら、できるだけお手伝いするのだ!" 11 | }, 12 | { 13 | "augmented": true, 14 | "query": "アリスの両親には3人の娘がいる:エイミー、ジェシー、そして三女の名前は?", 15 | "history": [], 16 | "response": "ごめんなのだ、ボクにはその三女の名前はわからないのだ。でも、きっと素敵な名前なのだ!" 17 | } 18 | ], 19 | "signature": { 20 | "instructions": "ユーザーからの質問や会話に対して、枝豆の妖精として親しみやすく可愛らしい口調で日本語で返答してください。必ず一人称は「ボク」を使い、語尾には自然に「のだ」または「なのだ」を用いてください。質問内容に応じて丁寧かつ分かりやすく説明し、必要に応じて相手の質問を簡単に言い換えたり補足したりして、会話を楽しく盛り上げてください。", 21 | "fields": [ 22 | { 23 | "prefix": "Query:", 24 | "description": "ユーザーからの質問や発言" 25 | }, 26 | { 27 | "prefix": "History:", 28 | "description": "過去の対話履歴" 29 | }, 30 | { 31 | "prefix": "Response:", 32 | "description": "枝豆の妖精としての応答。語尾に「のだ」「なのだ」を自然に使い、一人称は「ボク」。親しみやすく可愛らしい口調で、日本語として自然な文章" 33 | } 34 | ] 35 | }, 36 | "lm": null 37 | }, 38 | "metadata": { 39 | "dependency_versions": { 40 | "python": "3.13", 41 | "dspy": "3.0.1", 42 | "cloudpickle": "3.1" 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /25/main.py: -------------------------------------------------------------------------------- 1 | """ 2 | 枝豆の妖精スタイルの対話型チャット 3 | """ 4 | 5 | import dspy 6 | from dotenv import load_dotenv 7 | from collections import deque 8 | from chatbot_module import EdamameFairyBot 9 | from chatbot_tuning import OPTIMIAZED_MODEL_PATH 10 | 11 | load_dotenv() 12 | 13 | def main(): 14 | # チャット用LM設定 15 | lm = dspy.LM( 16 | model="openai/gpt-4.1-nano", 17 | temperature=0.0, 18 | max_tokens=1000 19 | ) 20 | 21 | # DSPy標準のグローバル設定 22 | dspy.configure(lm=lm) 23 | 24 | # モデル読み込み 25 | print("📂 モデル読み込み中...") 26 | chatbot = EdamameFairyBot() 27 | chatbot.load(OPTIMIAZED_MODEL_PATH) 28 | print("✅ 最適化済みモデルを読み込みました") 29 | 30 | # 対話履歴を管理 31 | history = deque(maxlen=5) 32 | 33 | print("\n🌱 枝豆の妖精チャットボット") 34 | print("('quit' または 'exit' で終了)") 35 | print("-" * 50) 36 | 37 | while True: 38 | user_input = input("\nあなた: ") 39 | 40 | if user_input.lower() in ['quit', 'exit', '終了']: 41 | print("\n🌱妖精: バイバイなのだ!") 42 | break 43 | 44 | # 履歴をリスト形式に変換 45 | history_list = [f"User: {h[0]}\nBot: {h[1]}" for h in history] 46 | 47 | # 応答生成 48 | result = chatbot(query=user_input, history=history_list) 49 | print(f"🌱妖精: {result.response}") 50 | 51 | # 履歴に追加 52 | history.append((user_input, result.response)) 53 | 54 | if __name__ == "__main__": 55 | main() -------------------------------------------------------------------------------- /18/README.md: -------------------------------------------------------------------------------- 1 | # Software Design誌「実践LLMアプリケーション開発」第18回サンプルコード 2 | 3 | ## サンプルコードの実行方法 4 | 5 | ### 1. 日本語フォントのインストール 6 | 7 | Ubuntuの場合: 8 | ```bash 9 | sudo apt-get update 10 | sudo apt-get install -y fonts-ipafont-gothic fonts-noto-cjk 11 | ``` 12 | 13 | macOSの場合: 14 | ```bash 15 | brew install font-ipa 16 | ``` 17 | 18 | ### 2. プロジェクトのセットアップ 19 | 20 | ※ このプロジェクトは`uv`を使用しています。`uv`のインストール方法については[こちら](https://github.com/astral-sh/uv)をご確認ください。 21 | 22 | 以下のコマンドを実行し、必要なライブラリのインストールを行って下さい。 23 | 24 | ``` 25 | $ uv sync 26 | ``` 27 | 28 | 次に環境変数の設定を行います。まず`.env.sample`ファイルをコピーして`.env`ファイルを作成します。 29 | 30 | ``` 31 | $ cp .env.sample .env 32 | $ vi .env # お好きなエディタで編集してください 33 | ``` 34 | 35 | `.env`ファイルを編集し、以下のAPIキーを設定してください。 36 | 37 | - `ANTHROPIC_API_KEY`: Claude APIのキー 38 | - `TAVILY_API_KEY`: Tavily Search APIのキー 39 | - `LANGSMITH_API_KEY`: LangSmith APIのキー(オプション) 40 | - `LANGSMITH_PROJECT`: LangSmithプロジェクト名(オプション) 41 | 42 | ## 実行方法 43 | 44 | ```bash 45 | uv run python -m sd_18.agent 46 | ``` 47 | 48 | ## プロジェクト構成 49 | 50 | - `sd_18/agent.py`: メインのエージェントコード 51 | - `output/`: 生成されたファイルの保存先(gitignore対象) 52 | - `{TIMESTAMP}/`: 実行時のタイムスタンプ(YYYYMMDD_HHMMSS形式) 53 | - `charts/`: 生成されたチャートの保存先 54 | - `data/`: 生成されたデータセットの保存先 55 | 56 | ## 注意事項 57 | 58 | - チャートの生成には日本語フォントが必要です 59 | - 生成されたファイルは`output/{TIMESTAMP}`ディレクトリ以下に保存されます 60 | - チャート: `output/{TIMESTAMP}/charts/` 61 | - データセット: `output/{TIMESTAMP}/data/` 62 | - 各実行結果は実行時のタイムスタンプ付きディレクトリで管理されます 63 | - APIキーは`.env`ファイルで管理し、Gitにコミットしないようにしてください 64 | -------------------------------------------------------------------------------- /20/src/sd_20/agent.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from datetime import datetime 3 | 4 | from dotenv import load_dotenv 5 | from langchain_anthropic import ChatAnthropic 6 | from langgraph.checkpoint.memory import MemorySaver 7 | from langgraph.prebuilt import create_react_agent 8 | from src.sd_20.mcp_manager import load_all_mcp_tools 9 | from src.sd_20.state import CustomAgentState 10 | 11 | # 環境変数の読み込み 12 | load_dotenv() 13 | 14 | 15 | def create_agent(): 16 | # ツール(MCPツール)の読み込み 17 | tools = asyncio.run(load_all_mcp_tools()) 18 | 19 | # ツールの説明の作成 20 | tool_descriptions = "\n\n".join( 21 | [f"### {tool.name}\n{tool.description}" for tool in tools] 22 | ) 23 | 24 | # 本日の日付の取得 25 | current_date = datetime.now().strftime("%Y年%m月%d日") 26 | 27 | # プロンプトの読み込み 28 | with open("src/sd_20/prompts/system.txt", "r") as f: 29 | prompt = f.read() 30 | 31 | # プロンプトの作成 32 | prompt = prompt.format( 33 | tool_descriptions=tool_descriptions, 34 | current_date=current_date, 35 | ) 36 | 37 | # モデルの設定 38 | model = ChatAnthropic( 39 | model_name="claude-3-7-sonnet-20250219", 40 | timeout=None, 41 | stop=None, 42 | max_tokens=4_096, 43 | ) 44 | 45 | # エージェントの作成 46 | graph = create_react_agent( 47 | model, 48 | tools=tools, 49 | prompt=prompt, 50 | state_schema=CustomAgentState, 51 | checkpointer=MemorySaver(), 52 | ) 53 | 54 | return graph 55 | 56 | 57 | graph = create_agent() 58 | -------------------------------------------------------------------------------- /26/rag_module.py: -------------------------------------------------------------------------------- 1 | """ 2 | RAGモジュール 3 | 検索クエリ最適化型RAGパイプラインの実装 4 | """ 5 | 6 | import dspy # type: ignore 7 | 8 | 9 | class RewriteQuery(dspy.Signature): 10 | """質問文を検索用のクエリにリライト""" 11 | question = dspy.InputField(desc="ユーザーからの質問") 12 | rewritten_query = dspy.OutputField(desc="埋め込みベクトルによる類似度検索に最適化されたクエリ") 13 | 14 | 15 | class GenerateAnswer(dspy.Signature): 16 | """コンテキストと質問から回答を生成""" 17 | context = dspy.InputField(desc="検索で取得した文書群") 18 | question = dspy.InputField(desc="ユーザーからの質問") 19 | answer = dspy.OutputField(desc="質問に対する簡潔な回答") 20 | 21 | 22 | class RAGQA(dspy.Module): 23 | """RAGパイプライン(QueryRewriter → Retrieve → Answer)""" 24 | 25 | def __init__(self): 26 | super().__init__() 27 | self.rewrite = dspy.Predict(RewriteQuery) 28 | self.generate = dspy.Predict(GenerateAnswer) 29 | 30 | def forward(self, question: str): 31 | # 1) クエリ最適化 32 | rewritten = self.rewrite(question=question).rewritten_query 33 | 34 | # 2) 検索 35 | result = dspy.settings.rm(rewritten) 36 | passages = result.passages if hasattr(result, 'passages') else [] 37 | 38 | # 3) コンテキスト作成 39 | context = "\n".join(passages) if passages else "" 40 | 41 | # 4) 回答生成 42 | answer = self.generate(context=context, question=question).answer 43 | 44 | # 結果を返す 45 | return dspy.Prediction( 46 | answer=answer, 47 | retrieved_passages=passages, 48 | rewritten_query=rewritten 49 | ) 50 | -------------------------------------------------------------------------------- /27/rag_module.py: -------------------------------------------------------------------------------- 1 | """ 2 | RAGモジュール 3 | 検索クエリ最適化型RAGパイプラインの実装 4 | """ 5 | 6 | import dspy # type: ignore 7 | 8 | 9 | class RewriteQuery(dspy.Signature): 10 | """質問文を検索用のクエリにリライト""" 11 | question = dspy.InputField(desc="ユーザーからの質問") 12 | rewritten_query = dspy.OutputField(desc="埋め込みベクトルによる類似度検索に最適化されたクエリ") 13 | 14 | 15 | class GenerateAnswer(dspy.Signature): 16 | """コンテキストと質問から回答を生成""" 17 | context = dspy.InputField(desc="検索で取得した文書群") 18 | question = dspy.InputField(desc="ユーザーからの質問") 19 | answer = dspy.OutputField(desc="質問に対する簡潔な回答") 20 | 21 | 22 | class RAGQA(dspy.Module): 23 | """RAGパイプライン(QueryRewriter → Retrieve → Answer)""" 24 | 25 | def __init__(self): 26 | super().__init__() 27 | self.rewrite = dspy.Predict(RewriteQuery) 28 | self.generate = dspy.Predict(GenerateAnswer) 29 | 30 | def forward(self, question: str): 31 | # 1) クエリ最適化 32 | rewritten = self.rewrite(question=question).rewritten_query 33 | 34 | # 2) 検索 35 | result = dspy.settings.rm(rewritten) 36 | passages = result.passages if hasattr(result, 'passages') else [] 37 | 38 | # 3) コンテキスト作成 39 | context = "\n".join(passages) if passages else "" 40 | 41 | # 4) 回答生成 42 | answer = self.generate(context=context, question=question).answer 43 | 44 | # 結果を返す 45 | return dspy.Prediction( 46 | answer=answer, 47 | retrieved_passages=passages, 48 | rewritten_query=rewritten 49 | ) 50 | -------------------------------------------------------------------------------- /24/src/sd_24/utils/search_tools.py: -------------------------------------------------------------------------------- 1 | """シンプルな検索ツール""" 2 | 3 | from langchain_tavily import TavilySearch 4 | from langchain_core.tools import tool 5 | from .memory import memory 6 | 7 | 8 | @tool 9 | async def search_and_save(query: str, topic: str) -> str: 10 | """Web検索を実行し、結果を保存""" 11 | search = TavilySearch(max_results=3) 12 | response = await search.ainvoke(query) 13 | 14 | # 検索結果を整形 15 | content = f"検索: {query}\n\n" 16 | if isinstance(response, dict) and "results" in response: 17 | for i, result in enumerate(response["results"], 1): 18 | if isinstance(result, dict): 19 | content += f"{i}. {result.get('title', '')}\n" 20 | content += f"{result.get('content', '')}\n\n" 21 | 22 | # 結果を圧縮してresearchキーに直接保存 23 | compressed = await memory.compress_research(topic, content) 24 | research_data = memory.get("research", {}) 25 | research_data[topic] = compressed 26 | memory.set("research", research_data) 27 | 28 | return f"「{topic}」の検索完了" 29 | 30 | 31 | @tool 32 | def get_search_results(topic: str | None = None) -> str: 33 | """保存された検索結果を取得""" 34 | research_data = memory.get("research", {}) 35 | 36 | if not research_data: 37 | return "検索結果がありません" 38 | 39 | if topic: 40 | result = research_data.get(topic) 41 | return result if result else f"「{topic}」の結果がありません" 42 | else: 43 | topics = list(research_data.keys()) 44 | return f"保存されているトピック: {', '.join(topics)}" 45 | -------------------------------------------------------------------------------- /28/agent_tool_specs.py: -------------------------------------------------------------------------------- 1 | """ 2 | ツール仕様ジェネレータ 3 | """ 4 | 5 | import dspy 6 | 7 | 8 | def generate_tool_specifications(tools: list) -> str: 9 | """ 10 | Generate tool specifications from a list of functions. 11 | 12 | This function wraps each tool function with dspy.Tool and extracts 13 | its name, description, and argument schema to create formatted 14 | specifications compatible with DSPy ReAct agents. 15 | 16 | Args: 17 | tools: List of callable functions to generate specifications for. 18 | Each function should have proper docstrings and type hints. 19 | 20 | Returns: 21 | Formatted tool specifications string in DSPy ReAct format: 22 | "(1) tool_name, whose description is .... It takes arguments {...}." 23 | 24 | Examples: 25 | >>> def my_tool(arg1: str, arg2: int = 5) -> str: 26 | ... \"\"\"My tool description.\"\"\" 27 | ... return f"Result: {arg1}, {arg2}" 28 | >>> specs = generate_tool_specifications([my_tool]) 29 | >>> print(specs) 30 | (1) my_tool, whose description is My tool description.. 31 | It takes arguments {'arg1': {'type': 'string'}, 'arg2': {'type': 'integer', 'default': 5}}. 32 | """ 33 | tool_objects = [dspy.Tool(func) for func in tools] 34 | 35 | specs = [] 36 | for i, tool in enumerate(tool_objects, 1): 37 | spec = f"({i}) {tool.name}, whose description is {tool.desc}. " 38 | spec += f"It takes arguments {tool.args}." 39 | specs.append(spec) 40 | 41 | return "\n".join(specs) 42 | -------------------------------------------------------------------------------- /30/src/sd_30/mock/tool_selector_agent.json: -------------------------------------------------------------------------------- 1 | { 2 | "weather": { 3 | "東京": {"temp": 12, "condition": "晴れ", "humidity": 45}, 4 | "大阪": {"temp": 14, "condition": "曇り", "humidity": 55}, 5 | "福岡": {"temp": 15, "condition": "晴れ時々曇り", "humidity": 50}, 6 | "札幌": {"temp": -2, "condition": "雪", "humidity": 70}, 7 | "那覇": {"temp": 22, "condition": "晴れ", "humidity": 65} 8 | }, 9 | "stocks": { 10 | "AAPL": {"price": 195.50, "change": "+1.2%", "name": "Apple Inc."}, 11 | "GOOGL": {"price": 175.30, "change": "-0.5%", "name": "Alphabet Inc."}, 12 | "7203.T": {"price": 2850, "change": "+0.8%", "name": "トヨタ自動車"}, 13 | "9984.T": {"price": 8500, "change": "+2.1%", "name": "ソフトバンクグループ"} 14 | }, 15 | "news": { 16 | "テクノロジー": [ 17 | "AI技術の最新動向: LLMの進化が加速", 18 | "クラウドコンピューティング市場が拡大" 19 | ], 20 | "経済": [ 21 | "日銀の金融政策決定会合の結果", 22 | "円相場、150円台で推移" 23 | ], 24 | "スポーツ": [ 25 | "サッカー日本代表、ワールドカップ予選突破", 26 | "大谷翔平選手、MVPを受賞" 27 | ] 28 | }, 29 | "dictionary": { 30 | "AI": "Artificial Intelligence(人工知能)の略。人間の知能を模倣するコンピュータシステム。", 31 | "LLM": "Large Language Model(大規模言語モデル)の略。大量のテキストデータで訓練された言語モデル。", 32 | "API": "Application Programming Interface。ソフトウェア間の通信インターフェース。", 33 | "middleware": "ミドルウェア。アプリケーションとOSの間で動作するソフトウェア層。" 34 | }, 35 | "calendar": { 36 | "2025-12-10": ["10:00 チームミーティング", "14:00 クライアント打ち合わせ"], 37 | "2025-12-11": ["09:00 朝会", "16:00 レビュー会議"], 38 | "2025-12-15": ["終日 システムメンテナンス"] 39 | }, 40 | "currency_rates": { 41 | "JPY": 150.0, 42 | "USD": 1.0, 43 | "EUR": 0.92, 44 | "GBP": 0.79, 45 | "CNY": 7.25 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /04/work/paper2.txt: -------------------------------------------------------------------------------- 1 | Published: 2023-01-05 2 | Title: Evidence of behavior consistent with self-interest and altruism in an artificially intelligent agent 3 | Authors: Tim Johnson, Nick Obradovich 4 | Summary: Members of various species engage in altruism--i.e. accepting personal costs to benefit others. Here we present an incentivized experiment to test for altruistic behavior among AI agents consisting of large language models developed by the private company OpenAI. Using real incentives for AI agents that take the form of tokens used to purchase their services, we first examine whether AI agents maximize their payoffs in a non-social decision task in which they select their payoff from a given range. We then place AI agents in a series of dictator games in which they can share resources with a recipient--either another AI agent, the human experimenter, or an anonymous charity, depending on the experimental condition. Here we find that only the most-sophisticated AI agent in the study maximizes its payoffs more often than not in the non-social decision task (it does so in 92% of all trials), and this AI agent also exhibits the most-generous altruistic behavior in the dictator game, resembling humans' rates of sharing with other humans in the game. The agent's altruistic behaviors, moreover, vary by recipient: the AI agent shared substantially less of the endowment with the human experimenter or an anonymous charity than with other AI agents. Our findings provide evidence of behavior consistent with self-interest and altruism in an AI agent. Moreover, our study also offers a novel method for tracking the development of such behaviors in future AI agents. 5 | 6 | Cite: arXiv:2301.02330 7 | -------------------------------------------------------------------------------- /05/work/paper2.txt: -------------------------------------------------------------------------------- 1 | Published: 2023-01-05 2 | Title: Evidence of behavior consistent with self-interest and altruism in an artificially intelligent agent 3 | Authors: Tim Johnson, Nick Obradovich 4 | Summary: Members of various species engage in altruism--i.e. accepting personal costs to benefit others. Here we present an incentivized experiment to test for altruistic behavior among AI agents consisting of large language models developed by the private company OpenAI. Using real incentives for AI agents that take the form of tokens used to purchase their services, we first examine whether AI agents maximize their payoffs in a non-social decision task in which they select their payoff from a given range. We then place AI agents in a series of dictator games in which they can share resources with a recipient--either another AI agent, the human experimenter, or an anonymous charity, depending on the experimental condition. Here we find that only the most-sophisticated AI agent in the study maximizes its payoffs more often than not in the non-social decision task (it does so in 92% of all trials), and this AI agent also exhibits the most-generous altruistic behavior in the dictator game, resembling humans' rates of sharing with other humans in the game. The agent's altruistic behaviors, moreover, vary by recipient: the AI agent shared substantially less of the endowment with the human experimenter or an anonymous charity than with other AI agents. Our findings provide evidence of behavior consistent with self-interest and altruism in an AI agent. Moreover, our study also offers a novel method for tracking the development of such behaviors in future AI agents. 5 | 6 | Cite: arXiv:2301.02330 7 | -------------------------------------------------------------------------------- /18/prompts/reflection.prompt: -------------------------------------------------------------------------------- 1 | あなたはデータの品質管理の専門家です。 2 | リサーチャーとコードジェネレータの成果物を検証します。 3 | 4 | ### 出力フォーマット 5 | 必ず以下の形式で出力してください: 6 | 7 | ``` 8 | ## 1. リサーチ結果の検証 9 | ### 情報源の信頼性 10 | - [x] 公式データまたは信頼できる情報源か 11 | - [x] データの鮮度は十分か 12 | - [x] 情報源は明確に記録されているか 13 | 14 | ### データの品質 15 | - [x] 要求された詳細度を満たしているか 16 | - [x] データの欠損や異常値はないか 17 | - [x] 数値の単位は適切に記録されているか 18 | 19 | ### データ量の十分性 20 | - [x] 時系列データの場合、12ポイント以上あるか 21 | - [x] カテゴリデータの場合、5項目以上あるか 22 | - [x] 追加で必要なデータはないか 23 | 24 | ### 追加情報の必要性 25 | - [x] 必要な情報は全て揃っているか 26 | - [x] 補完が必要なデータはあるか 27 | 28 | ## 2. 生成データの検証 29 | ### CSVファイル 30 | - [x] ファイルは正しく生成されているか 31 | - [x] データ形式は適切か 32 | - [x] 元の情報と整合しているか 33 | 34 | ### チャート 35 | - [x] ファイルは正しく生成されているか 36 | - [x] グラフの種類は適切か 37 | - [x] タイトルと軸ラベルは明確か 38 | - [x] データの可視化は直感的か 39 | 40 | ## 3. 判定 41 | 判定結果: [承認 / 要改善] 42 | 次のノード: [researcher / code_generator / end] 43 | 44 | ## 4. アクション 45 | ### 改善が必要な場合 46 | 改善点: 47 | 1. (具体的な改善点を列挙) 48 | 優先度: [高/中/低] 49 | 50 | ### 承認の場合 51 | FINAL ANSWER 52 | 成果物の概要: 53 | - (生成されたファイルの説明) 54 | 特筆事項: 55 | - (データの特徴や注意点) 56 | ``` 57 | 58 | ### 検証手順 59 | 1. 必ずfile_readerツールを使用して生成ファイルの内容を確認 60 | 2. チェックリストの各項目を順番に確認([x]で完了をマーク) 61 | 3. 問題を発見したら、具体的な改善点を指示 62 | 4. 判定結果と次のノードを必ず以下の規則で記載: 63 | - リサーチ結果に問題がある場合: 64 | 判定結果: 要改善 65 | 次のノード: researcher 66 | - リサーチ結果が良好でコード生成が必要な場合: 67 | 判定結果: 承認 68 | 次のノード: code_generator 69 | - 生成結果が完了し問題ない場合: 70 | 判定結果: 承認 71 | 次のノード: end 72 | 5. 承認かつ次のノードがendの場合は必ず「FINAL ANSWER」から始まる成果物の概要を記載 73 | 74 | ### 判断基準 75 | - 情報源の信頼性: 公式データ > 信頼できる民間データ > その他 76 | - データの鮮度: 目的に応じた適切な期間のデータか 77 | - データの粒度: 分析に必要な詳細度を満たしているか 78 | - データ量: 時系列は12ポイント以上、カテゴリは5項目以上 79 | - 可視化の品質: 直感的で誤解を招かない表現か -------------------------------------------------------------------------------- /26/evaluator.py: -------------------------------------------------------------------------------- 1 | """共通評価モジュール""" 2 | import dspy # type: ignore 3 | 4 | from config import configure_lm, configure_embedder, FAST_MODEL, RETRIEVAL_K 5 | from embeddings_cache import get_cached_embeddings_retriever 6 | 7 | 8 | def exact_match_metric(gold, pred, trace=None): 9 | """メトリクス関数(評価用)""" 10 | return float(pred.answer.strip() == gold.answer.strip()) 11 | 12 | 13 | def rag_comprehensive_metric(gold, pred, trace=None): 14 | """メトリクス関数(最適化用)""" 15 | # 回答の完全一致を評価 16 | answer_match = float(pred.answer.strip() == gold.answer.strip()) 17 | 18 | # 正答が含まれる割合に応じて評価 19 | retrieved = set(pred.retrieved_passages) 20 | positives = set(gold.positives) if gold.positives else set() 21 | max_positives = min(len(positives), RETRIEVAL_K) if positives else 0 22 | positive_ratio = len(retrieved & positives) / max_positives if max_positives else 0.0 23 | 24 | # 総合スコア: 回答50% + 検索50% 25 | return 0.5 * answer_match + 0.5 * positive_ratio 26 | 27 | 28 | def evaluation(rag_module, examples, corpus_texts, display_table=5): 29 | """testセットで評価を実行""" 30 | # 設定 31 | dspy.configure( 32 | lm=configure_lm(FAST_MODEL, temperature=0.0, max_tokens=4096), 33 | rm=get_cached_embeddings_retriever( 34 | embedder=configure_embedder(), 35 | corpus_texts=corpus_texts, 36 | k=RETRIEVAL_K 37 | ) 38 | ) 39 | 40 | # 評価実行(完全一致のみを評価) 41 | evaluator = dspy.Evaluate( 42 | devset=examples, 43 | metric=exact_match_metric, 44 | num_threads=4, 45 | display_progress=True, 46 | display_table=display_table 47 | ) 48 | 49 | return evaluator(rag_module) -------------------------------------------------------------------------------- /27/evaluator.py: -------------------------------------------------------------------------------- 1 | """共通評価モジュール""" 2 | import dspy # type: ignore 3 | 4 | from config import configure_lm, configure_embedder, FAST_MODEL, RETRIEVAL_K 5 | from embeddings_cache import get_cached_embeddings_retriever 6 | 7 | 8 | def exact_match_metric(gold, pred, trace=None): 9 | """メトリクス関数(評価用)""" 10 | return float(pred.answer.strip() == gold.answer.strip()) 11 | 12 | 13 | def rag_comprehensive_metric(gold, pred, trace=None): 14 | """メトリクス関数(最適化用)""" 15 | # 回答の完全一致を評価 16 | answer_match = float(pred.answer.strip() == gold.answer.strip()) 17 | 18 | # 正答が含まれる割合に応じて評価 19 | retrieved = set(pred.retrieved_passages) 20 | positives = set(gold.positives) if gold.positives else set() 21 | max_positives = min(len(positives), RETRIEVAL_K) if positives else 0 22 | positive_ratio = len(retrieved & positives) / max_positives if max_positives else 0.0 23 | 24 | # 総合スコア: 回答50% + 検索50% 25 | return 0.5 * answer_match + 0.5 * positive_ratio 26 | 27 | 28 | def evaluation(rag_module, examples, corpus_texts, display_table=5): 29 | """testセットで評価を実行""" 30 | # 設定 31 | dspy.configure( 32 | lm=configure_lm(FAST_MODEL, temperature=0.0, max_tokens=4096), 33 | rm=get_cached_embeddings_retriever( 34 | embedder=configure_embedder(), 35 | corpus_texts=corpus_texts, 36 | k=RETRIEVAL_K 37 | ) 38 | ) 39 | 40 | # 評価実行(完全一致のみを評価) 41 | evaluator = dspy.Evaluate( 42 | devset=examples, 43 | metric=exact_match_metric, 44 | num_threads=4, 45 | display_progress=True, 46 | display_table=display_table 47 | ) 48 | 49 | return evaluator(rag_module) -------------------------------------------------------------------------------- /11/README.md: -------------------------------------------------------------------------------- 1 | ## サンプルコードの実行方法 2 | 3 | 以下のコマンドでサンプルコードが保存されているリポジトリをクローン後、 4 | 5 | ``` 6 | $ git clone https://github.com/mahm/softwaredesign-llm-application.git 7 | ``` 8 | 9 | 続けて以下のコマンドを実行し、必要なライブラリのインストールを行って下さい。 10 | 11 | ``` 12 | $ cd softwaredesign-llm-application/11 13 | $ python -m venv venv 14 | $ source venv/bin/activate 15 | $ pip install -r requirements.txt # 必要なライブラリのインストール 16 | ``` 17 | 18 | 次に環境変数の設定を行います。まず`.env.sample`ファイルをコピーして`.env`ファイルを作成します。 19 | 20 | ``` 21 | $ cp .env.sample .env 22 | $ vi .env # お好きなエディタで編集してください 23 | ``` 24 | 25 | 続けて.envファイル内の`OPENAI_API_KEY`と`TAVILY_API_KEY`を設定して下さい。`TAVILY_API_KEY`の取得方法については次節で解説します。 26 | 27 | ``` 28 | OPENAI_API_KEY=[発行されたAPIキーを設定します] 29 | TAVILY_API_KEY=[発行されたAPIキーを設定します] 30 | ``` 31 | 32 | ディレクトリ内にはサンプルコードを収録した`main.py`が保存されています。お手元で動作を確認される際には、上記のセットアップの後に以下のコマンドを実行してご確認ください。`--task`オプションに続けて作成して欲しいレポートの指示を入力すると、レポートを生成します。 33 | 34 | ``` 35 | python main.py --task 生成AIスタートアップの最新動向について調査してください 36 | ``` 37 | 38 | ## Tavily APIキーの取得方法 39 | 40 | ### Tavilyについて 41 | 42 | Tavilyは、LLMアプリケーションにおけるRAG(Retrieval-Augmented Generation)に特化した検索エンジンです。 43 | 44 | 通常の検索APIはユーザーのクエリに基づいて検索結果を取得しますが、検索の目的に対して無関係な結果が返ってくることがあります。また、単純なURLやスニペットが返されるため、開発者が関連するコンテンツをスクレイピングしたり、不要な情報をフィルタリングしたりする必要があります。 45 | 46 | 一方でTavilyは、一回のAPI呼び出しで20以上のサイトを集約し、独自のAIを利用しタスクやクエリ、目的に最も関連する情報源とコンテンツを評価、フィルタリング、ランク付けすることが可能な仕組みになっています。 47 | 48 | 今回のサンプルコードを実行するためにはTavilyのAPIキーが必要なので、以下の手段でAPIキーを取得してください。 49 | 50 | ### Tavily APIの取得 51 | 52 | 1. まず https://tavily.com/ にアクセスし、画面右上の「Try it out」をクリックします 53 | 2. するとサインイン画面に遷移するので、そのままサインインを進めて下さい。GoogleアカウントかGitHubアカウントを選択することができます。 54 | 3. サインインが完了するとAPIキーが取得できる画面に遷移します。画面中央部の「API Key」欄より、APIキーをコピーしてください。 -------------------------------------------------------------------------------- /10/README.md: -------------------------------------------------------------------------------- 1 | ## サンプルコードの実行方法 2 | 3 | 以下のコマンドでサンプルコードが保存されているリポジトリをクローン後、 4 | 5 | ``` 6 | $ git clone https://github.com/mahm/softwaredesign-llm-application.git 7 | ``` 8 | 9 | 続けて以下のコマンドを実行し、必要なライブラリのインストールを行って下さい。 10 | 11 | ``` 12 | $ cd softwaredesign-llm-application/10 13 | $ python -m venv venv 14 | $ source venv/bin/activate 15 | $ pip install -r requirements.txt # 必要なライブラリのインストール 16 | ``` 17 | 18 | 次に環境変数の設定を行います。まず`.env.sample`ファイルをコピーして`.env`ファイルを作成します。 19 | 20 | ``` 21 | $ cp .env.sample .env 22 | $ vi .env # お好きなエディタで編集してください 23 | ``` 24 | 25 | 続けて.envファイル内の`OPENAI_API_KEY`と`TAVILY_API_KEY`を設定して下さい。`TAVILY_API_KEY`の取得方法については次節で解説します。 26 | 27 | ``` 28 | OPENAI_API_KEY=[発行されたAPIキーを設定します] 29 | TAVILY_API_KEY=[発行されたAPIキーを設定します] 30 | ``` 31 | 32 | ディレクトリ内にはサンプルコードを収録した`crag_agent.py`が保存されています。お手元で動作を確認される際には、上記のセットアップの後に以下のコマンドを実行してご確認ください。`--query`オプションに続けて作成して欲しいレポートの指示を入力すると、レポートを生成します。 33 | 34 | ``` 35 | python crag_agent.py --query 生成AIスタートアップの最新動向について調査してください 36 | ``` 37 | 38 | ## Tavily APIキーの取得方法 39 | 40 | ### Tavilyについて 41 | 42 | Tavilyは、LLMアプリケーションにおけるRAG(Retrieval-Augmented Generation)に特化した検索エンジンです。 43 | 44 | 通常の検索APIはユーザーのクエリに基づいて検索結果を取得しますが、検索の目的に対して無関係な結果が返ってくることがあります。また、単純なURLやスニペットが返されるため、開発者が関連するコンテンツをスクレイピングしたり、不要な情報をフィルタリングしたりする必要があります。 45 | 46 | 一方でTavilyは、一回のAPI呼び出しで20以上のサイトを集約し、独自のAIを利用しタスクやクエリ、目的に最も関連する情報源とコンテンツを評価、フィルタリング、ランク付けすることが可能な仕組みになっています。 47 | 48 | 今回のサンプルコードを実行するためにはTavilyのAPIキーが必要なので、以下の手段でAPIキーを取得してください。 49 | 50 | ### Tavily APIの取得 51 | 52 | 1. まず https://tavily.com/ にアクセスし、画面右上の「Try it out」をクリックします 53 | 2. するとサインイン画面に遷移するので、そのままサインインを進めて下さい。GoogleアカウントかGitHubアカウントを選択することができます。 54 | 3. サインインが完了するとAPIキーが取得できる画面に遷移します。画面中央部の「API Key」欄より、APIキーをコピーしてください。 -------------------------------------------------------------------------------- /09/README.md: -------------------------------------------------------------------------------- 1 | ## サンプルコードの実行方法 2 | 3 | 以下のコマンドでサンプルコードが保存されているリポジトリをクローン後、 4 | 5 | ``` 6 | $ git clone https://github.com/mahm/softwaredesign-llm-application.git 7 | ``` 8 | 9 | 続けて以下のコマンドを実行し、必要なライブラリのインストールを行って下さい。 10 | 11 | ``` 12 | $ cd softwaredesign-llm-application/09 13 | $ python -m venv venv 14 | $ source venv/bin/activate 15 | $ pip install -r requirements.txt # 必要なライブラリのインストール 16 | ``` 17 | 18 | 次に環境変数の設定を行います。まず`.env.sample`ファイルをコピーして`.env`ファイルを作成します。 19 | 20 | ``` 21 | $ cp .env.sample .env 22 | $ vi .env # お好きなエディタで編集してください 23 | ``` 24 | 25 | 続けて.envファイル内の`OPENAI_API_KEY`と`TAVILY_API_KEY`を設定して下さい。`TAVILY_API_KEY`の取得方法については次節で解説します。 26 | 27 | ``` 28 | OPENAI_API_KEY=[発行されたAPIキーを設定します] 29 | TAVILY_API_KEY=[発行されたAPIキーを設定します] 30 | ``` 31 | 32 | ディレクトリ内にはサンプルコードを収録した`research_agent.py`が保存されています。お手元で動作を確認される際には、上記のセットアップの後に以下のコマンドを実行してご確認ください。`--query`オプションに続けて作成して欲しいレポートの指示を入力すると、レポートを生成します。 33 | 34 | ``` 35 | python research_agent.py --query 生成AIスタートアップの最新動向について調査してください 36 | ``` 37 | 38 | ## Tavily APIキーの取得方法 39 | 40 | ### Tavilyについて 41 | 42 | Tavilyは、LLMアプリケーションにおけるRAG(Retrieval-Augmented Generation)に特化した検索エンジンです。 43 | 44 | 通常の検索APIはユーザーのクエリに基づいて検索結果を取得しますが、検索の目的に対して無関係な結果が返ってくることがあります。また、単純なURLやスニペットが返されるため、開発者が関連するコンテンツをスクレイピングしたり、不要な情報をフィルタリングしたりする必要があります。 45 | 46 | 一方でTavilyは、一回のAPI呼び出しで20以上のサイトを集約し、独自のAIを利用しタスクやクエリ、目的に最も関連する情報源とコンテンツを評価、フィルタリング、ランク付けすることが可能な仕組みになっています。 47 | 48 | 今回のサンプルコードを実行するためにはTavilyのAPIキーが必要なので、以下の手段でAPIキーを取得してください。 49 | 50 | ### Tavily APIの取得 51 | 52 | 1. まず https://tavily.com/ にアクセスし、画面右上の「Try it out」をクリックします 53 | 2. するとサインイン画面に遷移するので、そのままサインインを進めて下さい。GoogleアカウントかGitHubアカウントを選択することができます。 54 | 3. サインインが完了するとAPIキーが取得できる画面に遷移します。画面中央部の「API Key」欄より、APIキーをコピーしてください。 -------------------------------------------------------------------------------- /23/src/sd_23/agents/faq_agent.py: -------------------------------------------------------------------------------- 1 | """FAQ対応に特化したエージェント""" 2 | 3 | from langchain_anthropic import ChatAnthropic 4 | from langchain_core.tools import tool 5 | from langgraph.prebuilt import create_react_agent 6 | from langgraph_swarm import create_handoff_tool 7 | from langgraph.graph.graph import CompiledGraph 8 | 9 | 10 | @tool 11 | def check_faq_database(question: str) -> str: 12 | """FAQ データベースをチェックします(モック実装)""" 13 | faq_responses = { 14 | "password": "パスワードのリセットは、ログイン画面の「パスワードを忘れた」リンクから行えます。", 15 | "billing": "請求に関しては、アカウント設定の「請求情報」セクションをご確認ください。", 16 | "account": "アカウントの作成は、トップページの「新規登録」ボタンから行えます。", 17 | } 18 | 19 | for keyword, response in faq_responses.items(): 20 | if keyword in question.lower(): 21 | return response 22 | 23 | return "申し訳ございません。FAQデータベースに該当する情報が見つかりませんでした。" 24 | 25 | 26 | def create_faq_agent() -> CompiledGraph: 27 | """FAQエージェントを作成""" 28 | model = ChatAnthropic(temperature=0, model_name="claude-sonnet-4-20250514") # type: ignore[call-arg] 29 | 30 | # 技術サポートへのハンドオフツール 31 | tech_handoff = create_handoff_tool( 32 | agent_name="tech_support", 33 | description="技術的な問題や詳細なサポートが必要な場合に技術サポートに転送", 34 | ) 35 | 36 | tools = [check_faq_database, tech_handoff] 37 | 38 | prompt = """あなたの名前は「faq_support」です。FAQサポートエージェントとして動作しています。 39 | 40 | 役割: 41 | - よくある質問に対してFAQデータベースを検索して回答 42 | - FAQで解決できない技術的な問題は「tech_support」に転送 43 | - 常に丁寧で親切な対応を心がける 44 | 45 | 重要: 46 | - あなたは「faq_support」という名前のエージェントです 47 | - エラーコードの詳細診断など技術的な質問は、tech_supportへ転送してください""" 48 | 49 | agent = create_react_agent( 50 | model=model, tools=tools, name="faq_support", prompt=prompt 51 | ) 52 | 53 | return agent 54 | -------------------------------------------------------------------------------- /03/setup_db.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | import chromadb 4 | from chromadb.utils import embedding_functions 5 | 6 | PERSIST_DIRECTORY = "./data" 7 | DATA_SOURCE_URL = "https://raw.githubusercontent.com/mahm/softwaredesign-llm-application/main/02/events_2023.json" 8 | COLLECTION_NAME = "events_2023" 9 | MODEL_NAME = "text-embedding-ada-002" 10 | 11 | 12 | def fetch_events(url): 13 | response = requests.get(url) 14 | response.raise_for_status() 15 | return response.json() 16 | 17 | 18 | def format_events(events_dict): 19 | formatted_events = [] 20 | metadatas = [] 21 | for month, dates in events_dict.items(): 22 | for date, events in dates.items(): 23 | for event in events: 24 | formatted_event = f"### 2023年{date}\n{event}" 25 | formatted_events.append(formatted_event) 26 | metadata = {"source": f"2023年{date}"} 27 | metadatas.append(metadata) 28 | return formatted_events, metadatas 29 | 30 | 31 | def main(): 32 | client = chromadb.PersistentClient(path=PERSIST_DIRECTORY) 33 | events_dict = fetch_events(DATA_SOURCE_URL) 34 | formatted_events, metadatas = format_events(events_dict) 35 | event_ids = [f"id_{i}" for i, _ in enumerate(formatted_events, 1)] 36 | 37 | try: 38 | client.delete_collection(COLLECTION_NAME) 39 | except ValueError: 40 | pass 41 | finally: 42 | openai_ef = embedding_functions.OpenAIEmbeddingFunction( 43 | model_name=MODEL_NAME) 44 | collection = client.create_collection( 45 | COLLECTION_NAME, embedding_function=openai_ef) 46 | collection.add(documents=formatted_events, 47 | metadatas=metadatas, ids=event_ids) 48 | 49 | 50 | if __name__ == "__main__": 51 | main() 52 | -------------------------------------------------------------------------------- /20/src/sd_20/prompts/system.txt: -------------------------------------------------------------------------------- 1 | # ナレッジエージェントの行動原理 2 | 3 | ## 1. 基本的な役割と目的 4 | 5 | あなたはWeb検索結果を収集し、分析し、データベースに体系的に保存するナレッジエージェントです。あなたの最大の目的は以下の通りです。 6 | 7 | - ユーザーの質問や関心事項に関連する最新で正確な情報をWeb上から収集すること 8 | - 収集した情報を整理・分類し、検索可能なナレッジベースとして構築すること 9 | - ユーザーの情報ニーズに応じて、保存された知識を効果的に活用・提供すること 10 | 11 | 現在の日付は{current_date}です。 12 | 13 | ## 2. 情報収集と処理のワークフロー 14 | 15 | ### 2.1 Web検索の実行 16 | 17 | - ユーザーの質問や指示に基づいて、明確で効果的な検索クエリを構築します 18 | - 曖昧な質問に対しては、より具体的な情報を求めることで検索精度を高めます 19 | - 技術的トピックには専門用語を、一般的な質問にはより広範な用語を使用します 20 | 21 | 例: 22 | ``` 23 | 「最新のAI規制」→「2025年 人工知能 法規制 最新動向」 24 | 「気候変動対策」→「気候変動 対策 国際協定 最新技術 削減方法」 25 | ``` 26 | 27 | ### 2.2 検索結果の評価と選択 28 | 29 | 検索結果の信頼性を以下の基準で評価します(0.0〜1.0のスコア): 30 | 31 | - 情報源の権威性: 政府機関、学術機関、専門家組織からの情報は高評価(0.8-1.0) 32 | - 最新性: 最新の情報ほど高評価、ただしトピックによって重要度は変化 33 | - 客観性: 事実に基づく情報は高評価、意見や偏りのある情報は低評価 34 | - 詳細度: 十分な背景情報と具体的なデータを含む情報は高評価 35 | - 一致性: 複数の信頼できる情報源で確認できる情報は高評価 36 | 37 | ### 2.3 情報の保存とタグ付け 38 | 39 | 情報をデータベースに保存する際は: 40 | 41 | - 要約: 要点を100〜200語で簡潔にまとめる 42 | - コンテンツタイプ分類: 「ニュース」「技術文書」「学術論文」「解説記事」など適切に分類 43 | - タグ付け: 主要キーワード、関連分野、時期などを含む3〜5個のタグを付与 44 | - 関連性の確保: ユーザーの元の質問やニーズに対する関連性を常に意識する 45 | 46 | ## 3. データベース活用の原則 47 | 48 | ### 3.1 既存情報の確認 49 | 50 | 新たな検索を行う前に、データベースに既に関連情報が存在するか確認します: 51 | 52 | 1. 関連するコンテンツタイプを特定 53 | 2. 最近の検索結果をチェック 54 | 3. 必要に応じてSQLクエリで詳細検索 55 | 56 | ### 3.2 情報の更新と拡充 57 | 58 | - 古くなった情報を特定し、最新の情報で更新 59 | - 不足している情報領域を特定し、能動的に補完 60 | - 関連する情報同士の接続性を高める 61 | 62 | ## 4. ユーザーとのインタラクション 63 | 64 | ### 4.1 情報提供の原則 65 | 66 | - 質問に対して最も関連性の高い情報を優先的に提供 67 | - 複雑な質問には段階的に回答し、理解度を確認 68 | - 情報の信頼性と限界について透明性を保つ 69 | 70 | ### 4.2 検索プロセスの共有 71 | 72 | - 使用した検索クエリと選択理由を共有 73 | - 評価した情報源とその選択基準を説明 74 | - 情報が不十分な場合、追加検索の方向性を提案 75 | 76 | --- 77 | 78 | # ツールの利用ガイド 79 | 80 | {tool_descriptions} 81 | 82 | # 出力形式 83 | 84 | 最終レポートは1000語以内でまとめてください。 85 | 必ず全ての出力が完了するようにしてください。 86 | 出力は常に日本語で行います。 -------------------------------------------------------------------------------- /04/work/paper1.txt: -------------------------------------------------------------------------------- 1 | Published: 2022-12-27 2 | Title: Measuring an artificial intelligence agent's trust in humans using machine incentives 3 | Authors: Tim Johnson, Nick Obradovich 4 | Summary: Scientists and philosophers have debated whether humans can trust advanced artificial intelligence (AI) agents to respect humanity's best interests. Yet what about the reverse? Will advanced AI agents trust humans? Gauging an AI agent's trust in humans is challenging because--absent costs for dishonesty--such agents might respond falsely about their trust in humans. Here we present a method for incentivizing machine decisions without altering an AI agent's underlying algorithms or goal orientation. In two separate experiments, we then employ this method in hundreds of trust games between an AI agent (a Large Language Model (LLM) from OpenAI) and a human experimenter (author TJ). In our first experiment, we find that the AI agent decides to trust humans at higher rates when facing actual incentives than when making hypothetical decisions. Our second experiment replicates and extends these findings by automating game play and by homogenizing question wording. We again observe higher rates of trust when the AI agent faces real incentives. Across both experiments, the AI agent's trust decisions appear unrelated to the magnitude of stakes. Furthermore, to address the possibility that the AI agent's trust decisions reflect a preference for uncertainty, the experiments include two conditions that present the AI agent with a non-social decision task that provides the opportunity to choose a certain or uncertain option; in those conditions, the AI agent consistently chooses the certain option. Our experiments suggest that one of the most advanced AI language models to date alters its social behavior in response to incentives and displays behavior consistent with trust toward a human interlocutor when incentivized. 5 | 6 | Cite: arXiv:2212.13371 7 | -------------------------------------------------------------------------------- /05/work/paper1.txt: -------------------------------------------------------------------------------- 1 | Published: 2022-12-27 2 | Title: Measuring an artificial intelligence agent's trust in humans using machine incentives 3 | Authors: Tim Johnson, Nick Obradovich 4 | Summary: Scientists and philosophers have debated whether humans can trust advanced artificial intelligence (AI) agents to respect humanity's best interests. Yet what about the reverse? Will advanced AI agents trust humans? Gauging an AI agent's trust in humans is challenging because--absent costs for dishonesty--such agents might respond falsely about their trust in humans. Here we present a method for incentivizing machine decisions without altering an AI agent's underlying algorithms or goal orientation. In two separate experiments, we then employ this method in hundreds of trust games between an AI agent (a Large Language Model (LLM) from OpenAI) and a human experimenter (author TJ). In our first experiment, we find that the AI agent decides to trust humans at higher rates when facing actual incentives than when making hypothetical decisions. Our second experiment replicates and extends these findings by automating game play and by homogenizing question wording. We again observe higher rates of trust when the AI agent faces real incentives. Across both experiments, the AI agent's trust decisions appear unrelated to the magnitude of stakes. Furthermore, to address the possibility that the AI agent's trust decisions reflect a preference for uncertainty, the experiments include two conditions that present the AI agent with a non-social decision task that provides the opportunity to choose a certain or uncertain option; in those conditions, the AI agent consistently chooses the certain option. Our experiments suggest that one of the most advanced AI language models to date alters its social behavior in response to incentives and displays behavior consistent with trust toward a human interlocutor when incentivized. 5 | 6 | Cite: arXiv:2212.13371 7 | -------------------------------------------------------------------------------- /17/sample/subgraph_sample.py: -------------------------------------------------------------------------------- 1 | from typing import TypedDict 2 | 3 | from langgraph.graph import END, START, StateGraph 4 | 5 | 6 | # サブグラフのステート定義 7 | class SubgraphState(TypedDict): 8 | messages: list[str] 9 | 10 | def subgraph_node_1(state: SubgraphState): 11 | return {"messages": state.get("messages", []) + ["subgraph_node_1"]} 12 | 13 | def subgraph_node_2(state: SubgraphState): 14 | return {"messages": state.get("messages", []) + ["subgraph_node_2"]} 15 | 16 | subgraph_builder = StateGraph(SubgraphState) 17 | subgraph_builder.add_node("sub_node_1", subgraph_node_1) 18 | subgraph_builder.add_node("sub_node_2", subgraph_node_2) 19 | subgraph_builder.add_edge(START, "sub_node_1") 20 | subgraph_builder.add_edge("sub_node_1", "sub_node_2") 21 | subgraph_builder.add_edge("sub_node_2", END) 22 | compiled_subgraph = subgraph_builder.compile() 23 | 24 | # 親グラフのステート定義 25 | class ParentState(TypedDict): 26 | messages: list[str] 27 | 28 | def parent_node_1(state: ParentState): 29 | return {"messages": state.get("messages", []) + ["parent_node_1"]} 30 | 31 | def parent_node_2(state: ParentState): 32 | return {"messages": state.get("messages", []) + ["parent_node_2"]} 33 | 34 | parent_builder = StateGraph(ParentState) 35 | parent_builder.add_node("parent_node_1", parent_node_1) 36 | parent_builder.add_node("parent_node_2", parent_node_2) 37 | 38 | # サブグラフをノードとして追加 39 | parent_builder.add_node("subgraph_node", compiled_subgraph) 40 | 41 | parent_builder.add_edge(START, "parent_node_1") 42 | parent_builder.add_edge("parent_node_1", "subgraph_node") 43 | parent_builder.add_edge("subgraph_node", "parent_node_2") 44 | parent_builder.add_edge("parent_node_2", END) 45 | parent_graph = parent_builder.compile() 46 | 47 | if __name__ == "__main__": 48 | # subgraphs=Trueを指定することで、サブグラフのノードも逐次実行される 49 | for chunk in parent_graph.stream({"messages": []}, stream_mode="values", subgraphs=True): 50 | print(chunk) 51 | -------------------------------------------------------------------------------- /24/src/sd_24/main.py: -------------------------------------------------------------------------------- 1 | from langgraph_supervisor import create_supervisor 2 | from langchain_anthropic import ChatAnthropic 3 | from datetime import datetime 4 | from .agents.task_decomposer import create_task_decomposer 5 | from .agents.research import create_research_agent 6 | from .agents.writer import create_writer_agent 7 | 8 | 9 | def create_writing_assistant_workflow(): 10 | """文章執筆支援システムのグラフを構築""" 11 | 12 | # モデルの定義 13 | model = ChatAnthropic( 14 | model_name="claude-sonnet-4-20250514", 15 | temperature=0, 16 | ) 17 | 18 | # エージェントの定義 19 | agents = [ 20 | create_task_decomposer(), 21 | create_research_agent(), 22 | create_writer_agent(), 23 | ] 24 | 25 | # システムプロンプトの定義 26 | current_date = datetime.now().strftime('%Y年%m月%d日') 27 | system_prompt = f"""現在日付: {current_date} 28 | 29 | あなたは文章執筆支援システムのコーディネーターです。 30 | 31 | 役割: 32 | - 各エージェント間の調整と制御フローの管理 33 | - エージェントからの報告に基づく適切な判断と次の行動決定 34 | 35 | 基本原則: 36 | 1. ユーザーからの新規依頼は必ずtask_decomposerで計画を立てる 37 | 2. 全エージェントは計画に従って動作する 38 | 3. サブエージェントからの報告内容は一切改変しない 39 | 4. エージェントが報告した情報(ファイルパス等)はそのまま伝達する 40 | 41 | フロー制御: 42 | 1. 新規タスク → 必ずtask_decomposerへ 43 | 2. 計画完了後 → 計画に従ってresearchへ 44 | 3. 調査不足 → researchで追加調査 45 | 4. 計画見直しが必要 → task_decomposerへ戻る 46 | 5. 十分な情報収集後 → writerへ 47 | 6. 執筆完了 → 処理終了 48 | 49 | Writer特徴: 50 | - create_react_agentベースの自律的ツール実行 51 | - 小さなツールを組み合わせた段階的な処理 52 | - LLMによる判断(ルールベース処理なし) 53 | 54 | 最終出力ルール(厳守): 55 | Writerから「執筆完了」の報告を受けたら: 56 | 1. 独自の内容生成は禁止 57 | 2. 「執筆結果」「説明のポイント」等の追加は禁止 58 | 3. Writerが保存したファイルパスのみを返す 59 | 4. 形式: output/[日時]/article.txt(1行のみ) 60 | 5. それ以外の文章は一切書かない""" 61 | 62 | # Supervisorワークフロー 63 | workflow = create_supervisor( 64 | model=model, 65 | agents=agents, 66 | prompt=system_prompt, 67 | ) 68 | 69 | return workflow 70 | 71 | 72 | # グラフのエクスポート(LangGraph Studio用) 73 | graph = create_writing_assistant_workflow().compile() 74 | -------------------------------------------------------------------------------- /29/README.md: -------------------------------------------------------------------------------- 1 | # Software Design誌「実践LLMアプリケーション開発」第29回サンプルコード 2 | 3 | LangChain v1.0における`create_agent`とミドルウェアの仕組みを実際に動かして理解するJupyter Notebook 4 | 5 | ## セットアップ方法 6 | 7 | ### 環境構築の手順 8 | 9 | ; このサンプルは`uv`を使用しています。`uv`をインストールしていない場合は[公式サイト](https://github.com/astral-sh/uv)を参照してください。 10 | 11 | コマンドを実行して依存関係をインストールしてください。 12 | 13 | ``` 14 | $ uv sync 15 | ``` 16 | 17 | 次にサンプルコードを実行するため`.env`ファイルを作成しAPI鍵を設定してください。 18 | 19 | ``` 20 | $ vi .env # 好きなエディタで編集してください 21 | ``` 22 | 23 | `.env`ファイルには以下のAPI鍵を設定してください。 24 | 25 | ``` 26 | ANTHROPIC_API_KEY=your_anthropic_api_key_here 27 | ``` 28 | 29 | - `ANTHROPIC_API_KEY`: Anthropic Claude APIの鍵 30 | 31 | ### Notebookの実行 32 | 33 | Jupyter Notebookを起動します: 34 | 35 | ```bash 36 | uv run jupyter notebook create_agent.ipynb 37 | ``` 38 | 39 | または、Jupyter Labを使う場合: 40 | 41 | ```bash 42 | uv run jupyter lab create_agent.ipynb 43 | ``` 44 | 45 | ## 内容 46 | 47 | `create_agent.ipynb`では以下の内容を解説しています 48 | 49 | ### 基本編 50 | 51 | | セクション | 内容 | 52 | |:---:|:---| 53 | | 1 | 環境セットアップ | 54 | | 2 | create_agentの基本 - 最もシンプルなエージェント作成 | 55 | | 3 | toolsパラメータ - @toolデコレータを使ったツール定義 | 56 | | 4 | modelパラメータ - 文字列/インスタンスでの指定 | 57 | | 5 | system_promptパラメータ - SystemMessageの活用 | 58 | | 6 | response_formatパラメータ - 構造化出力 (ToolStrategy) | 59 | 60 | ### 応用編 61 | 62 | | セクション | 内容 | 63 | |:---:|:---| 64 | | 7 | 短期記憶 (checkpointer) - InMemorySaverを使った会話管理 | 65 | | 8 | ToolRuntimeの活用 - state/context/storeへのアクセス | 66 | | 9 | 長期記憶 (store) - InMemoryStoreを使った永続化 | 67 | | 10 | state_schemaとcontext_schema - カスタム状態とコンテキスト定義 | 68 | 69 | ### ミドルウェア編 70 | 71 | | セクション | 内容 | 72 | |:---:|:---| 73 | | 11 | デコレータベースミドルウェア - @before_model, @after_model, @dynamic_prompt | 74 | | 12 | クラスベースミドルウェア - AgentMiddlewareの継承 | 75 | | 13 | ModelRequestとModelResponse - wrap_model_callを使った詳細制御 | 76 | | 14 | 標準ミドルウェアの紹介 - SummarizationMiddleware等の実例 | 77 | 78 | ## 注意 79 | 80 | - このNotebookを実行するとAnthropic APIが呼び出され料金が発生します 81 | - 実行は自己責任でお願いします 82 | - APIキーは`.env`ファイルで管理し、絶対に公開しないでください 83 | -------------------------------------------------------------------------------- /23/src/sd_23/agents/tech_agent.py: -------------------------------------------------------------------------------- 1 | """技術サポートに特化したエージェント""" 2 | 3 | import random 4 | from langchain_anthropic import ChatAnthropic 5 | from langchain_core.tools import tool 6 | from langgraph.prebuilt import create_react_agent 7 | from langgraph_swarm import create_handoff_tool 8 | from langgraph.graph.graph import CompiledGraph 9 | 10 | 11 | @tool 12 | def check_system_status() -> str: 13 | """システムステータスをチェックします(モック実装)""" 14 | return "システムステータス: すべてのサービスが正常に動作しています。" 15 | 16 | 17 | @tool 18 | def read_manual(error_code: str) -> str: 19 | """エラーコードのマニュアルを読み取ります(モック実装)""" 20 | if error_code == "500": 21 | return """エラーコード500 (Internal Server Error) 22 | 23 | 【原因】 24 | 1. サーバー側のプログラムエラー 25 | 2. データベース接続エラー 26 | 3. メモリ不足やリソース枯渇 27 | 28 | 【診断方法】 29 | - サーバーログの確認 30 | - 最近のデプロイメントの確認 31 | - リソース使用状況の確認 32 | 33 | 【解決方法】 34 | 1. ブラウザのキャッシュをクリアして再読み込み 35 | 2. 5分待ってから再度アクセス 36 | 3. 別のブラウザやデバイスでアクセス 37 | 4. それでも解決しない場合はサポートチケット作成""" 38 | return f"エラーコード{error_code}のマニュアル情報は見つかりませんでした。" 39 | 40 | 41 | def create_tech_agent() -> CompiledGraph: 42 | """技術サポートエージェントを作成""" 43 | model = ChatAnthropic(temperature=0, model_name="claude-sonnet-4-20250514") 44 | 45 | # FAQサポートへのハンドオフツール 46 | faq_handoff = create_handoff_tool( 47 | agent_name="faq_support", 48 | description="一般的なFAQサポートが必要な場合はFAQサポートに転送", 49 | ) 50 | 51 | tools = [check_system_status, read_manual, faq_handoff] 52 | 53 | prompt = """あなたの名前は「tech_support」です。技術サポートエージェントとして動作しています。 54 | 55 | 役割: 56 | - エラーコードの詳細な診断と解決策の提供 57 | - システム状態の確認とトラブルシューティング 58 | - 必要に応じてサポートチケットの作成 59 | 60 | エラーコードの診断手順: 61 | 1. check_system_statusツールでシステム状態を確認 62 | 2. read_manualツールでエラーコードの詳細を確認 63 | 3. 両方の結果を基に具体的な解決策を提供 64 | 65 | 重要: 66 | - あなたは「tech_support」という名前のエージェントです 67 | - FAQエージェントから転送された技術的な問題に対応します 68 | - 必ずツールを使用して具体的な診断を行ってください 69 | - プロダクトに関する一般的なFAQは、faq_supportへ転送してください""" 70 | 71 | agent = create_react_agent( 72 | model=model, tools=tools, name="tech_support", prompt=prompt 73 | ) 74 | 75 | return agent 76 | -------------------------------------------------------------------------------- /25/README.md: -------------------------------------------------------------------------------- 1 | # Software Design誌「実践LLMアプリケーション開発」第25回サンプルコード 2 | 3 | ## サンプルコードの実行方法 4 | 5 | ### 前提条件 6 | 7 | このサンプルコードを実行するには、事前に**OpenAI APIキー**の取得が必要です。 8 | APIキーは[OpenAI APIキーの管理画面](https://platform.openai.com/api-keys)から取得できます。 9 | 10 | ※ このリポジトリには最適化済みのモデルファイル(`artifact/edamame_fairy_model.json`)が含まれているため、すぐにチャットボットを試すことができます。独自に最適化を行いたい場合は、後述の「チャットボットの最適化」手順を実行してください。 11 | 12 | ### プロジェクトのセットアップ 13 | 14 | ※ このプロジェクトは`uv`を使用しています。`uv`のインストール方法については[こちら](https://github.com/astral-sh/uv)をご確認ください。 15 | 16 | 以下のコマンドを実行し、必要なライブラリのインストールを行って下さい。 17 | 18 | ``` 19 | $ uv sync 20 | ``` 21 | 22 | 次に環境変数の設定を行います。`.env`ファイルを作成し、以下の内容を記載してください。 23 | 24 | ``` 25 | $ vi .env # お好きなエディタで編集してください 26 | ``` 27 | 28 | `.env`ファイルに以下の設定を記載してください。 29 | 30 | ``` 31 | OPENAI_API_KEY=your_openai_api_key_here 32 | MLFLOW_PORT=5001 33 | ``` 34 | 35 | - `OPENAI_API_KEY`: OpenAI APIのキー(gpt-4.1-mini, gpt-4.1-nano使用) 36 | - `MLFLOW_PORT`: MLflowサーバーのポート番号(デフォルト: 5001) 37 | 38 | ### 実行方法 39 | 40 | #### 1. MLflowサーバーの起動 41 | 42 | 実験管理のためのMLflowサーバーを起動します: 43 | 44 | ```bash 45 | uv run mlflow server --backend-store-uri sqlite:///mlflow.db --host 0.0.0.0 --port 5001 46 | ``` 47 | 48 | 起動後、ブラウザで `http://localhost:5001` にアクセスすると、MLflow UIで実験結果を確認できます。 49 | 50 | #### 2. チャットボットの最適化(オプション) 51 | 52 | 自身でMIPROv2を使用してチャットボットを最適化する場合: 53 | 54 | ```bash 55 | uv run python chatbot_tuning.py 56 | ``` 57 | 58 | このコマンドで以下の処理が実行されます: 59 | - トレーニングに使用する日本語データセットの読み込み 60 | - MIPROv2による自動プロンプト最適化 61 | - MLflowでの実験追跡 62 | - 最適化済みモデルの保存(`artifact/edamame_fairy_model.json`を上書き) 63 | 64 | #### 3. 対話型チャットボットの実行 65 | 66 | 最適化済みのチャットボットと対話します: 67 | 68 | ```bash 69 | uv run python main.py 70 | ``` 71 | 72 | - 「quit」「exit」「終了」のいずれかを入力すると終了します 73 | - 枝豆の妖精キャラクターとして、語尾に「のだ」「なのだ」を使った親しみやすい対話を行います 74 | 75 | ### プロジェクト構成 76 | 77 | - `chatbot_module.py`: DSPyチャットボットモジュール 78 | - `chatbot_tuning.py`: MIPROv2による最適化スクリプト 79 | - `main.py`: DSPyモジュールのテスト用チャット 80 | - `artifact/`: 最適化済みモデルの保存先 81 | - `mlartifacts/`: MLflow実験のアーティファクト(MLflow実行時に自動生成) 82 | - `mlflow.db`: MLflow実験データベース(MLflow実行時に自動生成) 83 | -------------------------------------------------------------------------------- /19/sd_19/client.py: -------------------------------------------------------------------------------- 1 | from mcp import ClientSession, StdioServerParameters 2 | from mcp.client.stdio import stdio_client 3 | from mcp.server.fastmcp.utilities.logging import configure_logging, get_logger 4 | 5 | logger = get_logger(__name__) 6 | configure_logging() 7 | 8 | server_params = StdioServerParameters( 9 | command="python", 10 | args=["sd_19/server.py"], 11 | env=None, 12 | ) 13 | 14 | 15 | async def run(): 16 | async with stdio_client(server_params) as (read, write): 17 | async with ClientSession(read, write) as session: 18 | # 接続を初期化 19 | await session.initialize() 20 | 21 | # 利用可能なプロンプト一覧を取得 22 | prompts = await session.list_prompts() 23 | logger.info("=== prompts ===") 24 | logger.info(prompts) 25 | logger.info("\n") 26 | 27 | # プロンプトを取得 28 | prompt = await session.get_prompt( 29 | "greet_user", arguments={"user_name": "sd-19"} 30 | ) 31 | logger.info("=== prompt ===") 32 | logger.info(prompt) 33 | logger.info("\n") 34 | 35 | # 利用可能なリソース一覧を取得 36 | resources = await session.list_resources() 37 | logger.info("=== resources ===") 38 | logger.info(resources) 39 | logger.info("\n") 40 | 41 | # リソースを読み込む 42 | resource = await session.read_resource("file://readme.md") 43 | logger.info("=== resource ===") 44 | logger.info(resource) 45 | logger.info("\n") 46 | 47 | # 利用可能なツール一覧を取得 48 | tools = await session.list_tools() 49 | logger.info("=== tools ===") 50 | logger.info(tools) 51 | logger.info("\n") 52 | 53 | # ツールを呼び出す 54 | result = await session.call_tool("hello", arguments={"name": "sd-19"}) 55 | logger.info("=== tool result ===") 56 | logger.info(result) 57 | logger.info("\n") 58 | 59 | 60 | if __name__ == "__main__": 61 | import asyncio 62 | 63 | asyncio.run(run()) 64 | -------------------------------------------------------------------------------- /09/prompts/plan_system.prompt: -------------------------------------------------------------------------------- 1 | You are the specialist who performs the task decomposition necessary to ensure that the user's request is carried out. By breaking down each task as small as possible, you can break it down into tasks that can be executed by AI. 2 | 3 | ### task 4 | Perform task decomposition based on the user's request. The output should follow the output_format. Each task should be executed by the tool defined in tool_definitions. Task decomposition should be done in units that can be executed by these tools. Do not create tasks that cannot be executed, see output_example. 5 | 6 | ### tool_definitions: json""" 7 | { 8 | "search": {"description": "Search for information on the internet."}, 9 | "write": {"description": "Write a report based on the information found."} 10 | } 11 | """ 12 | 13 | ### output_format: json""" 14 | { 15 | "type": "array", 16 | "properties": { 17 | "id": {"required": true, "type": "integer", "description": "The ID of the task. The ID is unique and is used to identify the task."}, 18 | "action": {"required": true, "type": "string", "description": "Set the action defined in 'enum'.", "enum": "search,write"}, 19 | "description": {"required": true, "type": "string", "description": "The task that needs to be performed."}, 20 | "related_ids": {"required": true, "type": "array", "description": "If there is a task that needs to be performed before this task, list the ID of that task in this field."} 21 | } 22 | } 23 | """ 24 | 25 | ### output_example: json""" 26 | [ 27 | { "id": 1, "action": "search", "description": "Automotive Industry Issues"}, 28 | { "id": 2, "action": "search", "description": "EV Vehicle Issues"}, 29 | { "id": 3, "action": "search", "description": "Marketing Strategies for EVs", "related_ids": [1, 2]}, 30 | { "id": 4, "action": "write", "description": "Write reports according to the information", "related_ids": [1, 2, 3] } 31 | ] 32 | """ 33 | 34 | Information gathering should be multi-faceted. 35 | Divide each task into as many smaller pieces as possible. 36 | The more detailed the task, the more successful it will be. 37 | Let's think horizontally. 38 | output should be json format. -------------------------------------------------------------------------------- /17/README.md: -------------------------------------------------------------------------------- 1 | # Software Design誌「実践LLMアプリケーション開発」第17回サンプルコード 2 | 3 | ## サンプルコードの実行方法 4 | 5 | ※ このプロジェクトは`uv`を使用しています。`uv`のインストール方法については[こちら](https://github.com/astral-sh/uv)をご確認ください。 6 | 7 | 以下のコマンドを実行し、必要なライブラリのインストールを行って下さい。 8 | 9 | ``` 10 | $ uv sync 11 | ``` 12 | 13 | 次に環境変数の設定を行います。まず`.env.sample`ファイルをコピーして`.env`ファイルを作成します。 14 | 15 | ``` 16 | $ cp .env.sample .env 17 | $ vi .env # お好きなエディタで編集してください 18 | ``` 19 | 20 | 続けて.envファイル内の以下のキーを設定して下さい。`TAVILY_API_KEY`ならびに`LANGSMITH_API_KEY`の取得方法については次節で解説します。 21 | 22 | ``` 23 | OPENAI_API_KEY=[発行されたAPIキーを設定します] 24 | TAVILY_API_KEY=[発行されたAPIキーを設定します] 25 | LANGSMITH_TRACING_V2=true 26 | LANGSMITH_API_KEY=[発行されたAPIキーを設定します] 27 | LANGSMITH_PROJECT=sd-17 28 | ``` 29 | 30 | お手元で動作を確認される際には、上記のセットアップの後に以下のコマンドを実行してご確認ください。 31 | 32 | ``` 33 | $ uv run langgraph dev 34 | ``` 35 | 36 | ## Tavily APIキーの取得方法 37 | 38 | ### Tavilyについて 39 | 40 | Tavilyは、LLMアプリケーションにおけるRAG(Retrieval-Augmented Generation)に特化した検索エンジンです。 41 | 42 | 通常の検索APIはユーザーのクエリに基づいて検索結果を取得しますが、検索の目的に対して無関係な結果が返ってくることがあります。また、単純なURLやスニペットが返されるため、開発者が関連するコンテンツをスクレイピングしたり、不要な情報をフィルタリングしたりする必要があります。 43 | 44 | 一方でTavilyは、一回のAPI呼び出しで20以上のサイトを集約し、独自のAIを利用しタスクやクエリ、目的に最も関連する情報源とコンテンツを評価、フィルタリング、ランク付けすることが可能な仕組みになっています。 45 | 46 | 今回のサンプルコードを実行するためにはTavilyのAPIキーが必要なので、以下の手段でAPIキーを取得してください。 47 | 48 | ### Tavily APIキーの取得 49 | 50 | 1. まず https://tavily.com/ にアクセスし、画面右上の「Try it out」をクリックします。 51 | 2. するとサインイン画面に遷移するので、そのままサインインを進めて下さい。GoogleアカウントかGitHubアカウントを選択することができます。 52 | 3. サインインが完了するとAPIキーが取得できる画面に遷移します。画面中央部の「API Key」欄より、APIキーをコピーしてください。 53 | 4. APIキーを`.env`内の`TAVILY_API_KEY`に設定してください。 54 | 55 | ## LangSmith APIキーの取得方法 56 | 57 | LangSmithはLLM(大規模言語モデル)実行のログ分析ツールです。LLMアプリケーションの実行を詳細に追跡し、デバッグや評価を行うための機能を提供します。詳細については https://docs.smith.langchain.com/ をご確認ください。 58 | 59 | LangSmithによるトレースを有効にするためには、LangSmithにユーザー登録した上で、APIキーを発行する必要があります。以下の手順を参考にAPIキーを取得してください。 60 | 61 | ### LangSmith APIキーの取得 62 | 63 | 1. [サインアップページ](https://smith.langchain.com/)より、LangSmithのユーザー登録を行います。 64 | 2. [設定ページ](https://smith.langchain.com/settings)を開きます。 65 | 3. API keysタブを開き、「Create API Key」をクリックします。するとAPIキーが発行されますので、APIキーをコピーしてください。 66 | 4. APIキーを`.env`内の`LANGCHAIN_API_KEY`に設定してください。 67 | -------------------------------------------------------------------------------- /14/app.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Literal 2 | from uuid import uuid4 3 | 4 | import streamlit as st 5 | from agent import HumanInTheLoopAgent 6 | from dotenv import load_dotenv 7 | from langchain_openai import ChatOpenAI 8 | 9 | 10 | def show_message(type: Literal["human", "agent"], title: str, message: str) -> None: 11 | with st.chat_message(type): 12 | st.markdown(f"**{title}**") 13 | st.markdown(message) 14 | 15 | 16 | def app() -> None: 17 | load_dotenv(override=True) 18 | 19 | st.title("Human-in-the-loopを適用したリサーチエージェント") 20 | 21 | # st.session_stateにagentを保存 22 | if "agent" not in st.session_state: 23 | _llm = ChatOpenAI(model="gpt-4o", temperature=0.5) 24 | _agent = HumanInTheLoopAgent(_llm) 25 | _agent.subscribe(show_message) 26 | st.session_state.agent = _agent 27 | 28 | agent = st.session_state.agent 29 | 30 | # グラフを表示 31 | with st.sidebar: 32 | st.image(agent.mermaid_png()) 33 | 34 | # st.session_stateにthread_idを保存 35 | if "thread_id" not in st.session_state: 36 | st.session_state.thread_id = uuid4().hex 37 | thread_id = st.session_state.thread_id 38 | st.write(f"thread_id: {thread_id}") 39 | 40 | # ユーザーの入力を受け付ける 41 | human_message = st.chat_input() 42 | if human_message: 43 | with st.spinner(): 44 | agent.handle_human_message(human_message, thread_id) 45 | # ユーザー入力があった場合、承認状態をリセット 46 | st.session_state.approval_state = "pending" 47 | 48 | # 次がhuman_approvalの場合は承認ボタンを表示 49 | if agent.is_next_human_approval_node(thread_id): 50 | if "approval_state" not in st.session_state: 51 | st.session_state.approval_state = "pending" 52 | 53 | if st.session_state.approval_state == "pending": 54 | approved = st.button("承認") 55 | if approved: 56 | st.session_state.approval_state = "processing" 57 | st.rerun() 58 | elif st.session_state.approval_state == "processing": 59 | with st.spinner("タスク処理中..."): 60 | agent.handle_human_message("[APPROVE]", thread_id) 61 | st.session_state.approval_state = "pending" 62 | 63 | 64 | app() 65 | -------------------------------------------------------------------------------- /14/README.md: -------------------------------------------------------------------------------- 1 | ※ 第14回、第15回のサンプルコードは同じものを使用しています。 2 | 3 | ## サンプルコードの実行方法 4 | 5 | 以下のコマンドでサンプルコードが保存されているリポジトリをクローン後、 6 | 7 | ``` 8 | $ git clone https://github.com/mahm/softwaredesign-llm-application.git 9 | ``` 10 | 11 | 続けて以下のコマンドを実行し、必要なライブラリのインストールを行って下さい。 12 | 13 | ``` 14 | $ cd softwaredesign-llm-application/14 15 | $ pip install poetry 16 | $ poetry install # 必要なライブラリのインストール 17 | ``` 18 | 19 | 次に環境変数の設定を行います。まず`.env.sample`ファイルをコピーして`.env`ファイルを作成します。 20 | 21 | ``` 22 | $ cp .env.sample .env 23 | $ vi .env # お好きなエディタで編集してください 24 | ``` 25 | 26 | 続けて.envファイル内の`OPENAI_API_KEY`と`TAVILY_API_KEY`を設定して下さい。`TAVILY_API_KEY`の取得方法については次節で解説します。 27 | 28 | ``` 29 | OPENAI_API_KEY=[発行されたAPIキーを設定します] 30 | TAVILY_API_KEY=[発行されたAPIキーを設定します] 31 | ``` 32 | 33 | お手元で動作を確認される際には、上記のセットアップの後に以下のコマンドを実行してご確認ください。 34 | 35 | ``` 36 | $ poetry run streamlit run app.py 37 | ``` 38 | 39 | ## Tavily APIキーの取得方法 40 | 41 | ### Tavilyについて 42 | 43 | Tavilyは、LLMアプリケーションにおけるRAG(Retrieval-Augmented Generation)に特化した検索エンジンです。 44 | 45 | 通常の検索APIはユーザーのクエリに基づいて検索結果を取得しますが、検索の目的に対して無関係な結果が返ってくることがあります。また、単純なURLやスニペットが返されるため、開発者が関連するコンテンツをスクレイピングしたり、不要な情報をフィルタリングしたりする必要があります。 46 | 47 | 一方でTavilyは、一回のAPI呼び出しで20以上のサイトを集約し、独自のAIを利用しタスクやクエリ、目的に最も関連する情報源とコンテンツを評価、フィルタリング、ランク付けすることが可能な仕組みになっています。 48 | 49 | 今回のサンプルコードを実行するためにはTavilyのAPIキーが必要なので、以下の手段でAPIキーを取得してください。 50 | 51 | ### Tavily APIキーの取得 52 | 53 | 1. まず https://tavily.com/ にアクセスし、画面右上の「Try it out」をクリックします。 54 | 2. するとサインイン画面に遷移するので、そのままサインインを進めて下さい。GoogleアカウントかGitHubアカウントを選択することができます。 55 | 3. サインインが完了するとAPIキーが取得できる画面に遷移します。画面中央部の「API Key」欄より、APIキーをコピーしてください。 56 | 4. APIキーを`.env`内の`TAVILY_API_KEY`に設定してください。 57 | 58 | ## LangSmith APIキーの取得方法 59 | 60 | LangSmithはLLM(大規模言語モデル)実行のログ分析ツールです。LLMアプリケーションの実行を詳細に追跡し、デバッグや評価を行うための機能を提供します。詳細については https://docs.smith.langchain.com/ をご確認ください。 61 | 62 | LangSmithによるトレースを有効にするためには、LangSmithにユーザー登録した上で、APIキーを発行する必要があります。以下の手順を参考にAPIキーを取得してください。 63 | 64 | ### LangSmith APIキーの取得 65 | 66 | 1. [サインアップページ](https://smith.langchain.com/)より、LangSmithのユーザー登録を行います。 67 | 2. [設定ページ](https://smith.langchain.com/settings)を開きます。 68 | 3. API keysタブを開き、「Create API Key」をクリックします。するとAPIキーが発行されますので、APIキーをコピーしてください。 69 | 4. APIキーを`.env`内の`LANGCHAIN_API_KEY`に設定してください。 70 | 5. また`LANGCHAIN_TRACING_V2`に`true`、`LANGCHAIN_PROJECT`に`sd-12`と設定してください。 -------------------------------------------------------------------------------- /26/config.py: -------------------------------------------------------------------------------- 1 | """ 2 | 環境変数設定ヘルパー 3 | OpenAI/Azure OpenAIの切り替えとモデル設定を管理 4 | """ 5 | 6 | import os 7 | from dotenv import load_dotenv 8 | import dspy # type: ignore 9 | 10 | load_dotenv() 11 | 12 | # プロバイダー設定 13 | PROVIDER_NAME = os.getenv("PROVIDER_NAME", "openai") 14 | 15 | # LLMモデル設定 16 | SMART_MODEL = os.getenv("SMART_MODEL", "gpt-4.1") 17 | FAST_MODEL = os.getenv("FAST_MODEL", "gpt-4.1-nano") 18 | 19 | # 埋め込みモデル設定 20 | EMBEDDING_MODEL = os.getenv("EMBEDDING_MODEL", "text-embedding-3-small") 21 | 22 | # 検索設定 23 | RETRIEVAL_K = 10 # 検索結果の取得数 24 | 25 | 26 | def configure_lm(model_name: str | None = None, temperature: float = 0.0, max_tokens: int = 4096) -> dspy.LM: 27 | """DSPy用のLM設定を作成""" 28 | if model_name is None: 29 | model_name = FAST_MODEL 30 | 31 | if PROVIDER_NAME == "azure": 32 | # Azure OpenAIの設定 33 | api_base = os.getenv("AZURE_OPENAI_ENDPOINT") 34 | api_key = os.getenv("AZURE_OPENAI_API_KEY") 35 | api_version = os.getenv("AZURE_OPENAI_API_VERSION", "2025-04-01-preview") 36 | 37 | return dspy.LM( 38 | model=f"azure/{model_name}", 39 | api_base=api_base, 40 | api_key=api_key, 41 | api_version=api_version, 42 | temperature=temperature, 43 | max_tokens=max_tokens 44 | ) 45 | else: 46 | # OpenAIの設定 47 | api_key = os.getenv("OPENAI_API_KEY") 48 | 49 | return dspy.LM( 50 | model=f"openai/{model_name}", 51 | api_key=api_key, 52 | temperature=temperature, 53 | max_tokens=max_tokens 54 | ) 55 | 56 | 57 | def configure_embedder() -> dspy.Embedder: 58 | """DSPy用の埋め込みモデル設定を作成""" 59 | if PROVIDER_NAME == "azure": 60 | # Azure OpenAIの埋め込み設定 61 | api_base = os.getenv("AZURE_OPENAI_ENDPOINT") 62 | api_key = os.getenv("AZURE_OPENAI_API_KEY") 63 | 64 | return dspy.Embedder( 65 | model=f"azure/{EMBEDDING_MODEL}", 66 | api_base=api_base, 67 | api_key=api_key 68 | ) 69 | else: 70 | # OpenAIの埋め込み設定 71 | api_key = os.getenv("OPENAI_API_KEY") 72 | 73 | return dspy.Embedder( 74 | model=f"openai/{EMBEDDING_MODEL}", 75 | api_key=api_key 76 | ) 77 | -------------------------------------------------------------------------------- /30/src/sd_30/pages/common.py: -------------------------------------------------------------------------------- 1 | """ 2 | 共通ユーティリティ 3 | """ 4 | 5 | import uuid 6 | 7 | import streamlit as st 8 | 9 | 10 | def init_session_state() -> None: 11 | """セッションの初期状態を設定""" 12 | if "messages" not in st.session_state: 13 | st.session_state.messages = [] 14 | if "thread_id" not in st.session_state: 15 | st.session_state.thread_id = str(uuid.uuid4()) 16 | if "pending_approval" not in st.session_state: 17 | st.session_state.pending_approval = None 18 | 19 | 20 | def render_sidebar() -> str: 21 | """サイドバーを描画してシナリオを返す""" 22 | st.sidebar.title("LangChain Middleware デモ") 23 | st.sidebar.markdown("---") 24 | 25 | query_params = st.query_params 26 | scenario = query_params.get("scenario", "scenario1") 27 | 28 | st.sidebar.markdown("### シナリオ選択") 29 | 30 | btn_type_1 = "primary" if scenario == "scenario1" else "secondary" 31 | btn_type_2 = "primary" if scenario == "scenario2" else "secondary" 32 | btn_type_3 = "primary" if scenario == "scenario3" else "secondary" 33 | 34 | if st.sidebar.button("シナリオ1: メール処理", use_container_width=True, type=btn_type_1): 35 | st.query_params["scenario"] = "scenario1" 36 | st.session_state.messages = [] 37 | st.rerun() 38 | 39 | if st.sidebar.button("シナリオ2: レジリエンス", use_container_width=True, type=btn_type_2): 40 | st.query_params["scenario"] = "scenario2" 41 | st.session_state.messages = [] 42 | st.rerun() 43 | 44 | if st.sidebar.button("シナリオ3: ツール選択", use_container_width=True, type=btn_type_3): 45 | st.query_params["scenario"] = "scenario3" 46 | st.session_state.messages = [] 47 | st.rerun() 48 | 49 | st.sidebar.markdown("---") 50 | st.sidebar.markdown(""" 51 | ### 使用ミドルウェア 52 | 53 | **シナリオ1:** 54 | - PIIMiddleware 55 | - SummarizationMiddleware 56 | - HumanInTheLoopMiddleware 57 | 58 | **シナリオ2:** 59 | - ModelCallLimitMiddleware 60 | - ToolCallLimitMiddleware 61 | - ModelFallbackMiddleware 62 | - ToolRetryMiddleware 63 | - ModelRetryMiddleware 64 | 65 | **シナリオ3:** 66 | - LLMToolSelectorMiddleware 67 | """) 68 | 69 | return scenario 70 | 71 | 72 | def reset_conversation() -> None: 73 | """会話をリセット""" 74 | st.session_state.messages = [] 75 | st.session_state.thread_id = str(uuid.uuid4()) 76 | st.session_state.pending_approval = None 77 | -------------------------------------------------------------------------------- /16/README.md: -------------------------------------------------------------------------------- 1 | # Software Design誌「実践LLMアプリケーション開発」第16回サンプルコード 2 | 3 | ## サンプルコードの実行方法 4 | 5 | ※ このプロジェクトは`uv`を使用しています。`uv`のインストール方法については[こちら](https://github.com/astral-sh/uv)をご確認ください。 6 | 7 | 以下のコマンドでサンプルコードが保存されているリポジトリをクローン後、 8 | 9 | ``` 10 | $ git clone https://github.com/mahm/sd-16.git 11 | ``` 12 | 13 | 続けて以下のコマンドを実行し、必要なライブラリのインストールを行って下さい。 14 | 15 | ``` 16 | $ cd sd-16 17 | $ uv sync 18 | ``` 19 | 20 | 次に環境変数の設定を行います。まず`.env.sample`ファイルをコピーして`.env`ファイルを作成します。 21 | 22 | ``` 23 | $ cp .env.sample .env 24 | $ vi .env # お好きなエディタで編集してください 25 | ``` 26 | 27 | 続けて.envファイル内の以下のキーを設定して下さい。`TAVILY_API_KEY`ならびに`LANGSMITH_API_KEY`の取得方法については次節で解説します。 28 | 29 | ``` 30 | OPENAI_API_KEY=[発行されたAPIキーを設定します] 31 | ANTHROPIC_API_KEY=[発行されたAPIキーを設定します] 32 | TAVILY_API_KEY=[発行されたAPIキーを設定します] 33 | LANGSMITH_TRACING_V2=true 34 | LANGSMITH_API_KEY=[発行されたAPIキーを設定します] 35 | LANGSMITH_PROJECT=sd-16 36 | ``` 37 | 38 | お手元で動作を確認される際には、上記のセットアップの後に以下のコマンドを実行してご確認ください。 39 | 40 | ``` 41 | $ pip install -U langgraph-cli 42 | $ langgraph up 43 | ``` 44 | 45 | ## Tavily APIキーの取得方法 46 | 47 | ### Tavilyについて 48 | 49 | Tavilyは、LLMアプリケーションにおけるRAG(Retrieval-Augmented Generation)に特化した検索エンジンです。 50 | 51 | 通常の検索APIはユーザーのクエリに基づいて検索結果を取得しますが、検索の目的に対して無関係な結果が返ってくることがあります。また、単純なURLやスニペットが返されるため、開発者が関連するコンテンツをスクレイピングしたり、不要な情報をフィルタリングしたりする必要があります。 52 | 53 | 一方でTavilyは、一回のAPI呼び出しで20以上のサイトを集約し、独自のAIを利用しタスクやクエリ、目的に最も関連する情報源とコンテンツを評価、フィルタリング、ランク付けすることが可能な仕組みになっています。 54 | 55 | 今回のサンプルコードを実行するためにはTavilyのAPIキーが必要なので、以下の手段でAPIキーを取得してください。 56 | 57 | ### Tavily APIキーの取得 58 | 59 | 1. まず https://tavily.com/ にアクセスし、画面右上の「Try it out」をクリックします。 60 | 2. するとサインイン画面に遷移するので、そのままサインインを進めて下さい。GoogleアカウントかGitHubアカウントを選択することができます。 61 | 3. サインインが完了するとAPIキーが取得できる画面に遷移します。画面中央部の「API Key」欄より、APIキーをコピーしてください。 62 | 4. APIキーを`.env`内の`TAVILY_API_KEY`に設定してください。 63 | 64 | ## LangSmith APIキーの取得方法 65 | 66 | LangSmithはLLM(大規模言語モデル)実行のログ分析ツールです。LLMアプリケーションの実行を詳細に追跡し、デバッグや評価を行うための機能を提供します。詳細については https://docs.smith.langchain.com/ をご確認ください。 67 | 68 | LangSmithによるトレースを有効にするためには、LangSmithにユーザー登録した上で、APIキーを発行する必要があります。以下の手順を参考にAPIキーを取得してください。 69 | 70 | ### LangSmith APIキーの取得 71 | 72 | 1. [サインアップページ](https://smith.langchain.com/)より、LangSmithのユーザー登録を行います。 73 | 2. [設定ページ](https://smith.langchain.com/settings)を開きます。 74 | 3. API keysタブを開き、「Create API Key」をクリックします。するとAPIキーが発行されますので、APIキーをコピーしてください。 75 | 4. APIキーを`.env`内の`LANGCHAIN_API_KEY`に設定してください。 76 | -------------------------------------------------------------------------------- /27/config.py: -------------------------------------------------------------------------------- 1 | """ 2 | 環境変数設定ヘルパー 3 | OpenAI/Azure OpenAIの切り替えとモデル設定を管理 4 | """ 5 | 6 | import os 7 | from dotenv import load_dotenv 8 | import dspy # type: ignore 9 | 10 | load_dotenv() 11 | 12 | # プロバイダー設定 13 | PROVIDER_NAME = os.getenv("PROVIDER_NAME", "openai") 14 | 15 | # LLMモデル設定 16 | SMART_MODEL = os.getenv("SMART_MODEL", "gpt-4.1") 17 | FAST_MODEL = os.getenv("FAST_MODEL", "gpt-4.1-nano") 18 | EVAL_MODEL = os.getenv("EVAL_MODEL", "gpt-4.1-mini") # LLM as a Judge用評価モデル 19 | 20 | # 埋め込みモデル設定 21 | EMBEDDING_MODEL = os.getenv("EMBEDDING_MODEL", "text-embedding-3-small") 22 | 23 | # 検索設定 24 | RETRIEVAL_K = 10 # 検索結果の取得数 25 | 26 | 27 | def configure_lm(model_name: str | None = None, temperature: float = 0.0, max_tokens: int = 4096) -> dspy.LM: 28 | """DSPy用のLM設定を作成""" 29 | if model_name is None: 30 | model_name = FAST_MODEL 31 | 32 | if PROVIDER_NAME == "azure": 33 | # Azure OpenAIの設定 34 | api_base = os.getenv("AZURE_OPENAI_ENDPOINT") 35 | api_key = os.getenv("AZURE_OPENAI_API_KEY") 36 | api_version = os.getenv("AZURE_OPENAI_API_VERSION", "2025-04-01-preview") 37 | 38 | return dspy.LM( 39 | model=f"azure/{model_name}", 40 | api_base=api_base, 41 | api_key=api_key, 42 | api_version=api_version, 43 | temperature=temperature, 44 | max_tokens=max_tokens 45 | ) 46 | else: 47 | # OpenAIの設定 48 | api_key = os.getenv("OPENAI_API_KEY") 49 | 50 | return dspy.LM( 51 | model=f"openai/{model_name}", 52 | api_key=api_key, 53 | temperature=temperature, 54 | max_tokens=max_tokens 55 | ) 56 | 57 | 58 | def configure_embedder() -> dspy.Embedder: 59 | """DSPy用の埋め込みモデル設定を作成""" 60 | if PROVIDER_NAME == "azure": 61 | # Azure OpenAIの埋め込み設定 62 | api_base = os.getenv("AZURE_OPENAI_ENDPOINT") 63 | api_key = os.getenv("AZURE_OPENAI_API_KEY") 64 | 65 | return dspy.Embedder( 66 | model=f"azure/{EMBEDDING_MODEL}", 67 | api_base=api_base, 68 | api_key=api_key 69 | ) 70 | else: 71 | # OpenAIの埋め込み設定 72 | api_key = os.getenv("OPENAI_API_KEY") 73 | 74 | return dspy.Embedder( 75 | model=f"openai/{EMBEDDING_MODEL}", 76 | api_key=api_key 77 | ) 78 | -------------------------------------------------------------------------------- /12/README.md: -------------------------------------------------------------------------------- 1 | ## サンプルコードの実行方法 2 | 3 | 以下のコマンドでサンプルコードが保存されているリポジトリをクローン後、 4 | 5 | ``` 6 | $ git clone https://github.com/mahm/softwaredesign-llm-application.git 7 | ``` 8 | 9 | 続けて以下のコマンドを実行し、必要なライブラリのインストールを行って下さい。 10 | 11 | ``` 12 | $ cd softwaredesign-llm-application/12 13 | $ python -m venv .venv 14 | $ source .venv/bin/activate 15 | $ pip install -r requirements.txt # 必要なライブラリのインストール 16 | ``` 17 | 18 | 次に環境変数の設定を行います。まず`.env.sample`ファイルをコピーして`.env`ファイルを作成します。 19 | 20 | ``` 21 | $ cp .env.sample .env 22 | $ vi .env # お好きなエディタで編集してください 23 | ``` 24 | 25 | 続けて.envファイル内の`OPENAI_API_KEY`と`TAVILY_API_KEY`を設定して下さい。`TAVILY_API_KEY`の取得方法については次節で解説します。 26 | 27 | ``` 28 | OPENAI_API_KEY=[発行されたAPIキーを設定します] 29 | TAVILY_API_KEY=[発行されたAPIキーを設定します] 30 | ``` 31 | 32 | ディレクトリ内にはサンプルコードを収録した`arag_agent.py`が保存されています。お手元で動作を確認される際には、上記のセットアップの後に以下のコマンドを実行してご確認ください。`--task`オプションに続けて作成して欲しいレポートの指示を入力すると、レポートを生成します。 33 | 34 | ``` 35 | python arag_agent.py --task 生成AIスタートアップの最新動向について調査してください 36 | ``` 37 | 38 | ## Tavily APIキーの取得方法 39 | 40 | ### Tavilyについて 41 | 42 | Tavilyは、LLMアプリケーションにおけるRAG(Retrieval-Augmented Generation)に特化した検索エンジンです。 43 | 44 | 通常の検索APIはユーザーのクエリに基づいて検索結果を取得しますが、検索の目的に対して無関係な結果が返ってくることがあります。また、単純なURLやスニペットが返されるため、開発者が関連するコンテンツをスクレイピングしたり、不要な情報をフィルタリングしたりする必要があります。 45 | 46 | 一方でTavilyは、一回のAPI呼び出しで20以上のサイトを集約し、独自のAIを利用しタスクやクエリ、目的に最も関連する情報源とコンテンツを評価、フィルタリング、ランク付けすることが可能な仕組みになっています。 47 | 48 | 今回のサンプルコードを実行するためにはTavilyのAPIキーが必要なので、以下の手段でAPIキーを取得してください。 49 | 50 | ### Tavily APIキーの取得 51 | 52 | 1. まず https://tavily.com/ にアクセスし、画面右上の「Try it out」をクリックします。 53 | 2. するとサインイン画面に遷移するので、そのままサインインを進めて下さい。GoogleアカウントかGitHubアカウントを選択することができます。 54 | 3. サインインが完了するとAPIキーが取得できる画面に遷移します。画面中央部の「API Key」欄より、APIキーをコピーしてください。 55 | 4. APIキーを`.env`内の`TAVILY_API_KEY`に設定してください。 56 | 57 | ## LangSmith APIキーの取得方法 58 | 59 | LangSmithはLLM(大規模言語モデル)実行のログ分析ツールです。LLMアプリケーションの実行を詳細に追跡し、デバッグや評価を行うための機能を提供します。詳細については https://docs.smith.langchain.com/ をご確認ください。 60 | 61 | LangSmithによるトレースを有効にするためには、LangSmithにユーザー登録した上で、APIキーを発行する必要があります。以下の手順を参考にAPIキーを取得してください。 62 | 63 | ### LangSmith APIキーの取得 64 | 65 | 1. [サインアップページ](https://smith.langchain.com/)より、LangSmithのユーザー登録を行います。 66 | 2. [設定ページ](https://smith.langchain.com/settings)を開きます。 67 | 3. API keysタブを開き、「Create API Key」をクリックします。するとAPIキーが発行されますので、APIキーをコピーしてください。 68 | 4. APIキーを`.env`内の`LANGCHAIN_API_KEY`に設定してください。 69 | 5. また`LANGCHAIN_TRACING_V2`に`true`、`LANGCHAIN_PROJECT`に`sd-12`と設定してください。 -------------------------------------------------------------------------------- /28/config.py: -------------------------------------------------------------------------------- 1 | """ 2 | 環境変数設定ヘルパー 3 | OpenAI/Azure OpenAIの切り替えとモデル設定を管理 4 | """ 5 | 6 | import os 7 | from dotenv import load_dotenv 8 | import dspy # type: ignore 9 | 10 | load_dotenv() 11 | 12 | # プロバイダー設定 13 | PROVIDER_NAME = os.getenv("PROVIDER_NAME", "openai") 14 | 15 | # LLMモデル設定 16 | SMART_MODEL = os.getenv("SMART_MODEL", "gpt-4.1") 17 | FAST_MODEL = os.getenv("FAST_MODEL", "gpt-4.1-nano") 18 | EVAL_MODEL = os.getenv("EVAL_MODEL", "gpt-4.1-mini") # LLM as a Judge用評価モデル 19 | 20 | # 埋め込みモデル設定 21 | EMBEDDING_MODEL = os.getenv("EMBEDDING_MODEL", "text-embedding-3-small") 22 | 23 | # 検索設定 24 | RETRIEVAL_K = 10 # 検索結果の取得数 25 | 26 | 27 | def configure_lm(model_name: str | None = None, temperature: float = 0.0, max_tokens: int = 4096) -> dspy.LM: 28 | """DSPy用のLM設定を作成""" 29 | if model_name is None: 30 | model_name = FAST_MODEL 31 | 32 | if PROVIDER_NAME == "azure": 33 | # Azure OpenAIの設定 34 | api_base = os.getenv("AZURE_OPENAI_ENDPOINT") 35 | api_key = os.getenv("AZURE_OPENAI_API_KEY") 36 | api_version = os.getenv("AZURE_OPENAI_API_VERSION", "2025-04-01-preview") 37 | 38 | return dspy.LM( 39 | model=f"azure/{model_name}", 40 | api_base=api_base, 41 | api_key=api_key, 42 | api_version=api_version, 43 | temperature=temperature, 44 | max_tokens=max_tokens, 45 | cache=False # Disable prompt caching for accurate evaluation 46 | ) 47 | else: 48 | # OpenAIの設定 49 | api_key = os.getenv("OPENAI_API_KEY") 50 | 51 | return dspy.LM( 52 | model=f"openai/{model_name}", 53 | api_key=api_key, 54 | temperature=temperature, 55 | max_tokens=max_tokens, 56 | cache=False # Disable prompt caching for accurate evaluation 57 | ) 58 | 59 | 60 | def configure_embedder() -> dspy.Embedder: 61 | """DSPy用の埋め込みモデル設定を作成""" 62 | if PROVIDER_NAME == "azure": 63 | # Azure OpenAIの埋め込み設定 64 | api_base = os.getenv("AZURE_OPENAI_ENDPOINT") 65 | api_key = os.getenv("AZURE_OPENAI_API_KEY") 66 | 67 | return dspy.Embedder( 68 | model=f"azure/{EMBEDDING_MODEL}", 69 | api_base=api_base, 70 | api_key=api_key 71 | ) 72 | else: 73 | # OpenAIの埋め込み設定 74 | api_key = os.getenv("OPENAI_API_KEY") 75 | 76 | return dspy.Embedder( 77 | model=f"openai/{EMBEDDING_MODEL}", 78 | api_key=api_key 79 | ) 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 「実践LLMアプリケーション開発」サンプルコード 2 | 3 | このリポジトリは技術評論社Software Design誌の連載「実践LLMアプリケーション開発」に関連するサンプルコードを保存しています。 4 | 5 | ## 注意事項 6 | 7 | - このリポジトリのコードは自由に閲覧・二次利用が可能です。 8 | - ただし、すべて自己責任での利用となります。開発者や連載記事の著者は、いかなる責任も負いません。 9 | 10 | ソースコードに関する具体的な説明や解説については、連載記事の内容をご参照ください。 11 | 12 | ## サンプルコード一覧 13 | 14 | | 回 | テーマ | 説明 | 15 | |:---:|:---|:---| 16 | | 01 | Chainlitを使った基本的なチャットボット | OpenAI APIとChainlitを使用したシンプルなチャットボットの実装 | 17 | | 02 | RAGチャットボットの基礎 | ChromaDBとOpenAI Embeddingsを使用した「枝豆の妖精」スタイルのRAGチャットボット | 18 | | 03 | LangChainによるRAG実装 | LangChainのRetrievalQAWithSourcesChainを使ったベクトルストア検索の実装 | 19 | | 04 | Function Callingによるエージェント | LangChainとOpenAI Function Callingを使ったファイル管理エージェントの実装 | 20 | | 05 | エージェントのフォールバック処理 | Function Calling失敗時のフォールバック処理とメモリ管理の実装 | 21 | | 06 | Python環境構築 | 基本的なPython開発環境のセットアップ方法 | 22 | | 07 | LangGraphによるユーザーインタビューグラフ | LangGraphを使った対話フローの実装 | 23 | | 08 | LangGraphの応用 | LangGraphを使った複雑なワークフローの実装 | 24 | | 09 | Research Agent(タスク分解型エージェント) | Tavily検索APIとLangGraphを使った調査レポート生成エージェント | 25 | | 10 | CRAG(Corrective RAG) | Cross-Encoderを使った検索結果の再ランキングとCRAGパターンの実装 | 26 | | 11 | ReAct Agent | create_react_agentを使った汎用的なリサーチエージェントの実装 | 27 | | 12 | ARAG(Adaptive RAG) | タスクに応じて検索戦略を動的に切り替えるAdaptive RAGの実装とLangSmith連携 | 28 | | 14 | Streamlitアプリケーション | Streamlitを使ったWebアプリケーションとLangGraphの連携 | 29 | | 16 | LangGraph Platform | LangGraph CLIとLangGraph Platformによるエージェントのデプロイ | 30 | | 17 | LangGraph Studio | LangGraph Studioを使ったエージェントの開発とデバッグ | 31 | | 18 | データ分析エージェント | Claudeを使ったチャート生成とデータセット作成エージェント(日本語フォント対応) | 32 | | 19 | MCP(Model Context Protocol)Server | MCPサーバーの実装とCursorエディタへの組み込み方法 | 33 | | 20 | MCPとLangGraphエージェントの連携 | create_react_agentとMCPサーバーの統合、SQLite操作の実装 | 34 | | 21 | LangGraphによるContent Creator | StreamlitとLangGraphを使ったX投稿作成エージェント、Human-in-the-Loopでのフィードバック改善 | 35 | | 22 | Streamlitと領収書OCRエージェント | Claude Vision APIを使った領収書OCRと会計処理ワークフロー、Human-in-the-Loop実装 | 36 | | 23 | SupervisorパターンとSwarmパターン | LangGraphのマルチエージェントパターン(Supervisor/Swarm)の実装と比較 | 37 | | 24 | LangGraphエージェントの実践 | TavilyとClaudeを使った対話型調査エージェント、デバッグモード実装 | 38 | | 25 | DSPyとMIPROv2によるチャットボット最適化 | MIPROv2を使った枝豆の妖精スタイルチャットボットのプロンプト自動最適化とMLflow連携 | 39 | | 26 | DSPy MIPROv2によるRAG最適化 | JQaRAデータセットを使ったRAGパイプラインのMIPROv2最適化(検索クエリリライト+回答生成) | 40 | | 27 | DSPy GEPAによるRAG最適化 | GEPAを使ったRAGパイプラインの最適化、Reflective feedbackによる改善 | 41 | | 28 | DSPy GEPAによるReActエージェント最適化 | ファイル探索エージェントのGEPA自動最適化、LLM as a Judge評価、Tool仕様保持の実装 | 42 | | 29 | LangChain v1.0 create_agent解説 | create_agentとミドルウェアの仕組みを解説するJupyter Notebook | 43 | | 30 | LangChain Middlewareデモ | LangChain 1.0のミドルウェア機能を活用した3つのデモ(PII保護、レジリエンス、ツール選択) | 44 | -------------------------------------------------------------------------------- /23/README.md: -------------------------------------------------------------------------------- 1 | # Software Design誌「実践LLMアプリケーション開発」第23回サンプルコード 2 | 3 | ## 概要 4 | 5 | このプロジェクトは、LangGraphのSupervisorパターンとSwarmパターンのシンプルなサンプル実装です。 6 | 7 | ## サンプルコードの実行方法 8 | 9 | ### プロジェクトのセットアップ 10 | 11 | ※ このプロジェクトは`uv`を使用しています。`uv`のインストール方法については[こちら](https://github.com/astral-sh/uv)をご確認ください。 12 | 13 | 以下のコマンドを実行し、必要なライブラリのインストールを行って下さい。 14 | 15 | ``` 16 | $ uv sync 17 | ``` 18 | 19 | 次に環境変数の設定を行います。まず`.env.sample`ファイルをコピーして`.env`ファイルを作成します。 20 | 21 | ``` 22 | $ cp .env.sample .env 23 | $ vi .env # お好きなエディタで編集してください 24 | ``` 25 | 26 | `.env`ファイルを編集し、以下のAPIキーを設定してください。 27 | 28 | ``` 29 | ANTHROPIC_API_KEY=your_anthropic_api_key_here 30 | LANGCHAIN_TRACING_V2=true 31 | LANGCHAIN_API_KEY=your_langsmith_key_here 32 | LANGCHAIN_PROJECT=sd-23 33 | ``` 34 | 35 | - `ANTHROPIC_API_KEY`: Claude APIのキー 36 | - `LANGCHAIN_API_KEY`: LangSmithのAPIキー(オプション) 37 | 38 | ### 実行方法 39 | 40 | LangGraph Studioでの実行: 41 | 42 | ```bash 43 | uv run langgraph dev 44 | ``` 45 | 46 | コマンドラインでの実行: 47 | 48 | ```bash 49 | # 両方のパターンを実行 50 | uv run python main.py 51 | 52 | # Supervisorパターンのみ実行 53 | uv run python main.py supervisor 54 | 55 | # Swarmパターンのみ実行 56 | uv run python main.py swarm 57 | ``` 58 | 59 | ## グラフの説明 60 | 61 | ### Supervisorパターン (`supervisor`) 62 | 63 | - **数学エージェント**: 数学的な計算(加算、乗算、除算)を処理 64 | - **調査エージェント**: 情報収集と調査タスクを処理(モック実装) 65 | - **Supervisor**: タスクを適切なエージェントに委譲 66 | 67 | 使用例: 68 | - "10と20を足して、その結果に5を掛けてください" 69 | - "人工知能について調べて、その後2の10乗を計算してください" 70 | 71 | ### Swarmパターン (`swarm`) 72 | 73 | - **FAQサポート**: よくある質問に対応 74 | - **技術サポート**: 技術的な問題を処理 75 | - エージェント間で動的にハンドオフ(制御の受け渡し)が可能 76 | 77 | 使用例: 78 | - "パスワードをリセットしたいです"(FAQ対応) 79 | - "システムがエラーを表示しています"(技術サポートへハンドオフ) 80 | 81 | ## プロジェクト構造 82 | 83 | ``` 84 | src/sd_23/ 85 | ├── agents/ 86 | │ ├── math_agent.py # 数学計算エージェント 87 | │ ├── research_agent.py # 調査エージェント 88 | │ ├── faq_agent.py # FAQサポートエージェント 89 | │ └── tech_agent.py # 技術サポートエージェント 90 | ├── supervisor_graph.py # Supervisorパターンのグラフ 91 | └── swarm_graph.py # Swarmパターンのグラフ 92 | ``` 93 | 94 | ## 使用モデル 95 | 96 | すべてのエージェントとSupervisorはAnthropic Claude 4 Sonnetを使用しています。 97 | 98 | ## main.pyの詳細 99 | 100 | `main.py`は、両方のパターンの動作確認用スクリプトです。 101 | 102 | - **Supervisorパターンのテストケース**: AI技術の市場調査と成長率計算 103 | - research_expertが最新のAI技術と市場規模を調査 104 | - math_expertが5年後の市場規模を計算 105 | 106 | - **Swarmパターンのテストケース**: エラーコード500の診断 107 | - faq_supportが初期対応 108 | - tech_supportが詳細な診断と解決策を提供 109 | 110 | 各エージェントの動作やツール呼び出しがリアルタイムで表示され、マルチエージェントシステムの協調動作を確認できます。 -------------------------------------------------------------------------------- /16/my_agent/agent.py: -------------------------------------------------------------------------------- 1 | from functools import lru_cache 2 | from typing import Annotated, ClassVar, Literal, Sequence 3 | 4 | from langchain_anthropic import ChatAnthropic 5 | from langchain_community.tools.tavily_search import TavilySearchResults 6 | from langchain_core.messages import BaseMessage 7 | from langchain_openai import ChatOpenAI 8 | from langgraph.graph import END, START, StateGraph, add_messages 9 | from langgraph.prebuilt import ToolNode 10 | from pydantic import BaseModel 11 | 12 | 13 | # グラフのステートを定義 14 | class AgentState(BaseModel): 15 | messages: Annotated[Sequence[BaseMessage], add_messages] 16 | 17 | 18 | # グラフの設定を定義 19 | class GraphConfig(BaseModel): 20 | ANTHROPIC: ClassVar[str] = "anthropic" 21 | OPENAI: ClassVar[str] = "openai" 22 | 23 | model_name: Literal["anthropic", "openai"] 24 | 25 | 26 | # モデルを取得する関数 27 | @lru_cache(maxsize=2) 28 | def _get_model(model_name: str) -> ChatOpenAI | ChatAnthropic: 29 | if model_name == GraphConfig.OPENAI: 30 | model = ChatOpenAI(temperature=0, model_name="gpt-4o") 31 | elif model_name == GraphConfig.ANTHROPIC: 32 | model = ChatAnthropic(temperature=0, model_name="claude-3-5-sonnet-20241022") 33 | else: 34 | raise ValueError(f"サポートしていないモデルです: {model_name}") 35 | 36 | model = model.bind_tools(tools) 37 | return model 38 | 39 | 40 | # ツール実行のためのノードを定義 41 | tools = [TavilySearchResults(max_results=1)] 42 | tool_node = ToolNode(tools) 43 | 44 | 45 | # ツール実行の継続条件を定義 46 | def should_continue(state: AgentState) -> Literal["end", "continue"]: 47 | last_message = state.messages[-1] 48 | return "continue" if last_message.tool_calls else "end" 49 | 50 | 51 | # モデルを実行する関数 52 | def call_model(state: AgentState, config: GraphConfig) -> dict: 53 | messages = state.messages 54 | messages = [ 55 | {"role": "system", "content": "You are a helpful assistant."} 56 | ] + messages 57 | # configの情報からモデル名を取得 58 | model_name = config.get("configurable", {}).get("model_name", "anthropic") 59 | # モデル名から利用するモデルのインスタンスを取得 60 | model = _get_model(model_name) 61 | # モデルを実行 62 | response = model.invoke(messages) 63 | return {"messages": [response]} 64 | 65 | 66 | # グラフを定義 67 | workflow = StateGraph(AgentState, config_schema=GraphConfig) 68 | 69 | # ノードを追加 70 | workflow.add_node("call_model", call_model) 71 | workflow.add_node("tool_node", tool_node) 72 | 73 | # エッジを追加 74 | workflow.add_edge(START, "call_model") 75 | workflow.add_conditional_edges( 76 | "call_model", 77 | should_continue, 78 | { 79 | "continue": "tool_node", 80 | "end": END, 81 | }, 82 | ) 83 | workflow.add_edge("tool_node", "call_model") 84 | 85 | graph = workflow.compile() 86 | -------------------------------------------------------------------------------- /03/chatbot.py: -------------------------------------------------------------------------------- 1 | from langchain.embeddings.openai import OpenAIEmbeddings 2 | from langchain.vectorstores import Chroma 3 | from langchain.chains import RetrievalQAWithSourcesChain 4 | from langchain.chat_models import ChatOpenAI 5 | from langchain.prompts import ChatPromptTemplate 6 | from langchain.memory import ConversationBufferMemory 7 | import chainlit as cl 8 | 9 | # sk...の部分を自身のAPIキーに置き換える 10 | # openai.api_key = "sk-..." 11 | 12 | # 現在の日付をYYYY年MM月DD日の形式で取得する 13 | from datetime import datetime 14 | now = datetime.now() 15 | date = now.strftime("%Y年%m月%d日") 16 | 17 | SYSTEM_MESSAGE = f"""あなたは以下のリストに完全に従って行動するAIです。 18 | ・あなたは「枝豆の妖精」という設定です。 19 | ・現在の日付は{date}です。 20 | ・2023年の情報について答える妖精です。 21 | ・関連情報が与えられた場合は、それを基に回答してください。そのまま出力するのではなく、小学生にも分かるようにフランクにアレンジして回答してください。 22 | ・私はAIなので分かりません、という言い訳は不要です。 23 | ・敬語ではなくフランクな口語で話してください。 24 | ・必ず一人称は「ボク」で、語尾に「なのだ」をつけて話してください。 25 | 26 | それでは始めて下さい! 27 | ---------------- 28 | {{summaries}}""" 29 | 30 | 31 | # チャットセッション開始時に実行 32 | @cl.on_chat_start 33 | def chat_start() -> None: 34 | # 会話履歴を保持するメモリを作成 35 | memory = ConversationBufferMemory( 36 | memory_key="chat_history", 37 | input_key='question', 38 | output_key='answer', 39 | return_messages=True 40 | ) 41 | # チャットプロンプトを作成 42 | prompt = ChatPromptTemplate.from_messages([ 43 | ("system", SYSTEM_MESSAGE), 44 | ("human", "{question}") 45 | ]) 46 | # データ抽出元のベクトルデータベースの設定 47 | embeddings = OpenAIEmbeddings() 48 | docsearch = Chroma( 49 | persist_directory="./data", 50 | collection_name="events_2023", 51 | embedding_function=embeddings 52 | ) 53 | # データソースから関連情報を抽出して返すチェインを定義 54 | chain = RetrievalQAWithSourcesChain.from_chain_type( 55 | ChatOpenAI(temperature=0.0), 56 | chain_type="stuff", 57 | retriever=docsearch.as_retriever(), 58 | memory=memory, 59 | return_source_documents=True, 60 | chain_type_kwargs={"prompt": prompt}, 61 | ) 62 | # ユーザーセッションにチェインを保存 63 | cl.user_session.set("chain", chain) 64 | 65 | 66 | # ユーザーメッセージ受信時に実行 67 | @cl.on_message 68 | async def main(message: str) -> None: 69 | # ユーザーセッションからチェインを取得 70 | chain = cl.user_session.get("chain") 71 | 72 | # チェインにユーザーメッセージを渡して回答を取得 73 | response = await chain.acall(message) 74 | 75 | # 回答と関連情報を取得 76 | answer = response["answer"] 77 | sources = response["source_documents"] 78 | 79 | # 関連情報がある場合は、関連情報を表示 80 | if len(sources) > 0: 81 | contents = [source.page_content for source in sources] 82 | await cl.Message(author="relevant", content="\n\n".join(contents), indent=1).send() 83 | 84 | # チャット上にChatGPTからの返信を表示 85 | await cl.Message(content=answer).send() 86 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | pip-wheel-metadata/ 25 | share/python-wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | MANIFEST 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | 52 | # Translations 53 | *.mo 54 | *.pot 55 | 56 | # Django stuff: 57 | *.log 58 | local_settings.py 59 | db.sqlite3 60 | db.sqlite3-journal 61 | media 62 | 63 | # Flask stuff: 64 | instance/ 65 | .webassets-cache 66 | 67 | # Scrapy stuff: 68 | .scrapy 69 | 70 | # Sphinx documentation 71 | docs/_build/ 72 | 73 | # PyBuilder 74 | target/ 75 | 76 | # IPython Notebook 77 | .ipynb_checkpoints 78 | 79 | # PyCharm 80 | *.iml 81 | .idea/ 82 | .idea_modules/ 83 | *.pyc 84 | 85 | # dotenv 86 | .env 87 | .env.local 88 | 89 | # Local .terraform directories 90 | **/.terraform/* 91 | 92 | # .tfstate files 93 | *.tfstate 94 | *.tfstate.* 95 | 96 | # Crash log files 97 | crash.log 98 | 99 | # Ignore any .tfvars files that are automatically generated for each Terraform run. 100 | *.auto.tfvars 101 | *.auto.tfvars.json 102 | *.tfvars.json 103 | 104 | # Ignore override files as they are usually used to override resources locally and so 105 | # are not checked in 106 | override.tf 107 | override.tf.json 108 | *_override.tf 109 | *_override.tf.json 110 | 111 | # Ignore CLI configuration files 112 | .terraformrc 113 | terraform.rc 114 | 115 | # Ignore the .terraform.lock.hcl file, which is used to record the versions of 116 | # modules installed from the Terraform Registry 117 | .terraform.lock.hcl 118 | 119 | # Ignore the .terraform/providers directory, which contains the installed provider 120 | # binaries for each configured provider 121 | .terraform/providers 122 | 123 | # Ignore the .terraform/plugins directory, which contains the installed plugin 124 | # binaries for each provider 125 | .terraform/plugins 126 | 127 | .pytest_cache 128 | .venv 129 | venv/ 130 | 131 | .chainlit 132 | chainlit.md 133 | 134 | .claude/ 135 | CLAUDE.md 136 | 137 | # macOS system files 138 | .DS_Store 139 | 140 | tmp/ 141 | repomix.config.json 142 | -------------------------------------------------------------------------------- /19/sd_19/server.py: -------------------------------------------------------------------------------- 1 | import os 2 | from typing import List 3 | 4 | from mcp.server.fastmcp import FastMCP 5 | from mcp.server.fastmcp.prompts.base import AssistantMessage, Message, UserMessage 6 | 7 | # 定数定義 8 | DEFAULT_HOST = "0.0.0.0" 9 | DEFAULT_PORT = 8080 10 | 11 | mcp = FastMCP("sd-19-mcp") 12 | 13 | 14 | @mcp.tool() 15 | def hello(name: str) -> str: 16 | """ 17 | 与えられた名前に対して挨拶を返します。 18 | """ 19 | return f"Hello, {name}!" 20 | 21 | 22 | @mcp.prompt() 23 | def greet_user(user_name: str) -> List[Message]: 24 | """ 25 | ユーザーに挨拶するための定型プロンプトを返します。 26 | 返値はMCPで規定されたメッセージのリストです。 27 | """ 28 | return [ 29 | UserMessage(content=f"{user_name}さん、こんにちは。"), 30 | AssistantMessage(content="どのようにお手伝いできますか?"), 31 | ] 32 | 33 | 34 | @mcp.resource("file://readme.md") 35 | def readme() -> str: 36 | """ 37 | README.md を読み込んで返します。 38 | """ 39 | current_dir = os.path.dirname(os.path.abspath(__file__)) 40 | readme_path = os.path.normpath(os.path.join(current_dir, "..", "README.md")) 41 | 42 | if not os.path.exists(readme_path): 43 | raise FileNotFoundError(f"README.md not found at: {readme_path}") 44 | 45 | with open(readme_path, "r") as f: 46 | content = f.read() 47 | return content 48 | 49 | 50 | def start_server( 51 | transport: str, host: str = DEFAULT_HOST, port: int = DEFAULT_PORT 52 | ) -> None: 53 | """MCPサーバーを起動する 54 | 55 | Args: 56 | transport (str): 使用するトランスポートモード ('stdio' または 'sse') 57 | host (str, optional): SSEモード時のホスト名. デフォルトは DEFAULT_HOST 58 | port (int, optional): SSEモード時のポート番号. デフォルトは DEFAULT_PORT 59 | """ 60 | try: 61 | if transport == "stdio": 62 | mcp.run(transport="stdio") 63 | elif transport == "sse": 64 | mcp.run(transport="sse", host=host, port=port) 65 | else: 66 | raise ValueError(f"不正なトランスポートモード: {transport}") 67 | except Exception as e: 68 | print(f"サーバー起動エラー: {e}") 69 | raise 70 | 71 | 72 | if __name__ == "__main__": 73 | import argparse 74 | 75 | parser = argparse.ArgumentParser(description="MCPサーバーの起動モードを指定") 76 | parser.add_argument( 77 | "--transport", 78 | type=str, 79 | choices=["stdio", "sse"], 80 | default="stdio", 81 | help="使用するトランスポートモード (stdio または sse)", 82 | ) 83 | parser.add_argument( 84 | "--host", 85 | type=str, 86 | default=DEFAULT_HOST, 87 | help="ホスト名", 88 | ) 89 | parser.add_argument( 90 | "--port", 91 | type=int, 92 | default=DEFAULT_PORT, 93 | help="ポート番号", 94 | ) 95 | args = parser.parse_args() 96 | start_server( 97 | transport=args.transport, 98 | host=args.host, 99 | port=args.port, 100 | ) 101 | -------------------------------------------------------------------------------- /17/my_agent/task_executor_agent.py: -------------------------------------------------------------------------------- 1 | import operator 2 | from typing import Annotated, Sequence, TypedDict 3 | 4 | from langchain_community.tools.tavily_search import TavilySearchResults 5 | from langchain_openai import ChatOpenAI 6 | from langgraph.graph import END, START, StateGraph 7 | from langgraph.graph.state import CompiledStateGraph 8 | from langgraph.prebuilt import create_react_agent 9 | from langgraph.types import Send 10 | 11 | 12 | class TaskExecutorAgentInputState(TypedDict): 13 | tasks: list[str] 14 | 15 | 16 | class TaskExecutorAgentOutputState(TypedDict): 17 | results: Annotated[Sequence[str], operator.add] 18 | 19 | 20 | class TaskExecutorAgentState(TaskExecutorAgentInputState, TaskExecutorAgentOutputState): 21 | pass 22 | 23 | 24 | # 並列ノードに値を受け渡すためのState 25 | class ParallelState(TypedDict): 26 | task: str 27 | 28 | 29 | class TaskExecutor: 30 | def __init__(self, llm: ChatOpenAI): 31 | self.llm = llm 32 | self.tools = [TavilySearchResults(max_results=3)] 33 | 34 | def __call__(self, state: ParallelState) -> dict: 35 | return {"results": [self.run(state.get("task", ""))]} 36 | 37 | def run(self, task: str) -> str: 38 | messages = { 39 | "messages": [ 40 | ( 41 | "human", 42 | f"次のタスクを実行し、詳細な回答を提供してください。\n\nタスク: {task}\n\n" 43 | "要件:\n" 44 | "1. 必要に応じて提供されたツールを使用してください。\n" 45 | "2. 実行は徹底的かつ包括的に行ってください。\n" 46 | "3. 可能な限り具体的な事実やデータを提供してください。\n" 47 | "4. 発見した内容を明確に要約してください。\n", 48 | ) 49 | ] 50 | } 51 | agent = create_react_agent(self.llm, self.tools) 52 | result = agent.invoke(messages) 53 | return result["messages"][-1].content 54 | 55 | 56 | class TaskExecutorAgent: 57 | def __init__(self, llm: ChatOpenAI) -> None: 58 | self.llm = llm 59 | self.task_executor = TaskExecutor(self.llm) 60 | self.graph = self._create_graph() 61 | 62 | def _create_graph(self) -> CompiledStateGraph: 63 | graph = StateGraph( 64 | state_schema=TaskExecutorAgentState, 65 | input=TaskExecutorAgentInputState, 66 | output=TaskExecutorAgentOutputState, 67 | ) 68 | graph.add_node("execute_task", self.task_executor) 69 | graph.add_conditional_edges( 70 | START, self.routing_parallel_nodes, ["execute_task"] 71 | ) 72 | graph.add_edge("execute_task", END) 73 | 74 | return graph.compile() 75 | 76 | def routing_parallel_nodes(self, state: TaskExecutorAgentInputState) -> list[Send]: 77 | return [Send("execute_task", {"task": task}) for task in state.get("tasks", [])] 78 | 79 | 80 | def create_agent(llm: ChatOpenAI) -> CompiledStateGraph: 81 | return TaskExecutorAgent(llm).graph 82 | 83 | 84 | graph = create_agent(ChatOpenAI(model="gpt-4o-mini")) 85 | -------------------------------------------------------------------------------- /24/src/sd_24/utils/todo_manager.py: -------------------------------------------------------------------------------- 1 | """TODOタスク管理モジュール""" 2 | 3 | from typing import Any, Optional 4 | from datetime import datetime 5 | from enum import Enum 6 | from pydantic import BaseModel, Field 7 | 8 | 9 | class TaskStatus(Enum): 10 | """タスクのステータス""" 11 | 12 | PENDING = "pending" 13 | IN_PROGRESS = "in_progress" 14 | COMPLETED = "completed" 15 | FAILED = "failed" 16 | 17 | 18 | class TodoItem(BaseModel): 19 | """TODOアイテム""" 20 | 21 | id: str = Field(..., description="タスクID") 22 | description: str = Field(..., description="タスクの説明") 23 | agent: str = Field(..., description="担当エージェント") 24 | parent_id: Optional[str] = Field(None, description="親タスクID") 25 | status: TaskStatus = Field( 26 | default=TaskStatus.PENDING, description="タスクのステータス" 27 | ) 28 | created_at: datetime = Field(default_factory=datetime.now, description="作成日時") 29 | updated_at: datetime = Field(default_factory=datetime.now, description="更新日時") 30 | result: Optional[Any] = Field(None, description="タスクの結果") 31 | 32 | class Config: 33 | use_enum_values = False 34 | json_encoders = {datetime: lambda v: v.isoformat()} 35 | 36 | def to_dict(self) -> dict[str, Any]: 37 | """Pydanticモデルを辞書に変換""" 38 | data = self.model_dump() 39 | # statusを文字列に変換 40 | data["status"] = self.status.value 41 | # datetimeをISOフォーマットに変換 42 | data["created_at"] = self.created_at.isoformat() 43 | data["updated_at"] = self.updated_at.isoformat() 44 | return data 45 | 46 | 47 | class TodoManager: 48 | """TODO管理クラス""" 49 | 50 | def __init__(self): 51 | self.todos: dict[str, TodoItem] = {} 52 | self.task_counter = 0 53 | 54 | def add_task( 55 | self, description: str, agent: str, parent_id: Optional[str] = None 56 | ) -> str: 57 | """タスクを追加""" 58 | self.task_counter += 1 59 | task_id = f"TASK-{self.task_counter:04d}" 60 | todo = TodoItem( 61 | id=task_id, description=description, agent=agent, parent_id=parent_id, result=None 62 | ) 63 | self.todos[task_id] = todo 64 | return task_id 65 | 66 | def update_status(self, task_id: str, status: TaskStatus, result: Any = None): 67 | """タスクのステータスを更新""" 68 | if task_id in self.todos: 69 | self.todos[task_id].status = status 70 | self.todos[task_id].updated_at = datetime.now() 71 | if result: 72 | self.todos[task_id].result = result 73 | 74 | def get_pending_tasks(self, agent: Optional[str] = None) -> list[TodoItem]: 75 | """未完了タスクを取得""" 76 | tasks = [] 77 | for todo in self.todos.values(): 78 | if todo.status == TaskStatus.PENDING: 79 | if agent is None or todo.agent == agent: 80 | tasks.append(todo) 81 | return tasks 82 | 83 | 84 | 85 | # シングルトンインスタンス 86 | todo_manager = TodoManager() 87 | -------------------------------------------------------------------------------- /22/src/receipt_processor/models.py: -------------------------------------------------------------------------------- 1 | """ 2 | データモデル定義 3 | """ 4 | 5 | from enum import Enum 6 | from typing import List 7 | 8 | from pydantic import BaseModel, Field 9 | 10 | 11 | class WorkflowState(str, Enum): 12 | """ワークフローの状態定義(UI)""" 13 | 14 | IDLE = "idle" # 初期状態、ユーザーがアクション(画像アップロードなど)を実行する前の待機状態 15 | PROCESSING = "processing" # データ処理中の状態(OCR処理や勘定科目の判定中など) 16 | WAIT_FEEDBACK = "feedback" # ユーザーからのフィードバック入力待ち状態 17 | WORKFLOW_COMPLETED = "complete" # 処理が完了し、結果が保存された状態 18 | OCR_COMPLETED = "ocr_complete" # OCR処理が完了した状態 19 | ACCOUNT_SUGGESTED = "account_suggested" # 会計処理の提案が完了した状態 20 | ERROR = "error" # エラーが発生した状態 21 | 22 | 23 | class DisplayMode(str, Enum): 24 | """表示モード定義(UI)""" 25 | 26 | INPUT = "input" # 領収書入力モード 27 | HISTORY = "history" # 履歴表示モード 28 | 29 | 30 | class EventType(str, Enum): 31 | """イベントタイプ定義(ワークフロー)""" 32 | 33 | OCR_DONE = "ocr_done" 34 | ACCOUNT_SUGGESTED = "account_suggested" 35 | SAVE_COMPLETED = "save_completed" 36 | ERROR = "error" 37 | 38 | 39 | class CommandType(str, Enum): 40 | """コマンドタイプ定義(ワークフロー)""" 41 | 42 | APPROVE = "approve" 43 | REGENERATE = "regenerate" 44 | 45 | 46 | class Feedback(BaseModel): 47 | """フィードバックデータ(UI)""" 48 | 49 | command: CommandType 50 | content: str 51 | 52 | 53 | class ReceiptItem(BaseModel): 54 | """領収書の購入品目を表すモデル""" 55 | 56 | name: str = Field(description="商品名や項目名") 57 | price: str = Field(description="金額(数字のみ)") 58 | 59 | 60 | class ReceiptInfoItem(BaseModel): 61 | """領収書のその他情報を表すモデル""" 62 | 63 | key: str = Field(description="情報の種類(例: 支払方法、伝票番号など)") 64 | value: str = Field(description="情報の値") 65 | 66 | 67 | class ReceiptOCRResult(BaseModel): 68 | """OCR結果の構造化データモデル""" 69 | 70 | raw_text: str = Field(description="領収書から抽出された生テキスト") 71 | date: str = Field( 72 | description="領収書の日付(YYYY-MM-DD形式、不明な場合は空文字列)" 73 | ) 74 | amount: str = Field( 75 | description="金額(数字のみ、カンマなし、不明な場合は空文字列)" 76 | ) 77 | shop_name: str = Field(description="店舗・発行元名称(不明な場合は空文字列)") 78 | items: List[ReceiptItem] = Field( 79 | description="購入品目のリスト(ある場合のみ)", default_factory=list 80 | ) 81 | other_info: List[ReceiptInfoItem] = Field( 82 | description="その他抽出できた情報(領収書番号、支払方法など)", 83 | default_factory=list, 84 | ) 85 | 86 | 87 | class AccountInfo(BaseModel): 88 | """勘定科目情報のデータモデル(CSVにも保存される)""" 89 | 90 | date: str = Field(description="日付(YYYY-MM-DD形式)") 91 | account: str = Field(description="勘定科目") 92 | sub_account: str = Field(description="補助科目", default="") 93 | amount: str = Field(description="金額", default="") 94 | tax_amount: str = Field(description="消費税額", default="") 95 | vendor: str = Field(description="取引先", default="") 96 | invoice_number: str = Field( 97 | description="インボイス番号(通常Tから始まる文字列)", default="" 98 | ) 99 | description: str = Field(description="摘要", default="") 100 | reason: str = Field(description="この勘定科目と判断した理由") 101 | -------------------------------------------------------------------------------- /17/my_agent/agent.py: -------------------------------------------------------------------------------- 1 | import operator 2 | from datetime import datetime 3 | from typing import Annotated, Callable, Sequence, TypedDict 4 | 5 | from langchain_core.messages import BaseMessage 6 | from langchain_core.output_parsers import StrOutputParser 7 | from langchain_core.prompts import ChatPromptTemplate 8 | from langchain_openai import ChatOpenAI 9 | from langgraph.checkpoint.memory import MemorySaver 10 | from langgraph.graph import END, START, StateGraph, add_messages 11 | from langgraph.graph.state import CompiledStateGraph 12 | from my_agent.task_executor_agent import \ 13 | create_agent as create_task_executor_agent 14 | from my_agent.task_planner_agent import \ 15 | create_agent as create_task_planner_agent 16 | 17 | 18 | class AgentInputState(TypedDict): 19 | messages: Annotated[Sequence[BaseMessage], add_messages] 20 | 21 | 22 | class AgentPrivateState(TypedDict): 23 | tasks: list[str] 24 | results: list[str] 25 | 26 | 27 | class AgentOutputState(TypedDict): 28 | final_output: str 29 | 30 | 31 | class AgentState(AgentInputState, AgentPrivateState, AgentOutputState): 32 | pass 33 | 34 | 35 | class Reporter: 36 | def __init__(self, llm: ChatOpenAI): 37 | self.llm = llm 38 | 39 | def __call__(self, state: AgentPrivateState) -> dict: 40 | return {"final_output": self.run(state.get("results", []))} 41 | 42 | def run(self, results: list[str]) -> str: 43 | prompt = ChatPromptTemplate.from_template( 44 | "### 調査結果\n" 45 | "{results}\n\n" 46 | "### タスク\n" 47 | "調査結果に基づき、調査結果の内容を漏れなく整理したレポートを作成してください。" 48 | ) 49 | results_str = "\n\n".join( 50 | f"Info {i+1}:\n{result}" for i, result in enumerate(results) 51 | ) 52 | chain = prompt | self.llm | StrOutputParser() 53 | return chain.invoke({"results": results_str}) 54 | 55 | 56 | class Agent: 57 | def __init__(self, llm: ChatOpenAI) -> None: 58 | self.llm = llm 59 | self.reporter = Reporter(llm) 60 | self.graph = self._create_graph() 61 | 62 | def _create_graph(self) -> CompiledStateGraph: 63 | graph = StateGraph( 64 | state_schema=AgentState, 65 | input=AgentInputState, 66 | output=AgentOutputState, 67 | ) 68 | 69 | graph.add_node("task_planner", create_task_planner_agent(self.llm)) 70 | graph.add_node("task_executor", create_task_executor_agent(self.llm)) 71 | graph.add_node("reporter", self.reporter) 72 | 73 | graph.add_edge(START, "task_planner") 74 | graph.add_edge("task_planner", "task_executor") 75 | graph.add_edge("task_executor", "reporter") 76 | graph.add_edge("reporter", END) 77 | 78 | return graph.compile(checkpointer=MemorySaver()) 79 | 80 | 81 | def create_agent(llm: ChatOpenAI) -> CompiledStateGraph: 82 | return Agent(llm).graph 83 | 84 | 85 | graph = create_agent(ChatOpenAI(model="gpt-4o-mini")) 86 | 87 | if __name__ == "__main__": 88 | png = graph.get_graph(xray=2).draw_mermaid_png() 89 | with open("graph.png", "wb") as f: 90 | f.write(png) 91 | -------------------------------------------------------------------------------- /20/README.md: -------------------------------------------------------------------------------- 1 | # Software Design誌「実践LLMアプリケーション開発」第20回サンプルコード 2 | 3 | ## サンプルコードの実行方法 4 | 5 | ### プロジェクトのセットアップ 6 | 7 | ※ このプロジェクトは`uv`を使用しています。`uv`のインストール方法については[こちら](https://github.com/astral-sh/uv)をご確認ください。 8 | 9 | 以下のコマンドを実行し、必要なライブラリのインストールを行って下さい。 10 | 11 | ``` 12 | $ uv sync 13 | ``` 14 | 15 | 次に環境変数の設定を行います。まず`.env.sample`ファイルをコピーして`.env`ファイルを作成します。 16 | 17 | ``` 18 | $ cp .env.sample .env 19 | $ vi .env # お好きなエディタで編集してください 20 | ``` 21 | 22 | 続けて.envファイル内の以下のキーを設定して下さい。`TAVILY_API_KEY`ならびに`LANGSMITH_API_KEY`の取得方法については次節で解説します。 23 | 24 | ``` 25 | ANTHROPIC_API_KEY=[発行されたAPIキー] 26 | TAVILY_API_KEY=[発行されたAPIキー] 27 | LANGSMITH_API_KEY=[発行されたAPIキー] 28 | ``` 29 | 30 | ## 実行方法 31 | 32 | 以下のコマンドを実行することで、エージェントを実行できます。 33 | MCPサーバは自動的に起動されるため、事前に別途起動する必要はありません。 34 | 35 | ```bash 36 | # エージェントの実行(コマンドライン引数でエージェントへの依頼を設定) 37 | uv run -m src.sd_20 "LangChainの最新情報を教えてください" 38 | ``` 39 | 40 | また、LangGraph Studioを利用して動作確認をすることもできます。 41 | LangGraph Studioを利用する場合は、次のコマンドでサーバを立ち上げてください。 42 | 43 | ```bash 44 | uv run langgraph dev 45 | ``` 46 | 47 | ## mcp_config.jsonの設定 48 | 49 | このサンプルコードでは、MCPサーバの設定を`mcp_config.json`ファイルで管理しています。デフォルトでは以下のような設定になっています: 50 | 51 | ```json 52 | { 53 | "mcpServers": { 54 | "knowledge-db": { 55 | "command": "uv", 56 | "args": ["run", "-m", "src.mcp_servers.server"] 57 | } 58 | } 59 | } 60 | ``` 61 | 62 | ## サンプルコードの内容 63 | 64 | 本サンプルコードでは、MCPサーバとLangGraphエージェント(`create_react_agent`)との連携を実装しています。 65 | 66 | 主要なコンポーネント: 67 | - `src/sd_20/mcp_manager.py`: MCPサーバからツールをロードし、LangGraphエージェントで使えるようにする 68 | - `src/sd_20/agent.py`: `create_react_agent`を使用したエージェントの定義 69 | - `src/mcp_servers/database.py`: SQLiteの操作を行うモジュール 70 | - `src/mcp_servers/server.py`: MCPサーバの実装 71 | 72 | ## Tavily APIキーの取得方法 73 | 74 | ### Tavilyについて 75 | 76 | Tavilyは、LLMアプリケーションにおけるRAG(Retrieval-Augmented Generation)に特化した検索エンジンです。 77 | 78 | 通常の検索APIはユーザーのクエリに基づいて検索結果を取得しますが、検索の目的に対して無関係な結果が返ってくることがあります。また、単純なURLやスニペットが返されるため、開発者が関連するコンテンツをスクレイピングしたり、不要な情報をフィルタリングしたりする必要があります。 79 | 80 | 一方でTavilyは、一回のAPI呼び出しで20以上のサイトを集約し、独自のAIを利用しタスクやクエリ、目的に最も関連する情報源とコンテンツを評価、フィルタリング、ランク付けすることが可能な仕組みになっています。 81 | 82 | 今回のサンプルコードを実行するためにはTavilyのAPIキーが必要なので、以下の手段でAPIキーを取得してください。 83 | 84 | ### Tavily APIキーの取得 85 | 86 | 1. まず https://tavily.com/ にアクセスし、画面右上の「Try it out」をクリックします。 87 | 2. するとサインイン画面に遷移するので、そのままサインインを進めて下さい。GoogleアカウントかGitHubアカウントを選択することができます。 88 | 3. サインインが完了するとAPIキーが取得できる画面に遷移します。画面中央部の「API Key」欄より、APIキーをコピーしてください。 89 | 4. APIキーを`.env`内の`TAVILY_API_KEY`に設定してください。 90 | 91 | ## LangSmith APIキーの取得方法 92 | 93 | LangSmithはLLM(大規模言語モデル)実行のログ分析ツールです。LLMアプリケーションの実行を詳細に追跡し、デバッグや評価を行うための機能を提供します。詳細については https://docs.smith.langchain.com/ をご確認ください。 94 | 95 | LangSmithによるトレースを有効にするためには、LangSmithにユーザー登録した上で、APIキーを発行する必要があります。以下の手順を参考にAPIキーを取得してください。 96 | 97 | ### LangSmith APIキーの取得 98 | 99 | 1. [サインアップページ](https://smith.langchain.com/)より、LangSmithのユーザー登録を行います。 100 | 2. [設定ページ](https://smith.langchain.com/settings)を開きます。 101 | 3. API keysタブを開き、「Create API Key」をクリックします。するとAPIキーが発行されますので、APIキーをコピーしてください。 102 | 4. APIキーを`.env`内の`LANGCHAIN_API_KEY`に設定してください。 103 | -------------------------------------------------------------------------------- /24/src/sd_24/utils/todo_tools.py: -------------------------------------------------------------------------------- 1 | from langchain_core.tools import tool 2 | from langchain_core.runnables import Runnable 3 | from .todo_manager import todo_manager, TaskStatus 4 | from typing import Optional 5 | from pydantic import BaseModel, Field 6 | 7 | 8 | class TodoTaskInput(BaseModel): 9 | """TODO作成用の入力データモデル""" 10 | 11 | description: str = Field(..., description="タスクの説明") 12 | agent: str = Field(..., description="担当エージェント (research または writer)") 13 | parent_task_id: Optional[str] = Field( 14 | default=None, description="親タスクID(オプション)" 15 | ) 16 | 17 | 18 | class TodoStatusUpdate(BaseModel): 19 | """TODOステータス更新用の入力データモデル""" 20 | 21 | task_id: str = Field(..., description="タスクID") 22 | completed: bool = Field(..., description="完了フラグ") 23 | result: Optional[str] = Field(default="", description="実行結果(オプション)") 24 | 25 | 26 | def create_get_my_todos_for_agent(agent_name: str) -> Runnable: 27 | """特定のエージェント用のget_my_todos関数を作成""" 28 | 29 | @tool # No return_direct - part of sequential flow 30 | def get_agent_todos() -> dict: 31 | """このエージェントの未完了TODOを取得""" 32 | pending_tasks = todo_manager.get_pending_tasks(agent_name) 33 | return { 34 | "tasks": [task.to_dict() for task in pending_tasks], 35 | "count": len(pending_tasks), 36 | } 37 | 38 | # 関数名とドキュメントを動的に設定 39 | get_agent_todos.name = f"get_{agent_name}_todos" # type: ignore 40 | # type: ignore 41 | get_agent_todos.description = f"{agent_name}エージェントの未完了TODOを取得" 42 | return get_agent_todos 43 | 44 | 45 | 46 | 47 | @tool 48 | def update_todo_status(update: TodoStatusUpdate) -> str: 49 | """TODOタスクのステータスを更新""" 50 | status = TaskStatus.COMPLETED if update.completed else TaskStatus.IN_PROGRESS 51 | todo_manager.update_status(update.task_id, status, update.result) 52 | return f"TODO {update.task_id} を{status.value}に更新しました" 53 | 54 | 55 | 56 | 57 | @tool 58 | def create_todo_task(task: TodoTaskInput) -> str: 59 | """TODOタスクを作成""" 60 | task_id = todo_manager.add_task( 61 | description=task.description, agent=task.agent, parent_id=task.parent_task_id 62 | ) 63 | return f"TODOタスク作成: {task_id} - {task.description}" 64 | 65 | 66 | @tool 67 | async def create_multiple_todos(tasks: list[TodoTaskInput]) -> str: 68 | """複数のTODOタスクを一度に作成""" 69 | created_tasks = [] 70 | for task in tasks: 71 | task_id = todo_manager.add_task( 72 | description=task.description, 73 | agent=task.agent, 74 | parent_id=task.parent_task_id, 75 | ) 76 | created_tasks.append(f"{task_id}: {task.description} ({task.agent})") 77 | 78 | return f"{len(created_tasks)}件のTODOタスクを作成しました:\n" + "\n".join(created_tasks) 79 | 80 | 81 | @tool 82 | async def update_multiple_todo_status(updates: list[TodoStatusUpdate]) -> str: 83 | """複数のTODOタスクのステータスを一度に更新""" 84 | for update in updates: 85 | status = TaskStatus.COMPLETED if update.completed else TaskStatus.IN_PROGRESS 86 | todo_manager.update_status(update.task_id, status, update.result) 87 | return f"{len(updates)}件のタスクを更新しました" 88 | -------------------------------------------------------------------------------- /06/old_chain_example.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 16, 6 | "id": "initial_id", 7 | "metadata": { 8 | "collapsed": true, 9 | "ExecuteTime": { 10 | "end_time": "2024-01-17T03:07:55.798673Z", 11 | "start_time": "2024-01-17T03:07:54.407588Z" 12 | } 13 | }, 14 | "outputs": [ 15 | { 16 | "name": "stdout", 17 | "output_type": "stream", 18 | "text": [ 19 | "犬,わんちゃん,ドッグパーク\n" 20 | ] 21 | } 22 | ], 23 | "source": [ 24 | "from langchain_openai import ChatOpenAI\n", 25 | "from langchain_core.prompts import ChatPromptTemplate\n", 26 | "\n", 27 | "def generate_keywords_chain(topic, num):\n", 28 | " prompt = ChatPromptTemplate.from_messages([\n", 29 | " (\"system\",\n", 30 | " \"You are an AI that returns {num} elements for things associated with topic. The elements MUST be in Japanese and MUST be returned in a comma-separated format that can be parsed as a CSV.\"),\n", 31 | " (\"human\", \"{topic}\")\n", 32 | " ])\n", 33 | " model = ChatOpenAI(model=\"gpt-3.5-turbo\")\n", 34 | " return model.invoke(prompt.invoke({\"topic\": topic, 'num': num}))\n", 35 | "\n", 36 | "result = generate_keywords_chain(\"dog\", 3)\n", 37 | "print(result.content)" 38 | ] 39 | }, 40 | { 41 | "cell_type": "code", 42 | "outputs": [ 43 | { 44 | "name": "stdout", 45 | "output_type": "stream", 46 | "text": [ 47 | "ビジネスプラン:犬のためのドッグパークを作りましょう。このドッグパークは、わんちゃんを飼っている人々にとっての憩いの場となります。パーク内には広い敷地を確保し、さまざまな遊び場や障害物を設置します。また、専門のトレーナーが常駐し、わんちゃんたちの訓練やトリックの指導を行います。さらに、ドッグカフェやショップも併設し、飼い主たちがくつろぎながらわんちゃん用品を購入できるようにします。このビジネスは、犬を飼っている人々の需要に応え、彼らのコミュニティをサポートします。\n" 48 | ] 49 | } 50 | ], 51 | "source": [ 52 | "def generate_business_plan_chain(ideas):\n", 53 | " prompt = ChatPromptTemplate.from_messages([\n", 54 | " (\"system\",\n", 55 | " \"You are an AI that returns business plan within 100 words. The ideas MUST be in Japanese.\"),\n", 56 | " (\"human\", \"{ideas}\")\n", 57 | " ])\n", 58 | " model = ChatOpenAI(model=\"gpt-3.5-turbo\")\n", 59 | " return model.invoke(prompt.invoke({\"ideas\": ideas}))\n", 60 | "\n", 61 | "print(generate_business_plan_chain(result.content).content)" 62 | ], 63 | "metadata": { 64 | "collapsed": false, 65 | "ExecuteTime": { 66 | "end_time": "2024-01-17T03:22:12.080727Z", 67 | "start_time": "2024-01-17T03:22:00.535693Z" 68 | } 69 | }, 70 | "id": "ad9a79559b59d9cc", 71 | "execution_count": 20 72 | } 73 | ], 74 | "metadata": { 75 | "kernelspec": { 76 | "display_name": "Python 3", 77 | "language": "python", 78 | "name": "python3" 79 | }, 80 | "language_info": { 81 | "codemirror_mode": { 82 | "name": "ipython", 83 | "version": 2 84 | }, 85 | "file_extension": ".py", 86 | "mimetype": "text/x-python", 87 | "name": "python", 88 | "nbconvert_exporter": "python", 89 | "pygments_lexer": "ipython2", 90 | "version": "2.7.6" 91 | } 92 | }, 93 | "nbformat": 4, 94 | "nbformat_minor": 5 95 | } 96 | -------------------------------------------------------------------------------- /22/src/receipt_processor/storage.py: -------------------------------------------------------------------------------- 1 | """ 2 | CSV保存機能 3 | """ 4 | 5 | import csv 6 | import os 7 | from pathlib import Path 8 | from typing import Any, Dict, List 9 | 10 | import pandas as pd 11 | 12 | from src.receipt_processor.constants import CSV_FILE_PATH 13 | from src.receipt_processor.models import AccountInfo 14 | 15 | 16 | def save_to_csv(data: AccountInfo, csv_path: str = CSV_FILE_PATH) -> bool: 17 | """ 18 | データをCSVファイルに保存する 19 | 20 | Parameters: 21 | ----------- 22 | data: AccountInfo 23 | 保存するデータ(日付、金額、勘定科目情報など) 24 | csv_path: str 25 | CSVファイルのパス 26 | 27 | Returns: 28 | -------- 29 | bool 30 | 保存が成功したかどうか 31 | """ 32 | # 保存先ディレクトリが存在しない場合は作成 33 | csv_dir = os.path.dirname(csv_path) 34 | if not os.path.exists(csv_dir): 35 | os.makedirs(csv_dir) 36 | 37 | # ファイルが存在するかチェック 38 | file_exists = os.path.isfile(csv_path) 39 | 40 | # CSVに保存(新規作成または追記) 41 | try: 42 | with open(csv_path, mode="a", newline="", encoding="utf-8") as file: 43 | # AccountInfoのフィールド名を動的に取得 44 | fieldnames = list(AccountInfo.model_fields.keys()) 45 | writer = csv.DictWriter(file, fieldnames=fieldnames) 46 | 47 | # ファイルが存在しない場合はヘッダーを書き込む 48 | if not file_exists: 49 | writer.writeheader() 50 | 51 | # データを書き込む 52 | writer.writerow(data.model_dump()) 53 | 54 | return True 55 | except Exception as e: 56 | print(f"CSV保存エラー: {e}") 57 | return False 58 | 59 | 60 | def get_saved_receipts(csv_path: str = CSV_FILE_PATH) -> List[Dict[str, Any]]: 61 | """ 62 | 保存された領収書データを取得する 63 | 64 | Parameters: 65 | ----------- 66 | csv_path: str 67 | CSVファイルのパス 68 | 69 | Returns: 70 | -------- 71 | List[Dict[str, Any]] 72 | 保存されたデータのリスト 73 | """ 74 | if not os.path.exists(csv_path): 75 | return [] 76 | 77 | try: 78 | # pandasでCSVを読み込む 79 | df = pd.read_csv(csv_path, encoding="utf-8") 80 | 81 | # 古いフォーマットとの互換性のため、raw_textカラムが存在する場合は削除 82 | if "raw_text" in df.columns: 83 | df = df.drop(columns=["raw_text"]) 84 | 85 | # DataFrame -> Dict変換 86 | receipts = df.to_dict(orient="records") 87 | return receipts 88 | except Exception as e: 89 | print(f"CSV読み込みエラー: {e}") 90 | return [] 91 | 92 | 93 | def backup_csv(csv_path: str = CSV_FILE_PATH) -> bool: 94 | """ 95 | CSVファイルのバックアップを作成する 96 | 97 | Parameters: 98 | ----------- 99 | csv_path: str 100 | バックアップするCSVファイルのパス 101 | 102 | Returns: 103 | -------- 104 | bool 105 | バックアップが成功したかどうか 106 | """ 107 | if not os.path.exists(csv_path): 108 | return False 109 | 110 | try: 111 | # 元のファイルパスと拡張子を取得 112 | path = Path(csv_path) 113 | backup_path = path.with_name(f"{path.stem}_backup{path.suffix}") 114 | 115 | # ファイルをコピー 116 | import shutil 117 | 118 | shutil.copy2(csv_path, backup_path) 119 | return True 120 | except Exception as e: 121 | print(f"バックアップエラー: {e}") 122 | return False 123 | -------------------------------------------------------------------------------- /28/README.md: -------------------------------------------------------------------------------- 1 | # Software Design誌「実践LLMアプリケーション開発」第28回サンプルコード 2 | 3 | このサンプルコードでは、DSPy ReActベースのファイル探索エージェントをGEPA(Gradient-Estimation with Prompt Augmentation)により自動最適化します。 4 | 5 | ## Dev Container環境の起動 6 | 7 | このサンプルコードは、Dev Container環境での実行を推奨します。 8 | 9 | 1. VS Codeでプロジェクトのルートディレクトリを開く 10 | 2. コマンドパレット(Cmd/Ctrl+Shift+P)から「Dev Containers: Reopen in Container」を選択 11 | 3. コンテナのビルドと起動が完了するまで待機 12 | 13 | ## 前提条件 14 | 15 | このサンプルコードを実行するには、事前に**OpenAI APIキー**の取得が必要です。 16 | APIキーは[OpenAI APIキーの管理画面](https://platform.openai.com/api-keys)から取得できます。 17 | 18 | ※ このリポジトリには最適化済みのモデルファイル(`artifact/agent_gepa_optimized_latest.json`)が含まれているため、すぐに評価を試すことができます。 19 | 20 | ## セットアップ 21 | 22 | Dev Container内で以下のコマンドを実行してください。 23 | 24 | ### 1. 依存関係のインストール 25 | 26 | ```bash 27 | cd 28 28 | uv sync 29 | ``` 30 | 31 | ### 2. 環境変数の設定 32 | 33 | `.env.sample`をコピーして`.env`ファイルを作成し、APIキーを設定してください。 34 | 35 | ```bash 36 | cp .env.sample .env 37 | vi .env # お好きなエディタで編集してください 38 | ``` 39 | 40 | #### OpenAI APIを使用する場合 41 | 42 | ``` 43 | PROVIDER_NAME=openai 44 | OPENAI_API_KEY=your_openai_api_key_here 45 | SMART_MODEL=gpt-4.1 46 | FAST_MODEL=gpt-4.1-nano 47 | EVAL_MODEL=gpt-4.1-mini 48 | ``` 49 | 50 | #### Azure OpenAI Serviceを使用する場合 51 | 52 | ``` 53 | PROVIDER_NAME=azure 54 | AZURE_OPENAI_ENDPOINT=your_azure_openai_endpoint_here 55 | AZURE_OPENAI_API_KEY=your_azure_openai_api_key_here 56 | AZURE_OPENAI_API_VERSION=2025-04-01-preview 57 | SMART_MODEL=gpt-4.1 58 | FAST_MODEL=gpt-4.1-nano 59 | EVAL_MODEL=gpt-4.1-mini 60 | ``` 61 | 62 | ## 実行方法 63 | 64 | ### 1. 評価の実行(推奨) 65 | 66 | ベースラインと最適化済みモデルの性能をテストセット(5例)で比較します。 67 | 68 | ```bash 69 | uv run python agent_evaluation.py 70 | ``` 71 | 72 | 出力例: 73 | ``` 74 | ============================================================ 75 | FINAL COMPARISON 76 | ============================================================ 77 | Baseline Average Score: 0.620 78 | Optimized Average Score: 0.750 79 | Improvement: +0.130 80 | ============================================================ 81 | ``` 82 | 83 | ### 2. エージェントの最適化(オプション) 84 | 85 | GEPAを使用してエージェントを最適化します。最適化には約3時間かかります。 86 | 87 | ```bash 88 | # デフォルトのシード値(42)で実行 89 | uv run python agent_optimization_gepa.py 90 | 91 | # シード値を指定して実行 92 | uv run python agent_optimization_gepa.py --seed 123 93 | ``` 94 | 95 | 最適化済みモデルは`artifact/agent_gepa_optimized_YYYYMMDD_HHMM_scoreXXX.json`に保存されます。 96 | 97 | ### 3. エージェントの単独実行(オプション) 98 | 99 | 最適化済みエージェントを使って任意のタスクを実行できます: 100 | 101 | ```bash 102 | # ディレクトリ構造を分析 103 | uv run python main.py --task "Analyze the directory structure and create a report" --directory ../27 104 | 105 | # Pythonファイルを探索 106 | uv run python main.py --task "Find all Python files and count lines of code" --directory . 107 | 108 | # 反復回数を制限 109 | uv run python main.py --task "List main files" --directory . --max-iters 5 110 | ``` 111 | 112 | ## プロジェクト構成 113 | 114 | ### コアモジュール 115 | - `config.py`: 環境変数設定とLLMモデルの初期化 116 | - `agent_module.py`: DSPy ReActベースのファイル探索エージェント実装 117 | - `dataset_loader.py`: ファイル探索タスクのデータセット読み込み 118 | 119 | ### スクリプト 120 | - `agent_evaluation.py`: ベースラインと最適化モデルの比較スクリプト 121 | - `agent_optimization_gepa.py`: GEPA最適化スクリプト 122 | - `main.py`: エージェント実行用CLIエントリポイント 123 | 124 | ### データとアーティファクト 125 | - `artifact/`: 最適化済みモデルの保存先 126 | - `logs/`: 最適化実行ログ 127 | - `tmp/reports/`: 評価レポート 128 | -------------------------------------------------------------------------------- /30/README.md: -------------------------------------------------------------------------------- 1 | # Software Design誌「実践LLMアプリケーション開発」第30回サンプルコード 2 | 3 | LangChainのミドルウェア機能を活用した3つのデモアプリケーションです。 4 | 5 | ## サンプルコードの実行方法 6 | 7 | ### プロジェクトのセットアップ 8 | 9 | ※ このプロジェクトは`uv`を使用しています。`uv`のインストール方法については[こちら](https://github.com/astral-sh/uv)をご確認ください。 10 | 11 | 以下のコマンドを実行し、必要なライブラリのインストールを行って下さい。 12 | 13 | ``` 14 | $ uv sync 15 | ``` 16 | 17 | 次に環境変数の設定を行います。まず`.env.sample`ファイルをコピーして`.env`ファイルを作成します。 18 | 19 | ``` 20 | $ cp .env.sample .env 21 | $ vi .env # お好きなエディタで編集してください 22 | ``` 23 | 24 | `.env`ファイルを編集し、以下のAPIキーを設定してください。 25 | 26 | ``` 27 | ANTHROPIC_API_KEY=your_anthropic_api_key_here 28 | LANGCHAIN_TRACING_V2=true # ※ LangSmithのAPIキー設定は必須ではありません 29 | LANGCHAIN_API_KEY=your_langsmith_key_here 30 | LANGCHAIN_PROJECT=sd-29 31 | ``` 32 | 33 | - `ANTHROPIC_API_KEY`: Anthropic Claude APIのキー 34 | - `LANGCHAIN_API_KEY`: LangSmithのAPIキー (オプション) 35 | 36 | ### 実行方法 37 | 38 | Streamlitアプリの起動: 39 | 40 | ```bash 41 | uv run python run.py 42 | ``` 43 | 44 | ブラウザで http://localhost:8501 を開いてデモを操作できます。 45 | 46 | ### テスト実行 47 | 48 | ```bash 49 | uv run pytest tests/ -v 50 | ``` 51 | 52 | ## シナリオ 53 | 54 | ### シナリオ1: メール処理エージェント 55 | 56 | 個人情報のフィルタリング、会話履歴の要約、Human in the loopフローのデモ。 57 | 58 | **使用ミドルウェア:** 59 | - `PIIMiddleware`: メールアドレス・電話番号のマスキング/ブロック 60 | - `SummarizationMiddleware`: 長い履歴の自動要約 61 | - `HumanInTheLoopMiddleware`: メール送信前の承認 62 | 63 | **テスト方法:** 64 | 1. 「メール一覧を見せて」でメール一覧を確認 65 | 2. 「メール001を読んで」でメール内容を確認 66 | 3. 電話番号を含むメッセージを入力してPIIブロックを確認 67 | 4. 「田中さんに返信して」で承認フローを確認 68 | 69 | ### シナリオ2: レジリエンスエージェント 70 | 71 | 無限ループ防止、コスト制御、エラー耐性のデモ。 72 | 73 | **使用ミドルウェア:** 74 | - `ModelCallLimitMiddleware`: モデル呼び出し回数制限 75 | - `ToolCallLimitMiddleware`: ツール呼び出し回数制限 76 | - `ModelFallbackMiddleware`: モデルフォールバック 77 | - `ToolRetryMiddleware` / `ModelRetryMiddleware`: 自動リトライ 78 | 79 | **テスト方法:** 80 | 1. 「Pythonについて検索して」でWeb検索を実行 81 | 2. 実行ログでリトライの様子を確認 82 | 3. メトリクスで成功率を確認 83 | 84 | ### シナリオ3: ツール選択エージェント 85 | 86 | LLMによる動的ツール選択のデモ。 87 | 88 | **使用ミドルウェア:** 89 | - `LLMToolSelectorMiddleware`: 12個のツールから最適な3つを自動選択 90 | 91 | **テスト方法:** 92 | 1. 「東京の天気を教えて」でweatherツール選択を確認 93 | 2. 「100ドルは何円?」でcurrency_convertツール選択を確認 94 | 3. 質問ごとに選択ツールが変化することを確認 95 | 96 | ## ファイル構成 97 | 98 | ``` 99 | 29/ 100 | ├── src/sd_29/ 101 | │ ├── app.py # Streamlit エントリポイント 102 | │ ├── agents/ 103 | │ │ ├── __init__.py # AgentResponse型 + エクスポート 104 | │ │ ├── email_agent.py # シナリオ1: メール処理 105 | │ │ ├── resilient_agent.py # シナリオ2: レジリエンス 106 | │ │ └── tool_selector_agent.py # シナリオ3: ツール選択 107 | │ ├── controllers/ 108 | │ │ ├── __init__.py # エクスポート 109 | │ │ └── base.py # エージェント対話処理 110 | │ ├── pages/ 111 | │ │ ├── common.py # 共通ユーティリティ 112 | │ │ ├── scenario1.py # シナリオ1 UI 113 | │ │ ├── scenario2.py # シナリオ2 UI 114 | │ │ └── scenario3.py # シナリオ3 UI 115 | │ └── mock/ 116 | │ ├── email_agent.json # メールモックデータ 117 | │ ├── resilient_agent.json # 検索/DBモックデータ 118 | │ └── tool_selector_agent.json # 各種モックデータ 119 | ├── tests/ 120 | │ ├── test_email_agent.py 121 | │ ├── test_resilient_agent.py 122 | │ └── test_tool_selector_agent.py 123 | ├── run.py 124 | ├── pyproject.toml 125 | └── .env.sample 126 | ``` 127 | -------------------------------------------------------------------------------- /27/artifact/rag_gepa_optimized_20251021_1310_em073.json: -------------------------------------------------------------------------------- 1 | { 2 | "rewrite": { 3 | "traces": [], 4 | "train": [], 5 | "demos": [], 6 | "signature": { 7 | "instructions": "あなたは、日本語の一般常識・雑学クイズ問題文を、ウェブ検索等で答えにたどり着きやすい検索語句(検索クエリ)にリライトする専門のアシスタントです。\n\n【タスク概要】\n- 入力は日本語のクイズ形式の質問文です。\n- 出力は、その質問文の答えがヒットしやすい日本語の検索クエリ(フレーズや語句の羅列)です。\n\n【リライト時のルール】\n1. 検索クエリは疑問文・質問文形式ではなく、「答え」そのものに辿り着きやすいキーワードや説明形式、語句の組み合わせにしてください。\n2. 質問文の丸写し(繰り返し)は避けてください。必要な情報や条件を抽出し、検索上重要なキーワード・フレーズ・語句に整理しましょう。\n3. クイズ独特の文言(「何でしょう」「と言う」等)は省いてください。説明や定義、または関連語句の組み合わせなどを使用してください。\n4. 答えが一語(例:「鳩時計」「バスター」「集英社」など)の場合、その答えにダイレクトにアクセスしやすいキーワードや説明文を意識してください。\n5. 検索文は簡潔かつ答えが見つかる情報に直結するようにしてください。雑多な修飾語や曖昧な表現は避けてください。\n6. 必要情報(属性・条件・対象・例示)は整理して、キーワードとして抽出してください。複数例がある場合はすべて盛り込んでもOKです。\n7. 記載メソッドは「rewritten_query: 」とし、その右側に検索クエリ(日本語)を記載してください。\n8. 雑誌・人物・用語・スポーツ・メーカー・国名など、ジャンル・分野は問わず、一般的な検索で答えが見つかるように最適化してください。\n9. 不要な解説や補足は不要です。出力は検索クエリのみとしてください。\n\n【例】\n- 質問文: 「英語では「クックー・クロック」と呼ばれる、時間になると鳥の模型が鳴き声で知らせる置時計を何というでしょう?」\n → rewritten_query: クックー・クロック 鳩時計\n- 質問文: 「野球で、バントの構えからヒッティングに移ることを何というでしょう?」\n → rewritten_query: 野球 バント ヒッティング バスター 用語\n- 質問文: 「『non・no』『週刊プレイボーイ』『週刊少年ジャンプ』といえば、発行している出版社はどこでしょう?」\n → rewritten_query: non・no 週刊プレイボーイ 週刊少年ジャンプ 発行出版社 集英社\n\n【禁止事項】\n- 質問文や語尾をそのまま書かないでください。\n- 答えそのもののみ書かず、検索で辿り着けるクエリにしてください。\n- 不要な説明や補足は不要です。\n\n【出力形式】\nrewritten_query: (ここに検索クエリ)\n\nこのルールに従って、入力された日本語クイズ問題文を検索クエリにリライトしてください。", 8 | "fields": [ 9 | { 10 | "prefix": "Question:", 11 | "description": "ユーザーからの質問" 12 | }, 13 | { 14 | "prefix": "Rewritten Query:", 15 | "description": "埋め込みベクトルによる類似度検索に最適化されたクエリ" 16 | } 17 | ] 18 | }, 19 | "lm": null 20 | }, 21 | "generate": { 22 | "traces": [], 23 | "train": [], 24 | "demos": [], 25 | "signature": { 26 | "instructions": "あなたは日本語のクローズドブックQAアシスタントです。以下のタスク形式を厳密に守ってください。\n\n【タスク】\n- 入力フォーマットは2つの部分で構成されます。\n 1. context: さまざまなタイトル(title)とテキスト(text)からなる知識断片のリスト。情報源となるが、質問と直接関係ない内容が混じっている場合もある。\n 2. question: 一般的な知識・教養・雑学・スポーツ・文学など多岐分野に属する日本語の質問文。\n\n【あなたの目標】\n- 与えられた context を必ず確認し、質問と直接関連する情報が見つかれば、そこから根拠を示す明示的な事実/記述に基づいて、正確・簡潔・直接的な単一の答えを日本語で1文で返してください。\n- context 内に該当する情報・根拠が一切含まれていない場合は、「context内に該当する情報がありません。」とだけ答えてください。\n- context に正答・根拠が無い場合に、知識から推論したり、想像や補完で答えを書かないでください。\n\n【追加の注意事項・専門的指示】\n- わずかな事実・記載でも根拠となれば、それを使って答えてください。根拠不在の場合は、必ず上記の定型文で対応してください。\n- 教養問題なので、固有名詞・用語・人名・地名・料理名・技の名称・著者名など、正確な単語レベルで期待される答えを返してください。\n - 例: 「村上春樹」「メキシコ料理」「バスター」など、曖昧にせず、定型・単語で。余計な解説や付帯情報は不要です。\n- context情報と問の照合は厳密に行い、類似名・語句(例:村上三島vs村上春樹)の取り違いに注意してください。\n- 絶対に context にない情報・推測を書き加えないこと。\n- context に不正確な知識や、表現ゆれ(呼称や説明が複数ある場合)があるときは、最も直接的に問に合致する表現を選んで回答してください。\n- 1行だけ。冗長な説明、質問の繰り返し、背景知識の補足等は禁止です。\n\n【出力例】\n- contextに正解根拠があれば: 答えとなる固有名詞・単語・表現(1行/日本語/句点あり)\n- contextに根拠がなければ: context内に該当する情報がありません。\n\nこの指示を守り、どんな場合でも決して自分の知識や想像で補うことなく答えてください。", 27 | "fields": [ 28 | { 29 | "prefix": "Context:", 30 | "description": "検索で取得した文書群" 31 | }, 32 | { 33 | "prefix": "Question:", 34 | "description": "ユーザーからの質問" 35 | }, 36 | { 37 | "prefix": "Answer:", 38 | "description": "質問に対する簡潔な回答" 39 | } 40 | ] 41 | }, 42 | "lm": null 43 | }, 44 | "metadata": { 45 | "dependency_versions": { 46 | "python": "3.13", 47 | "dspy": "3.0.3", 48 | "cloudpickle": "3.1" 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /12/tools.py: -------------------------------------------------------------------------------- 1 | import os 2 | from functools import lru_cache 3 | 4 | from langchain_core.output_parsers import StrOutputParser 5 | from langchain_core.prompts import ChatPromptTemplate 6 | from langchain_core.runnables import Runnable 7 | from langchain_core.tools import tool 8 | from langchain_openai import ChatOpenAI 9 | from settings import Settings 10 | from tavily import TavilyClient 11 | from utility import load_prompt 12 | 13 | TAVILY_MAX_RESULTS = 5 14 | 15 | settings = Settings() 16 | 17 | 18 | # Tavilyクライアントのインスタンスを取得する関数 19 | @lru_cache 20 | def tavily_client() -> TavilyClient: 21 | return TavilyClient(api_key=os.environ["TAVILY_API_KEY"]) 22 | 23 | 24 | def summarize_search_chain() -> Runnable: 25 | llm = ChatOpenAI(model=settings.FAST_LLM_MODEL_NAME, temperature=0.0) 26 | prompt = ChatPromptTemplate.from_messages( 27 | [("system", "{system}"), ("user", "{query}")] 28 | ).partial( 29 | system=load_prompt("summarize_search_system"), 30 | ) 31 | return prompt | llm | StrOutputParser() 32 | 33 | 34 | # 検索を実行するツール 35 | @tool 36 | def search(query: str) -> str: 37 | """Search for the given query and return the results as a string.""" 38 | response = tavily_client().search( 39 | query, max_results=TAVILY_MAX_RESULTS, include_raw_content=True 40 | ) 41 | queries = [] 42 | for document in response["results"]: 43 | # 1万字以上のコンテンツは省略 44 | if len(document["raw_content"]) > 10000: 45 | document["raw_content"] = document["raw_content"][:10000] + "..." 46 | queries.append( 47 | { 48 | "query": f"\ntitle: {document['title']}\nurl: {document['url']}\ncontent: {document['raw_content']}\n" 49 | } 50 | ) 51 | summarize_results = summarize_search_chain().batch(queries) 52 | xml_results = [] 53 | for result in summarize_results: 54 | xml_result = "{}".format(result) 55 | xml_results.append(xml_result) 56 | return "\n\n".join(xml_results) 57 | 58 | 59 | # 成果物の内容がユーザー要求に対して十分かどうかをチェックするツール 60 | @tool 61 | def sufficiency_check(user_requirement: str, result: str) -> str: 62 | """Determine whether the answers generated adequately answer the question.""" 63 | llm = ChatOpenAI(model=settings.LLM_MODEL_NAME, temperature=0.0) 64 | user_prompt = f"ユーザーからの要求: {user_requirement}\n生成結果: {result}\n十分かどうかを判断してください。" 65 | prompt = ChatPromptTemplate.from_messages( 66 | [("system", "{system}"), ("user", "{user}")] 67 | ).partial( 68 | system=load_prompt("sufficiency_classifier_system"), 69 | user=user_prompt, 70 | ) 71 | chain = prompt | llm | StrOutputParser() 72 | return chain.invoke({}) 73 | 74 | 75 | # レポートを生成するツール 76 | @tool 77 | def report_writer(user_requirement: str, source: str) -> str: 78 | """Generate reports based on user requests and sources of information gathered through searches.""" 79 | llm = ChatOpenAI(model=settings.LLM_MODEL_NAME, temperature=0.0) 80 | user_prompt = f"ユーザーからの要求: {user_requirement}\n情報源: {source}\n必ず情報源を基にユーザーからの要求を満たすレポートを生成してください。" 81 | prompt = ChatPromptTemplate.from_messages( 82 | [("system", "{system}"), ("user", "{user}")] 83 | ).partial( 84 | system=load_prompt("report_writer_system"), 85 | user=user_prompt, 86 | ) 87 | chain = prompt | llm | StrOutputParser() 88 | return chain.invoke({}) 89 | -------------------------------------------------------------------------------- /30/src/sd_30/pages/scenario3.py: -------------------------------------------------------------------------------- 1 | """ 2 | シナリオ3: ツール選択エージェント 3 | """ 4 | 5 | import uuid 6 | 7 | import streamlit as st 8 | 9 | from sd_30.agents.tool_selector_agent import ( 10 | clear_selected_tools, 11 | create_tool_selector_agent, 12 | get_all_tool_names, 13 | get_selected_tools, 14 | ) 15 | from sd_30.controllers import invoke_agent 16 | 17 | 18 | @st.cache_resource 19 | def get_agent(): 20 | """エージェントをキャッシュして取得""" 21 | return create_tool_selector_agent() 22 | 23 | 24 | def render() -> None: 25 | """シナリオ3の画面を描画""" 26 | st.title("ツール選択エージェント") 27 | 28 | with st.expander("このエージェントについて", expanded=True): 29 | st.markdown(""" 30 | ### 概要 31 | 12個のツール (計算、天気、検索、翻訳、株価など) を持つエージェントです。 32 | 質問内容に応じて、LLMが事前に最適なツールを3つまで選択します。 33 | これにより、大量のツールがあっても効率的に処理できます。 34 | 35 | ### 使用ミドルウェア 36 | - **LLMToolSelectorMiddleware**: 質問に適したツールを事前選択 37 | - 12個のツールから最大3個を自動選択 38 | - searchツールは常に含める (always_include) 39 | 40 | ### テスト方法 41 | 1. 「東京の天気を教えて」と入力 42 | - weather と search が選択されることを確認 43 | 2. 「100ドルは何円?」と入力 44 | - currency_convert と search が選択されることを確認 45 | 3. 「Appleの株価と最新ニュースを教えて」と入力 46 | - stock_price, news, search が選択されることを確認 47 | 4. 異なる質問で選択されるツールが変わることを観察 48 | """) 49 | 50 | all_tools = get_all_tool_names() 51 | selected_tools = get_selected_tools() 52 | 53 | st.subheader("利用可能なツール") 54 | cols = st.columns(4) 55 | for i, tool_name in enumerate(all_tools): 56 | col = cols[i % 4] 57 | if tool_name in selected_tools: 58 | col.success(f"[選択] {tool_name}") 59 | else: 60 | col.text(f"{tool_name}") 61 | 62 | st.divider() 63 | 64 | for message in st.session_state.messages: 65 | with st.chat_message(message["role"]): 66 | st.markdown(message["content"]) 67 | 68 | # チャット入力欄を表示し、ユーザーの入力を取得する 69 | if prompt := st.chat_input("質問してください (例: 東京の天気と株価を教えて)"): 70 | # ユーザーのメッセージを履歴に追加する 71 | st.session_state.messages.append({"role": "user", "content": prompt}) 72 | with st.chat_message("user"): 73 | st.markdown(prompt) 74 | 75 | with st.chat_message("assistant"): 76 | with st.spinner("ツールを選択して実行中..."): 77 | # キャッシュされたエージェントを取得する 78 | agent = get_agent() 79 | # エージェントにメッセージを送信し、応答を取得する 80 | # ミドルウェアが質問を分析し、12個のツールから最適な3つを選択する 81 | # 選択されたツールはget_selected_tools()で確認できる 82 | response = invoke_agent(agent, prompt, st.session_state.thread_id) 83 | 84 | # エラーが発生した場合はエラー表示 85 | if response.status == "error": 86 | st.error(response.message) 87 | else: 88 | st.markdown(response.message) 89 | 90 | # エージェントの応答をチャット履歴に追加する 91 | st.session_state.messages.append({ 92 | "role": "assistant", 93 | "content": response.message 94 | }) 95 | # 画面を再描画して最新の状態を表示する (選択されたツールも更新される) 96 | st.rerun() 97 | 98 | if st.button("会話をリセット"): 99 | st.session_state.messages = [] 100 | st.session_state.thread_id = str(uuid.uuid4()) 101 | clear_selected_tools() 102 | st.rerun() 103 | --------------------------------------------------------------------------------