æ¬è¨äºã¯
çæAIã¦ã£ã¼ã¯
2æ¥ç®ã®è¨äºã§ãã
ð¨âð»
1æ¥ç®
â¶â¶ æ¬è¨äº â¶â¶
3æ¥ç®
ð©âð»
- ã¯ããã«
- LangGraphã¨ã¯
- æ§ç¯ã®æµã
- 1. ãã¼ã¿ã½ã¼ã¹1ã¤ã®RAG
- 2. ãã¼ã¿ã½ã¼ã¹2ã¤ã®RAG
- 3. ãã¼ã¿ã½ã¼ã¹2ã¤+Webæ¤ç´¢ã®RAG
- ã¾ã¨ã
ã¯ããã«
ããã«ã¡ã¯å ¤ã§ãã
LLMã§ã¯å¤é¨ãã¼ã¿ãæ´»ç¨ãã¦ãã精度ã®é«ãåçãçæããRetrieval-Augmented Generationï¼RAGï¼ãåºããã¤ã¤ããã¾ããRAGã¨ããè¨èèªä½ãä¸è¬çã«ãªãã¤ã¤ããã¾ããã精度åä¸ã®ããã®ææ³ãè²ã èãããã¦ãããæ§ã ãªã¢ããã¼ããåå¨ãã¾ããã¾ã䏿¹ã§æè¿ã¯Agent ã®æ´»ç¨ãé²ãã§ãããããã«ä¼´ãLangGraphãæ³¨ç®ããã¦ãã¾ããæ¬è¨äºã§ã¯ãLangGraphãç¨ãããã«ããã¼ã¿ã½ã¼ã¹å¯¾å¿ã®RAGãã¹ããããã¤ã¹ãããã§å®è£ ãã¦ã¿ããã¨æãã¾ãã
LangGraphã¨ã¯
LangGraphã¯ãLLMãæ´»ç¨ããã¢ããªã±ã¼ã·ã§ã³ãæ§ç¯ããããã®ã©ã¤ãã©ãªã§ããã¹ããããç¶æ ãã°ã©ãæ§é ã§ç®¡çãããã¨ã§ãè¤éãªã¯ã¼ã¯ããã¼ãã¨ã¼ã¸ã§ã³ãã®è¨è¨ã容æã«ãã¾ããç¹ã«ãã«ã¼ããå«ãããã¼ãåçãªç¶æ 管çãæ±ããããã¢ããªã±ã¼ã·ã§ã³ã«ããã¦ãLangGraphãæ´»ç¨ãããã¨ã§ãæè»ãã¤å¹ççãªã·ã¹ãã æ§ç¯ãå¯è½ã«ãªãã¾ãã
æ§ç¯ã®æµã
ããããã¯LangGraphãç¨ããRAGãæ§ç¯ãã¦ããã¾ãã ä»åã¯
- ãã¼ã¿ã½ã¼ã¹1ã¤ã®RAG
- ãã¼ã¿ã½ã¼ã¹2ã¤ã®RAG
- ãã¼ã¿ã½ã¼ã¹2ã¤+Webæ¤ç´¢ã®RAG
ã®3Stepã§æ§ç¯ãã¦ããããã¨æãã¾ãã ä»åã¯LangGraphã®åºæ¬çãªã³ã³ãã¼ãã³ããææ³ã«ã¤ãã¦ã¯è§¦ããªãã®ã§ãå ¬å¼ããã¥ã¡ã³ããä»ã®ããã°ããåç §ãã ããã
1. ãã¼ã¿ã½ã¼ã¹1ã¤ã®RAG
ã¾ãã¯1ã¤ã®ãã¼ã¿ã½ã¼ã¹ã®ã¿ãåç §ããã·ã³ãã«ãªRAGãLangGraphã§æ§ç¯ãã¾ãã ä»åã¯Wikipediaã®ãéçããã¼ã¸ãèªã¿è¾¼ã¾ãã¦ãã®å 容ãåçãã¦ãããã¾ãã

ã³ã¼ãä¾
from typing import Annotated from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_chroma import Chroma from langchain_community.document_loaders import UnstructuredURLLoader from langchain_openai import ChatOpenAI, OpenAIEmbeddings from langgraph.graph import END, START, StateGraph from langgraph.graph.message import add_messages from typing_extensions import TypedDict llm = ChatOpenAI(model="gpt-4o-mini") embedding_model = OpenAIEmbeddings() vectorstore = Chroma( embedding_function=embedding_model, persist_directory="./chroma_db" ) class State(TypedDict): messages: Annotated[list[str], add_messages] def retrieve(state: State): question = state["messages"][-1].content retriever = vectorstore.similarity_search(question, k=3) context = "\n\n".join([doc.page_content for doc in retriever]) return { "messages": [ { "role": "user", "content": f"以ä¸ã®ã³ã³ããã¹ãæ å ±ã使ç¨ãã¦åçãã¦ãã ããã\n\n{context}", } ] } def generate_answer(state: State): response = llm.invoke(state["messages"]) return {"messages": [response]} def load_data(urls): loader = UnstructuredURLLoader(urls) docs = loader.load() text_splitter = RecursiveCharacterTextSplitter(chunk_size=100, chunk_overlap=50) splits = text_splitter.split_documents(docs) return splits urls = ["https://ja.wikipedia.org/wiki/%E9%87%8E%E7%90%83"] docs = load_data(urls) vectorstore.add_documents(docs) builder = StateGraph(State) builder.add_node("retrieve", retrieve) builder.add_node("generate_answer", generate_answer) builder.add_edge(START, "retrieve") builder.add_edge("retrieve", "generate_answer") builder.add_edge("generate_answer", END) graph = builder.compile()
ã°ã©ãæ§é
LangGraphã¯ã°ã©ãæ§é ãMermaidè¨æ³ã§åºåãããã¨ãã§ãã¾ãã å¯è¦åããã¨ãã®ãããªæ§é ã«ãªãã¾ãã
ãã®ãããªã·ã³ãã«ãªæ§é ã§ããã°LangGraphã使ãæå³ã¯ãã¾ããªããLangChainã§ãåé¡ãªãããã§ãã

å®è¡ä¾
æ§ç¯ããã¯ã¼ã¯ããã¼ãå®è¡ãã¦ã¿ã¾ãã
response = graph.invoke(
{"messages": [{"role": "user", "content": "ä¸çåã®ããçå£ã¯ï¼"}]},
debug=True,
)
print(response["messages"][-1].content)
ä¸çåã®ããçå£ã¯ã·ã³ã·ããã£ã»ã¬ããã¹ãããã³ã°ã¹ï¼Cincinnati Redsï¼ã§ãã1869å¹´ã«è¨ç«ããã¾ããã
Wikipediaããæ å ±ãèªã¿åã£ã¦åçãã¦ãããã¨ãå®è¡ãã°ãã確èªã§ãã¾ãã
å®è¡ãã°
[-1:checkpoint] State at the end of step -1:
{'messages': []}
[0:tasks] Starting 1 task for step 0:
- __start__ -> {'messages': [{'content': 'ä¸çåã®ããçå£ã¯ï¼', 'role': 'user'}]}
[0:writes] Finished step 0 with writes to 1 channel:
- messages -> [{'content': 'ä¸çåã®ããçå£ã¯ï¼', 'role': 'user'}]
[0:checkpoint] State at the end of step 0:
{'messages': [HumanMessage(content='ä¸çåã®ããçå£ã¯ï¼', additional_kwargs={}, response_metadata={}, id='accb2f91-4c53-436e-a246-175fd9864071')]}
[1:tasks] Starting 1 task for step 1:
- retrieve -> {'messages': [HumanMessage(content='ä¸çåã®ããçå£ã¯ï¼', additional_kwargs={}, response_metadata={}, id='accb2f91-4c53-436e-a246-175fd9864071')]}
[1:writes] Finished step 1 with writes to 1 channel:
- messages -> [{'content': '以ä¸ã®ã³ã³ããã¹ãæ
å ±ã使ç¨ãã¦åçãã¦ãã ããã\n'
'\n'
'1869å¹´ã«ã¯ä¸çåã®ããçå£ã§ããã·ã³ã·ããã£ã»ã¬ããã¹ãããã³ã°ã¹ãè¨ç«ããã1871å¹´ã«ã¯ä¸çåã®ããéçãªã¼ã°ã§ãããã·ã§ãã«ã»ã¢ã½ã·ã¨ã¼ã·ã§ã³ãè¨ç«ãããããã®ãªã¼ã°èªä½ã¯5å¹´ã§ç ´ç¶»ããã\n'
'\n'
'1869å¹´ã«ã¯ä¸çåã®ããçå£ã§ããã·ã³ã·ããã£ã»ã¬ããã¹ãããã³ã°ã¹ãè¨ç«ããã1871å¹´ã«ã¯ä¸çåã®ããéçãªã¼ã°ã§ãããã·ã§ãã«ã»ã¢ã½ã·ã¨ã¼ã·ã§ã³ãè¨ç«ãããããã®ãªã¼ã°èªä½ã¯5å¹´ã§ç ´ç¶»ããã\n'
'\n'
'åºç¤ãã¤ãããããã¨ãããä¸è¬ã«åå½ãéççºç¥¥ã®å°ã¨ããã¦ããã1869å¹´ã«ã¯æåã®ãããã¼ã ãçã¾ããã¢ã¡ãªã«ã§ææ°ã®äººæ°ã¹ãã¼ãã¨ãªãã彿°ç娯楽ã¨ãªã£ããå
ã
ã¯21ç¹å
åå¶ãåæã ãã§ãªã両ãã¼',
'role': 'user'}]
[1:checkpoint] State at the end of step 1:
{'messages': [HumanMessage(content='ä¸çåã®ããçå£ã¯ï¼', additional_kwargs={}, response_metadata={}, id='accb2f91-4c53-436e-a246-175fd9864071'),
HumanMessage(content='以ä¸ã®ã³ã³ããã¹ãæ
å ±ã使ç¨ãã¦åçãã¦ãã ããã\n\n1869å¹´ã«ã¯ä¸çåã®ããçå£ã§ããã·ã³ã·ããã£ã»ã¬ããã¹ãããã³ã°ã¹ãè¨ç«ããã1871å¹´ã«ã¯ä¸çåã®ããéçãªã¼ã°ã§ãããã·ã§ãã«ã»ã¢ã½ã·ã¨ã¼ã·ã§ã³ãè¨ç«ãããããã®ãªã¼ã°èªä½ã¯5å¹´ã§ç ´ç¶»ããã\n\n1869å¹´ã«ã¯ä¸çåã®ããçå£ã§ããã·ã³ã·ããã£ã»ã¬ããã¹ãããã³ã°ã¹ãè¨ç«ããã1871å¹´ã«ã¯ä¸çåã®ããéçãªã¼ã°ã§ãããã·ã§ãã«ã»ã¢ã½ã·ã¨ã¼ã·ã§ã³ãè¨ç«ãããããã®ãªã¼ã°èªä½ã¯5å¹´ã§ç ´ç¶»ããã\n\nåºç¤ãã¤ãããããã¨ãããä¸è¬ã«åå½ãéççºç¥¥ã®å°ã¨ããã¦ããã1869å¹´ã«ã¯æåã®ãããã¼ã ãçã¾ããã¢ã¡ãªã«ã§ææ°ã®äººæ°ã¹ãã¼ãã¨ãªãã彿°ç娯楽ã¨ãªã£ããå
ã
ã¯21ç¹å
åå¶ãåæã ãã§ãªã両ãã¼', additional_kwargs={}, response_metadata={}, id='6633d4cb-8275-41b0-9f36-a747667fb083')]}
[2:tasks] Starting 1 task for step 2:
- generate_answer -> {'messages': [HumanMessage(content='ä¸çåã®ããçå£ã¯ï¼', additional_kwargs={}, response_metadata={}, id='accb2f91-4c53-436e-a246-175fd9864071'),
HumanMessage(content='以ä¸ã®ã³ã³ããã¹ãæ
å ±ã使ç¨ãã¦åçãã¦ãã ããã\n\n1869å¹´ã«ã¯ä¸çåã®ããçå£ã§ããã·ã³ã·ããã£ã»ã¬ããã¹ãããã³ã°ã¹ãè¨ç«ããã1871å¹´ã«ã¯ä¸çåã®ããéçãªã¼ã°ã§ãããã·ã§ãã«ã»ã¢ã½ã·ã¨ã¼ã·ã§ã³ãè¨ç«ãããããã®ãªã¼ã°èªä½ã¯5å¹´ã§ç ´ç¶»ããã\n\n1869å¹´ã«ã¯ä¸çåã®ããçå£ã§ããã·ã³ã·ããã£ã»ã¬ããã¹ãããã³ã°ã¹ãè¨ç«ããã1871å¹´ã«ã¯ä¸çåã®ããéçãªã¼ã°ã§ãããã·ã§ãã«ã»ã¢ã½ã·ã¨ã¼ã·ã§ã³ãè¨ç«ãããããã®ãªã¼ã°èªä½ã¯5å¹´ã§ç ´ç¶»ããã\n\nåºç¤ãã¤ãããããã¨ãããä¸è¬ã«åå½ãéççºç¥¥ã®å°ã¨ããã¦ããã1869å¹´ã«ã¯æåã®ãããã¼ã ãçã¾ããã¢ã¡ãªã«ã§ææ°ã®äººæ°ã¹ãã¼ãã¨ãªãã彿°ç娯楽ã¨ãªã£ããå
ã
ã¯21ç¹å
åå¶ãåæã ãã§ãªã両ãã¼', additional_kwargs={}, response_metadata={}, id='6633d4cb-8275-41b0-9f36-a747667fb083')]}
[2:writes] Finished step 2 with writes to 1 channel:
- messages -> [AIMessage(content='ä¸çåã®ããçå£ã¯ã·ã³ã·ããã£ã»ã¬ããã¹ãããã³ã°ã¹ï¼Cincinnati Redsï¼ã§ãã1869å¹´ã«è¨ç«ããã¾ããã', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 36, 'prompt_tokens': 256, 'total_tokens': 292, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_00428b782a', 'finish_reason': 'stop', 'logprobs': None}, id='run-d7bf082a-acab-4987-9d79-e2319ecb9dd1-0', usage_metadata={'input_tokens': 256, 'output_tokens': 36, 'total_tokens': 292, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]
[2:checkpoint] State at the end of step 2:
{'messages': [HumanMessage(content='ä¸çåã®ããçå£ã¯ï¼', additional_kwargs={}, response_metadata={}, id='accb2f91-4c53-436e-a246-175fd9864071'),
HumanMessage(content='以ä¸ã®ã³ã³ããã¹ãæ
å ±ã使ç¨ãã¦åçãã¦ãã ããã\n\n1869å¹´ã«ã¯ä¸çåã®ããçå£ã§ããã·ã³ã·ããã£ã»ã¬ããã¹ãããã³ã°ã¹ãè¨ç«ããã1871å¹´ã«ã¯ä¸çåã®ããéçãªã¼ã°ã§ãããã·ã§ãã«ã»ã¢ã½ã·ã¨ã¼ã·ã§ã³ãè¨ç«ãããããã®ãªã¼ã°èªä½ã¯5å¹´ã§ç ´ç¶»ããã\n\n1869å¹´ã«ã¯ä¸çåã®ããçå£ã§ããã·ã³ã·ããã£ã»ã¬ããã¹ãããã³ã°ã¹ãè¨ç«ããã1871å¹´ã«ã¯ä¸çåã®ããéçãªã¼ã°ã§ãããã·ã§ãã«ã»ã¢ã½ã·ã¨ã¼ã·ã§ã³ãè¨ç«ãããããã®ãªã¼ã°èªä½ã¯5å¹´ã§ç ´ç¶»ããã\n\nåºç¤ãã¤ãããããã¨ãããä¸è¬ã«åå½ãéççºç¥¥ã®å°ã¨ããã¦ããã1869å¹´ã«ã¯æåã®ãããã¼ã ãçã¾ããã¢ã¡ãªã«ã§ææ°ã®äººæ°ã¹ãã¼ãã¨ãªãã彿°ç娯楽ã¨ãªã£ããå
ã
ã¯21ç¹å
åå¶ãåæã ãã§ãªã両ãã¼', additional_kwargs={}, response_metadata={}, id='6633d4cb-8275-41b0-9f36-a747667fb083'),
AIMessage(content='ä¸çåã®ããçå£ã¯ã·ã³ã·ããã£ã»ã¬ããã¹ãããã³ã°ã¹ï¼Cincinnati Redsï¼ã§ãã1869å¹´ã«è¨ç«ããã¾ããã', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 36, 'prompt_tokens': 256, 'total_tokens': 292, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_00428b782a', 'finish_reason': 'stop', 'logprobs': None}, id='run-d7bf082a-acab-4987-9d79-e2319ecb9dd1-0', usage_metadata={'input_tokens': 256, 'output_tokens': 36, 'total_tokens': 292, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]}
2. ãã¼ã¿ã½ã¼ã¹2ã¤ã®RAG
次ã¯ããµãã«ã¼ãã«é¢ããWikipediaã®ãã¼ã¿ã½ã¼ã¹ã1ã¤å¢ããã¦2ã¤ã®ãã¼ã¿ã½ã¼ã¹ããæ å ±ãåå¾ããããã«ãã¾ãã

ã³ã¼ãä¾
éè¤ãå¤ãã®ã§ãæ°ãã使ããRouterãã¼ãã®ã¿è¨è¼ãã¾ãã
class RouterResponse(TypedDict): goto: Literal["node1", "node2"] def router_node(state: State) -> Command[Literal["node1", "node2"]]: llm = ChatOpenAI(model="gpt-4o-mini") prompt = ChatPromptTemplate.from_messages( [ ( "system", "質åãéçã«é¢ãããã®ã§ããã°node1ã«ããµãã«ã¼ã®è³ªåã§ããã°node2ã«ã«ã¼ãã£ã³ã°ãã¦ãã ããã", ), ("user", "{message}"), ] ) chain = prompt | llm.with_structured_output(RouterResponse) response = chain.invoke({"message": state["messages"][-1]}) return Command(goto=response["goto"])
ã©ã¡ãã®ãã¼ã¿ã½ã¼ã¹ã使ç¨ãããã¯ãRouterãã¼ãã夿ãã¦é©åãªãã¼ã¿ã½ã¼ã¹ã«ã«ã¼ãã£ã³ã°ããããã«ãã¾ãã Commandæ©è½ã使ããã¨ã§åçã«æ¬¡ã®ãã¼ããæ±ºå®ããã®ã§ç°¡æ½ã«æ¸ããã¨ãã§ãã¾ãã
ã°ã©ãæ§é

å®è¡ä¾
response = graph.invoke(
{"messages": [{"role": "user", "content": "éçã®èµ·æºã¨ãªã£ãçæã¯ä½ã¨ããã¦ããï¼"}]},
debug=True,
)
print(response["messages"][-1].content)
éçã®èµ·æºã¨ãªã£ãçæã¨ãã¦ã¯ãã¤ã®ãªã¹ã®ãã¿ã¦ã³ãã¼ã«ããæãããã¾ããã¿ã¦ã³ãã¼ã«ã¯ã¤ã®ãªã¹ç³»ç§»æ°ã«ãã£ã¦ã¢ã¡ãªã«ã«æã¡è¾¼ã¾ããããããå¤åãã¦ãã£ãã¨èãããã¦ãã¾ããå¤ãã®ç ç©¶è ãããã®éç¨ãçµã¦éçãå½¢æãããã¨è¦ã¦ãã¾ãã
å®è¡ãã°
[-1:checkpoint] State at the end of step -1:
{'messages': []}
[0:tasks] Starting 1 task for step 0:
- __start__ -> {'messages': [{'content': 'éçã®èµ·æºã¨ãªã£ãçæã¯ä½ã¨ããã¦ããï¼', 'role': 'user'}]}
[0:writes] Finished step 0 with writes to 1 channel:
- messages -> [{'content': 'éçã®èµ·æºã¨ãªã£ãçæã¯ä½ã¨ããã¦ããï¼', 'role': 'user'}]
[0:checkpoint] State at the end of step 0:
{'messages': [HumanMessage(content='éçã®èµ·æºã¨ãªã£ãçæã¯ä½ã¨ããã¦ããï¼', additional_kwargs={}, response_metadata={}, id='c74dc0c3-5eea-4f43-866d-f8416e49f44e')]}
[1:tasks] Starting 1 task for step 1:
- router_node -> {'messages': [HumanMessage(content='éçã®èµ·æºã¨ãªã£ãçæã¯ä½ã¨ããã¦ããï¼', additional_kwargs={}, response_metadata={}, id='c74dc0c3-5eea-4f43-866d-f8416e49f44e')]}
{'goto': 'node1'}
[1:writes] Finished step 1 with writes to 0 channels:
[1:checkpoint] State at the end of step 1:
{'messages': [HumanMessage(content='éçã®èµ·æºã¨ãªã£ãçæã¯ä½ã¨ããã¦ããï¼', additional_kwargs={}, response_metadata={}, id='c74dc0c3-5eea-4f43-866d-f8416e49f44e')]}
[2:tasks] Starting 1 task for step 2:
- node1 -> {'messages': [HumanMessage(content='éçã®èµ·æºã¨ãªã£ãçæã¯ä½ã¨ããã¦ããï¼', additional_kwargs={}, response_metadata={}, id='c74dc0c3-5eea-4f43-866d-f8416e49f44e')]}
[2:writes] Finished step 2 with writes to 1 channel:
- messages -> [{'content': '以ä¸ã®ã³ã³ããã¹ãæ
å ±ã使ç¨ãã¦åçãã¦ãã ããã\n'
'\n'
'éçã®èµ·æºã¯æç¢ºã«ã¯ããã¦ããªãããã¤ã®ãªã¹ã®çæã§ãããã¿ã¦ã³ãã¼ã«ããã¤ã®ãªã¹ç³»ç§»æ°ã«ãã£ã¦ã¢ã¡ãªã«ã«æã¡è¾¼ã¾ããå¾ã«å¤åããéçã¨ãã¦å½¢æãããã¨èããç ç©¶è
ãå¤ãã1830年代ãã1840å¹´\n'
'\n'
'é¡ä¼¼ç«¶æ\n'
'\n'
'[ç·¨é]\n'
'\n'
'éçã¨ã¯ç°ãªãèµ·æºãæã¤ç«¶æ\n'
'\n'
'[ç·¨é]\n'
'\n'
'ã¯ãªã±ãã\n'
'\n'
'ã©ãã¿ã¼\n'
'\n'
'ã©ã¦ã³ãã¼ãº\n'
'\n'
'ã¹ãã£ãã¯ãã¼ã«\n'
'\n'
'ã·ã¥ã©ã¼ã¯ãã«\n'
'\n'
'éçããæ´¾çããç«¶æ\n'
'\n'
'[ç·¨é]\n'
'\n'
'ã½ãããã¼ã«\n'
'\n'
'ã¹ãã£ãã¯ãã¼ã«\n'
'\n'
'ã·ã¥ã©ã¼ã¯ãã«\n'
'\n'
'éçããæ´¾çããç«¶æ\n'
'\n'
'[ç·¨é]\n'
'\n'
'ã½ãããã¼ã«\n'
'\n'
'ããµããã\n'
'\n'
'ãã£ã¼ãã¼ã«\n'
'\n'
'ããã¯ãã¼ã¹ãã¼ã«\n'
'\n'
'ãã³ããã¼ã¹ãã¼ã«\n'
'\n'
'ã©ã±ãããã¼ã¹ãã¼ã«',
'role': 'user'}]
[2:checkpoint] State at the end of step 2:
{'messages': [HumanMessage(content='éçã®èµ·æºã¨ãªã£ãçæã¯ä½ã¨ããã¦ããï¼', additional_kwargs={}, response_metadata={}, id='c74dc0c3-5eea-4f43-866d-f8416e49f44e'),
HumanMessage(content='以ä¸ã®ã³ã³ããã¹ãæ
å ±ã使ç¨ãã¦åçãã¦ãã ããã\n\néçã®èµ·æºã¯æç¢ºã«ã¯ããã¦ããªãããã¤ã®ãªã¹ã®çæã§ãããã¿ã¦ã³ãã¼ã«ããã¤ã®ãªã¹ç³»ç§»æ°ã«ãã£ã¦ã¢ã¡ãªã«ã«æã¡è¾¼ã¾ããå¾ã«å¤åããéçã¨ãã¦å½¢æãããã¨èããç ç©¶è
ãå¤ãã1830年代ãã1840å¹´\n\né¡ä¼¼ç«¶æ\n\n[ç·¨é]\n\néçã¨ã¯ç°ãªãèµ·æºãæã¤ç«¶æ\n\n[ç·¨é]\n\nã¯ãªã±ãã\n\nã©ãã¿ã¼\n\nã©ã¦ã³ãã¼ãº\n\nã¹ãã£ãã¯ãã¼ã«\n\nã·ã¥ã©ã¼ã¯ãã«\n\néçããæ´¾çããç«¶æ\n\n[ç·¨é]\n\nã½ãããã¼ã«\n\nã¹ãã£ãã¯ãã¼ã«\n\nã·ã¥ã©ã¼ã¯ãã«\n\néçããæ´¾çããç«¶æ\n\n[ç·¨é]\n\nã½ãããã¼ã«\n\nããµããã\n\nãã£ã¼ãã¼ã«\n\nããã¯ãã¼ã¹ãã¼ã«\n\nãã³ããã¼ã¹ãã¼ã«\n\nã©ã±ãããã¼ã¹ãã¼ã«', additional_kwargs={}, response_metadata={}, id='7a7ce564-7ec9-45c0-a45d-965ba4399cf7')]}
[3:tasks] Starting 1 task for step 3:
- generate_answer -> {'messages': [HumanMessage(content='éçã®èµ·æºã¨ãªã£ãçæã¯ä½ã¨ããã¦ããï¼', additional_kwargs={}, response_metadata={}, id='c74dc0c3-5eea-4f43-866d-f8416e49f44e'),
HumanMessage(content='以ä¸ã®ã³ã³ããã¹ãæ
å ±ã使ç¨ãã¦åçãã¦ãã ããã\n\néçã®èµ·æºã¯æç¢ºã«ã¯ããã¦ããªãããã¤ã®ãªã¹ã®çæã§ãããã¿ã¦ã³ãã¼ã«ããã¤ã®ãªã¹ç³»ç§»æ°ã«ãã£ã¦ã¢ã¡ãªã«ã«æã¡è¾¼ã¾ããå¾ã«å¤åããéçã¨ãã¦å½¢æãããã¨èããç ç©¶è
ãå¤ãã1830年代ãã1840å¹´\n\né¡ä¼¼ç«¶æ\n\n[ç·¨é]\n\néçã¨ã¯ç°ãªãèµ·æºãæã¤ç«¶æ\n\n[ç·¨é]\n\nã¯ãªã±ãã\n\nã©ãã¿ã¼\n\nã©ã¦ã³ãã¼ãº\n\nã¹ãã£ãã¯ãã¼ã«\n\nã·ã¥ã©ã¼ã¯ãã«\n\néçããæ´¾çããç«¶æ\n\n[ç·¨é]\n\nã½ãããã¼ã«\n\nã¹ãã£ãã¯ãã¼ã«\n\nã·ã¥ã©ã¼ã¯ãã«\n\néçããæ´¾çããç«¶æ\n\n[ç·¨é]\n\nã½ãããã¼ã«\n\nããµããã\n\nãã£ã¼ãã¼ã«\n\nããã¯ãã¼ã¹ãã¼ã«\n\nãã³ããã¼ã¹ãã¼ã«\n\nã©ã±ãããã¼ã¹ãã¼ã«', additional_kwargs={}, response_metadata={}, id='7a7ce564-7ec9-45c0-a45d-965ba4399cf7')]}
[3:writes] Finished step 3 with writes to 1 channel:
- messages -> [AIMessage(content='éçã®èµ·æºã¨ãªã£ãçæã¨ãã¦ã¯ãã¤ã®ãªã¹ã®ãã¿ã¦ã³ãã¼ã«ããæãããã¾ããã¿ã¦ã³ãã¼ã«ã¯ã¤ã®ãªã¹ç³»ç§»æ°ã«ãã£ã¦ã¢ã¡ãªã«ã«æã¡è¾¼ã¾ããããããå¤åãã¦ãã£ãã¨èãããã¦ãã¾ããå¤ãã®ç ç©¶è
ãããã®éç¨ãçµã¦éçãå½¢æãããã¨è¦ã¦ãã¾ãã', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 90, 'prompt_tokens': 242, 'total_tokens': 332, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_00428b782a', 'finish_reason': 'stop', 'logprobs': None}, id='run-8a1dd045-cf16-4f8a-a377-58385e3c461c-0', usage_metadata={'input_tokens': 242, 'output_tokens': 90, 'total_tokens': 332, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]
[3:checkpoint] State at the end of step 3:
{'messages': [HumanMessage(content='éçã®èµ·æºã¨ãªã£ãçæã¯ä½ã¨ããã¦ããï¼', additional_kwargs={}, response_metadata={}, id='c74dc0c3-5eea-4f43-866d-f8416e49f44e'),
HumanMessage(content='以ä¸ã®ã³ã³ããã¹ãæ
å ±ã使ç¨ãã¦åçãã¦ãã ããã\n\néçã®èµ·æºã¯æç¢ºã«ã¯ããã¦ããªãããã¤ã®ãªã¹ã®çæã§ãããã¿ã¦ã³ãã¼ã«ããã¤ã®ãªã¹ç³»ç§»æ°ã«ãã£ã¦ã¢ã¡ãªã«ã«æã¡è¾¼ã¾ããå¾ã«å¤åããéçã¨ãã¦å½¢æãããã¨èããç ç©¶è
ãå¤ãã1830年代ãã1840å¹´\n\né¡ä¼¼ç«¶æ\n\n[ç·¨é]\n\néçã¨ã¯ç°ãªãèµ·æºãæã¤ç«¶æ\n\n[ç·¨é]\n\nã¯ãªã±ãã\n\nã©ãã¿ã¼\n\nã©ã¦ã³ãã¼ãº\n\nã¹ãã£ãã¯ãã¼ã«\n\nã·ã¥ã©ã¼ã¯ãã«\n\néçããæ´¾çããç«¶æ\n\n[ç·¨é]\n\nã½ãããã¼ã«\n\nã¹ãã£ãã¯ãã¼ã«\n\nã·ã¥ã©ã¼ã¯ãã«\n\néçããæ´¾çããç«¶æ\n\n[ç·¨é]\n\nã½ãããã¼ã«\n\nããµããã\n\nãã£ã¼ãã¼ã«\n\nããã¯ãã¼ã¹ãã¼ã«\n\nãã³ããã¼ã¹ãã¼ã«\n\nã©ã±ãããã¼ã¹ãã¼ã«', additional_kwargs={}, response_metadata={}, id='7a7ce564-7ec9-45c0-a45d-965ba4399cf7'),
AIMessage(content='éçã®èµ·æºã¨ãªã£ãçæã¨ãã¦ã¯ãã¤ã®ãªã¹ã®ãã¿ã¦ã³ãã¼ã«ããæãããã¾ããã¿ã¦ã³ãã¼ã«ã¯ã¤ã®ãªã¹ç³»ç§»æ°ã«ãã£ã¦ã¢ã¡ãªã«ã«æã¡è¾¼ã¾ããããããå¤åãã¦ãã£ãã¨èãããã¦ãã¾ããå¤ãã®ç ç©¶è
ãããã®éç¨ãçµã¦éçãå½¢æãããã¨è¦ã¦ãã¾ãã', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 90, 'prompt_tokens': 242, 'total_tokens': 332, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_00428b782a', 'finish_reason': 'stop', 'logprobs': None}, id='run-8a1dd045-cf16-4f8a-a377-58385e3c461c-0', usage_metadata={'input_tokens': 242, 'output_tokens': 90, 'total_tokens': 332, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]}
response = graph.invoke(
{"messages": [{"role": "user", "content": "ãµãã«ã¼ã®ä¸çåã®å½é試åã¯ã©ã対ã©ãï¼"}]},
debug=True,
)
print(response["messages"][-1].content)
ãµãã«ã¼ã®ä¸çåã®å ¬å¼å½é試åã¯ã1867å¹´ã«è¡ãããã¤ã³ã°ã©ã³ãã¨ã¹ã³ããã©ã³ãã®è©¦åã§ãã¹ã³ã¢ã¯0-0ã®å¼ãåãã§ããã
å®è¡ãã°
[-1:checkpoint] State at the end of step -1:
{'messages': []}
[0:tasks] Starting 1 task for step 0:
- __start__ -> {'messages': [{'content': 'ãµãã«ã¼ã®ä¸çåã®å½é試åã¯ã©ã対ã©ãï¼', 'role': 'user'}]}
[0:writes] Finished step 0 with writes to 1 channel:
- messages -> [{'content': 'ãµãã«ã¼ã®ä¸çåã®å½é試åã¯ã©ã対ã©ãï¼', 'role': 'user'}]
[0:checkpoint] State at the end of step 0:
{'messages': [HumanMessage(content='ãµãã«ã¼ã®ä¸çåã®å½é試åã¯ã©ã対ã©ãï¼', additional_kwargs={}, response_metadata={}, id='29d8ca6c-a4e3-4253-a284-2d7eda634fad')]}
[1:tasks] Starting 1 task for step 1:
- router_node -> {'messages': [HumanMessage(content='ãµãã«ã¼ã®ä¸çåã®å½é試åã¯ã©ã対ã©ãï¼', additional_kwargs={}, response_metadata={}, id='29d8ca6c-a4e3-4253-a284-2d7eda634fad')]}
{'goto': 'node2'}
[1:writes] Finished step 1 with writes to 0 channels:
[1:checkpoint] State at the end of step 1:
{'messages': [HumanMessage(content='ãµãã«ã¼ã®ä¸çåã®å½é試åã¯ã©ã対ã©ãï¼', additional_kwargs={}, response_metadata={}, id='29d8ca6c-a4e3-4253-a284-2d7eda634fad')]}
[2:tasks] Starting 1 task for step 2:
- node2 -> {'messages': [HumanMessage(content='ãµãã«ã¼ã®ä¸çåã®å½é試åã¯ã©ã対ã©ãï¼', additional_kwargs={}, response_metadata={}, id='29d8ca6c-a4e3-4253-a284-2d7eda634fad')]}
[2:writes] Finished step 2 with writes to 1 channel:
- messages -> [{'content': '以ä¸ã®ã³ã³ããã¹ãæ
å ±ã使ç¨ãã¦åçãã¦ãã ããã\n'
'\n'
'ã§ãä¸çã§æåã®âå
¬å¼âå½é試åããã¤ã³ã°ã©ã³ãã¨ã¹ã³ããã©ã³ãã®éã§å®æ½ããããã¹ã³ã¢ã¯0ï¼0ã®å¼ãåãã ã£ã[25]ããã®å¾1880年代ã¾ã§ã«ãã¹ã³ããã©ã³ããã¦ã§ã¼ã«ãºãã¢ã¤ã«ã©ã³ãã§ã¯ãµãã«ã¼\n'
'\n'
'ã§ãä¸çã§æåã®âå
¬å¼âå½é試åããã¤ã³ã°ã©ã³ãã¨ã¹ã³ããã©ã³ãã®éã§å®æ½ããããã¹ã³ã¢ã¯0ï¼0ã®å¼ãåãã ã£ã[25]ããã®å¾1880年代ã¾ã§ã«ãã¹ã³ããã©ã³ããã¦ã§ã¼ã«ãºãã¢ã¤ã«ã©ã³ãã§ã¯ãµãã«ã¼\n'
'\n'
'ãã®FAã«ã¼ã«ã§ã®åã®è©¦åãã¤ã¾ãä¸çåã®ããµãã«ã¼ãã®è©¦åã¯ã1863å¹´12æ19æ¥ã«ã¤ã³ã°ã©ã³ãã§è¡ããããªããã¢ã³ã対ãã¼ã³ãºæ¦ã§ã0-0ã®å¼ãåãã ã£ã[25]ã',
'role': 'user'}]
[2:checkpoint] State at the end of step 2:
{'messages': [HumanMessage(content='ãµãã«ã¼ã®ä¸çåã®å½é試åã¯ã©ã対ã©ãï¼', additional_kwargs={}, response_metadata={}, id='29d8ca6c-a4e3-4253-a284-2d7eda634fad'),
HumanMessage(content='以ä¸ã®ã³ã³ããã¹ãæ
å ±ã使ç¨ãã¦åçãã¦ãã ããã\n\nã§ãä¸çã§æåã®âå
¬å¼âå½é試åããã¤ã³ã°ã©ã³ãã¨ã¹ã³ããã©ã³ãã®éã§å®æ½ããããã¹ã³ã¢ã¯0ï¼0ã®å¼ãåãã ã£ã[25]ããã®å¾1880年代ã¾ã§ã«ãã¹ã³ããã©ã³ããã¦ã§ã¼ã«ãºãã¢ã¤ã«ã©ã³ãã§ã¯ãµãã«ã¼\n\nã§ãä¸çã§æåã®âå
¬å¼âå½é試åããã¤ã³ã°ã©ã³ãã¨ã¹ã³ããã©ã³ãã®éã§å®æ½ããããã¹ã³ã¢ã¯0ï¼0ã®å¼ãåãã ã£ã[25]ããã®å¾1880年代ã¾ã§ã«ãã¹ã³ããã©ã³ããã¦ã§ã¼ã«ãºãã¢ã¤ã«ã©ã³ãã§ã¯ãµãã«ã¼\n\nãã®FAã«ã¼ã«ã§ã®åã®è©¦åãã¤ã¾ãä¸çåã®ããµãã«ã¼ãã®è©¦åã¯ã1863å¹´12æ19æ¥ã«ã¤ã³ã°ã©ã³ãã§è¡ããããªããã¢ã³ã対ãã¼ã³ãºæ¦ã§ã0-0ã®å¼ãåãã ã£ã[25]ã', additional_kwargs={}, response_metadata={}, id='f081b9fe-7441-4c27-a77d-2d3938b4ce70')]}
[3:tasks] Starting 1 task for step 3:
- generate_answer -> {'messages': [HumanMessage(content='ãµãã«ã¼ã®ä¸çåã®å½é試åã¯ã©ã対ã©ãï¼', additional_kwargs={}, response_metadata={}, id='29d8ca6c-a4e3-4253-a284-2d7eda634fad'),
HumanMessage(content='以ä¸ã®ã³ã³ããã¹ãæ
å ±ã使ç¨ãã¦åçãã¦ãã ããã\n\nã§ãä¸çã§æåã®âå
¬å¼âå½é試åããã¤ã³ã°ã©ã³ãã¨ã¹ã³ããã©ã³ãã®éã§å®æ½ããããã¹ã³ã¢ã¯0ï¼0ã®å¼ãåãã ã£ã[25]ããã®å¾1880年代ã¾ã§ã«ãã¹ã³ããã©ã³ããã¦ã§ã¼ã«ãºãã¢ã¤ã«ã©ã³ãã§ã¯ãµãã«ã¼\n\nã§ãä¸çã§æåã®âå
¬å¼âå½é試åããã¤ã³ã°ã©ã³ãã¨ã¹ã³ããã©ã³ãã®éã§å®æ½ããããã¹ã³ã¢ã¯0ï¼0ã®å¼ãåãã ã£ã[25]ããã®å¾1880年代ã¾ã§ã«ãã¹ã³ããã©ã³ããã¦ã§ã¼ã«ãºãã¢ã¤ã«ã©ã³ãã§ã¯ãµãã«ã¼\n\nãã®FAã«ã¼ã«ã§ã®åã®è©¦åãã¤ã¾ãä¸çåã®ããµãã«ã¼ãã®è©¦åã¯ã1863å¹´12æ19æ¥ã«ã¤ã³ã°ã©ã³ãã§è¡ããããªããã¢ã³ã対ãã¼ã³ãºæ¦ã§ã0-0ã®å¼ãåãã ã£ã[25]ã', additional_kwargs={}, response_metadata={}, id='f081b9fe-7441-4c27-a77d-2d3938b4ce70')]}
[3:writes] Finished step 3 with writes to 1 channel:
- messages -> [AIMessage(content='ãµãã«ã¼ã®ä¸çåã®å
¬å¼å½é試åã¯ã1867å¹´ã«è¡ãããã¤ã³ã°ã©ã³ãã¨ã¹ã³ããã©ã³ãã®è©¦åã§ãã¹ã³ã¢ã¯0-0ã®å¼ãåãã§ããã', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 49, 'prompt_tokens': 249, 'total_tokens': 298, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_13eed4fce1', 'finish_reason': 'stop', 'logprobs': None}, id='run-70ee4d89-00fa-40f1-8a3e-ff79a17722ca-0', usage_metadata={'input_tokens': 249, 'output_tokens': 49, 'total_tokens': 298, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]
[3:checkpoint] State at the end of step 3:
{'messages': [HumanMessage(content='ãµãã«ã¼ã®ä¸çåã®å½é試åã¯ã©ã対ã©ãï¼', additional_kwargs={}, response_metadata={}, id='29d8ca6c-a4e3-4253-a284-2d7eda634fad'),
HumanMessage(content='以ä¸ã®ã³ã³ããã¹ãæ
å ±ã使ç¨ãã¦åçãã¦ãã ããã\n\nã§ãä¸çã§æåã®âå
¬å¼âå½é試åããã¤ã³ã°ã©ã³ãã¨ã¹ã³ããã©ã³ãã®éã§å®æ½ããããã¹ã³ã¢ã¯0ï¼0ã®å¼ãåãã ã£ã[25]ããã®å¾1880年代ã¾ã§ã«ãã¹ã³ããã©ã³ããã¦ã§ã¼ã«ãºãã¢ã¤ã«ã©ã³ãã§ã¯ãµãã«ã¼\n\nã§ãä¸çã§æåã®âå
¬å¼âå½é試åããã¤ã³ã°ã©ã³ãã¨ã¹ã³ããã©ã³ãã®éã§å®æ½ããããã¹ã³ã¢ã¯0ï¼0ã®å¼ãåãã ã£ã[25]ããã®å¾1880年代ã¾ã§ã«ãã¹ã³ããã©ã³ããã¦ã§ã¼ã«ãºãã¢ã¤ã«ã©ã³ãã§ã¯ãµãã«ã¼\n\nãã®FAã«ã¼ã«ã§ã®åã®è©¦åãã¤ã¾ãä¸çåã®ããµãã«ã¼ãã®è©¦åã¯ã1863å¹´12æ19æ¥ã«ã¤ã³ã°ã©ã³ãã§è¡ããããªããã¢ã³ã対ãã¼ã³ãºæ¦ã§ã0-0ã®å¼ãåãã ã£ã[25]ã', additional_kwargs={}, response_metadata={}, id='f081b9fe-7441-4c27-a77d-2d3938b4ce70'),
AIMessage(content='ãµãã«ã¼ã®ä¸çåã®å
¬å¼å½é試åã¯ã1867å¹´ã«è¡ãããã¤ã³ã°ã©ã³ãã¨ã¹ã³ããã©ã³ãã®è©¦åã§ãã¹ã³ã¢ã¯0-0ã®å¼ãåãã§ããã', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 49, 'prompt_tokens': 249, 'total_tokens': 298, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_13eed4fce1', 'finish_reason': 'stop', 'logprobs': None}, id='run-70ee4d89-00fa-40f1-8a3e-ff79a17722ca-0', usage_metadata={'input_tokens': 249, 'output_tokens': 49, 'total
ããããã®è³ªåã«ãã£ããã¼ã¿ã½ã¼ã¹ãåç §ããé©åãªåçããã¦ãããã¨ã確èªã§ãã¾ãã
3. ãã¼ã¿ã½ã¼ã¹2ã¤+Webæ¤ç´¢ã®RAG
ä»åº¦ã¯ãã¼ã¿ã½ã¼ã¹ã«ãªã質åãæ¥ãå ´åãWebæ¤ç´¢ãè¡ã£ã¦åçãè¡ãRAGã使ãã¾ãã Webæ¤ç´¢ã«ã¯Tavily Search APIã使ç¨ãã¾ãã

ã³ã¼ãä¾
ã³ã¼ãå ¨æ
from typing import Annotated from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_chroma import Chroma from langchain_community.document_loaders import UnstructuredURLLoader from langchain_community.tools import TavilySearchResults from langchain_core.prompts import ChatPromptTemplate from langchain_openai import ChatOpenAI, OpenAIEmbeddings from langgraph.graph import END, START, StateGraph from langgraph.graph.message import add_messages from langgraph.types import Command from typing_extensions import Literal, TypedDict llm = ChatOpenAI(model="gpt-4o-mini") embedding_model = OpenAIEmbeddings() vectorstore_a = Chroma( embedding_function=embedding_model, persist_directory="./chroma_db_a" ) vectorstore_b = Chroma( embedding_function=embedding_model, persist_directory="./chroma_db_b" ) class State(TypedDict): messages: Annotated[list[str], add_messages] query: str class RouterResponse(TypedDict): goto: Literal["node1", "node2", "generate_query"] class QueryResponse(TypedDict): query: str def router_node(state: State) -> Command[Literal["node1", "node2", "generate_query"]]: llm = ChatOpenAI(model="gpt-4o-mini") prompt = ChatPromptTemplate.from_messages( [ ( "system", "質åãéçã«é¢ãããã®ã§ããã°node1ã«ããµãã«ã¼ã®è³ªåã§ããã°node2, ãã以å¤ã®è³ªåã¯generate_queryã«ã«ã¼ãã£ã³ã°ãã¦ãã ããã", ), ("user", "{message}"), ] ) chain = prompt | llm.with_structured_output(RouterResponse) response = chain.invoke({"message": state["messages"][-1]}) print(response) return Command(goto=response["goto"]) def retrieve(state: State, vectorstore: Chroma): question = state["messages"][-1].content retriever = vectorstore.similarity_search(question, k=3) context = "\n\n".join([doc.page_content for doc in retriever]) return { "messages": [ { "role": "user", "content": f"以ä¸ã®ã³ã³ããã¹ãæ å ±ã使ç¨ãã¦åçãã¦ãã ããã\n\n{context}", } ] } def generate_query(state: State): llm = ChatOpenAI(model="gpt-4o-mini") message = state["messages"][-1].content prompt = ChatPromptTemplate.from_messages( [ ("system", "Webæ¤ç´¢ç¨ã®ã¯ã¨ãªãçæãã¦ãã ããã"), ("user", "{message}"), ] ) chain = prompt | llm.with_structured_output(QueryResponse) response = chain.invoke({"message": state["messages"][-1]}) print(response) return {"query": response["query"]} def node3(state: State): search = TavilySearchResults(max_results=3) result = search.invoke(state["query"]) print(result) context = "\n\n".join([doc["content"] for doc in result]) return { "messages": [ { "role": "user", "content": f"以ä¸ã®ã³ã³ããã¹ãæ å ±ã使ç¨ãã¦åçãã¦ãã ããã\n\n{context}", } ] } def generate_answer(state: State): response = llm.invoke(state["messages"]) return {"messages": [response]} def node1(state: State, vectorstore=vectorstore_a): return retrieve(state, vectorstore) def node2(state: State, vectorstore=vectorstore_b): return retrieve(state, vectorstore) urls_a = ["https://ja.wikipedia.org/wiki/%E9%87%8E%E7%90%83"] urls_b = ["https://ja.wikipedia.org/wiki/%E3%82%B5%E3%83%83%E3%82%AB%E3%83%BC"] def load_data(urls): loader = UnstructuredURLLoader(urls) docs = loader.load() text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200) splits = text_splitter.split_documents(docs) return splits docs_a = load_data(urls_a) docs_b = load_data(urls_b) vectorstore_a.add_documents(docs_a) vectorstore_b.add_documents(docs_b) retriever_a = vectorstore_a.as_retriever(search_kwargs={"k": 3}) retriever_b = vectorstore_b.as_retriever(search_kwargs={"k": 3}) builder = StateGraph(State) builder.add_edge(START, "router_node") builder.add_node(router_node) builder.add_node(node1) builder.add_node(node2) builder.add_node(generate_query) builder.add_node(node3) builder.add_node(generate_answer) builder.add_edge("node1", "generate_answer") builder.add_edge("node2", "generate_answer") builder.add_edge("generate_query", "node3") builder.add_edge("node3", "generate_answer") builder.add_edge("generate_answer", END) graph = builder.compile()
Webæ¤ç´¢ããããã®ãã¼ããnode3ã¨ãã¦è¿½å ãã¾ãã
def node3(state: State): search = TavilySearchResults(max_results=3) result = search.invoke(state["query"]) print(result) context = "\n\n".join([doc["content"] for doc in result]) return { "messages": [ { "role": "user", "content": f"以ä¸ã®ã³ã³ããã¹ãæ å ±ã使ç¨ãã¦åçãã¦ãã ããã\n\n{context}", } ] }
ã¾ããWebæ¤ç´¢ããåã¯æ¤ç´¢ã«é©ããã¯ã¨ãªã«å¤æããããã®ãã¼ãã追å ãã¾ãã
def generate_query(state: State): llm = ChatOpenAI(model="gpt-4o-mini") message = state["messages"][-1].content prompt = ChatPromptTemplate.from_messages( [ ("system", "Webæ¤ç´¢ç¨ã®ã¯ã¨ãªãçæãã¦ãã ããã"), ("user", "{message}"), ] ) chain = prompt | llm.with_structured_output(QueryResponse) response = chain.invoke({"message": state["messages"][-1]}) print(response) return {"query": response["query"]}
ã°ã©ãæ§é

å®è¡ä¾
response = graph.invoke(
{"messages": [{"role": "user", "content": "ããªãªãªã³ããã¯ã®ç·åãã¹ã±ãããã¼ã«ã®åªåå½ã¯ã©ãï¼"}]},
debug=True,
)
print(response["messages"][-1].content)
ããªãªãªã³ããã¯ã®ç·åãã¹ã±ãããã¼ã«ã§åªåããå½ã¯ã¢ã¡ãªã«åè¡å½ã§ããæ±ºåæ¦ã§ã¯ãã©ã³ã¹ã98対87ã§ç ´ãã5大ä¼é£ç¶17度ç®ã®éã¡ãã«ãç²å¾ãã¾ããã
å®è¡ãã°
[-1:checkpoint] State at the end of step -1:
{'messages': []}
[0:tasks] Starting 1 task for step 0:
- __start__ -> {'messages': [{'content': 'ããªãªãªã³ããã¯ã®ç·åãã¹ã±ãããã¼ã«ã®åªåå½ã¯ã©ãï¼', 'role': 'user'}]}
[0:writes] Finished step 0 with writes to 1 channel:
- messages -> [{'content': 'ããªãªãªã³ããã¯ã®ç·åãã¹ã±ãããã¼ã«ã®åªåå½ã¯ã©ãï¼', 'role': 'user'}]
[0:checkpoint] State at the end of step 0:
{'messages': [HumanMessage(content='ããªãªãªã³ããã¯ã®ç·åãã¹ã±ãããã¼ã«ã®åªåå½ã¯ã©ãï¼', additional_kwargs={}, response_metadata={}, id='a1b7b778-d44c-439e-84a1-52e58ca0b5a0')]}
[1:tasks] Starting 1 task for step 1:
- router_node -> {'messages': [HumanMessage(content='ããªãªãªã³ããã¯ã®ç·åãã¹ã±ãããã¼ã«ã®åªåå½ã¯ã©ãï¼', additional_kwargs={}, response_metadata={}, id='a1b7b778-d44c-439e-84a1-52e58ca0b5a0')]}
{'goto': 'generate_query'}
[1:writes] Finished step 1 with writes to 0 channels:
[1:checkpoint] State at the end of step 1:
{'messages': [HumanMessage(content='ããªãªãªã³ããã¯ã®ç·åãã¹ã±ãããã¼ã«ã®åªåå½ã¯ã©ãï¼', additional_kwargs={}, response_metadata={}, id='a1b7b778-d44c-439e-84a1-52e58ca0b5a0')]}
[2:tasks] Starting 1 task for step 2:
- generate_query -> {'messages': [HumanMessage(content='ããªãªãªã³ããã¯ã®ç·åãã¹ã±ãããã¼ã«ã®åªåå½ã¯ã©ãï¼', additional_kwargs={}, response_metadata={}, id='a1b7b778-d44c-439e-84a1-52e58ca0b5a0')]}
{'query': '2024 ããªãªãªã³ãã㯠ç·åãã¹ã±ãããã¼ã« åªåå½ã¯ã©ã'}
[2:writes] Finished step 2 with writes to 1 channel:
- query -> '2024 ããªãªãªã³ãã㯠ç·åãã¹ã±ãããã¼ã« åªåå½ã¯ã©ã'
[2:checkpoint] State at the end of step 2:
{'messages': [HumanMessage(content='ããªãªãªã³ããã¯ã®ç·åãã¹ã±ãããã¼ã«ã®åªåå½ã¯ã©ãï¼', additional_kwargs={}, response_metadata={}, id='a1b7b778-d44c-439e-84a1-52e58ca0b5a0')],
'query': '2024 ããªãªãªã³ãã㯠ç·åãã¹ã±ãããã¼ã« åªåå½ã¯ã©ã'}
[3:tasks] Starting 1 task for step 3:
- node3 -> {'messages': [HumanMessage(content='ããªãªãªã³ããã¯ã®ç·åãã¹ã±ãããã¼ã«ã®åªåå½ã¯ã©ãï¼', additional_kwargs={}, response_metadata={}, id='a1b7b778-d44c-439e-84a1-52e58ca0b5a0')],
'query': '2024 ããªãªãªã³ãã㯠ç·åãã¹ã±ãããã¼ã« åªåå½ã¯ã©ã'}
[{'url': 'https://ja.wikipedia.org/wiki/2024%E5%B9%B4%E3%83%91%E3%83%AA%E3%82%AA%E3%83%AA%E3%83%B3%E3%83%94%E3%83%83%E3%82%AF%E3%81%AE%E3%83%90%E3%82%B9%E3%82%B1%E3%83%83%E3%83%88%E3%83%9C%E3%83%BC%E3%83%AB%E7%AB%B6%E6%8A%80', 'content': '... åªåå½â¦ ã¢ã¡ãªã«åè¡å½ã®æ · ã¢ã¡ãªã«åè¡å½; ãªãªã³ããã¯éå¬å½â¦ ãã©ã³ã¹ã®æ · ãã©ã³ã¹. ä¸çæçµäºé¸ã®çµã¿åããæ½é¸ã¯2023å¹´10æ5æ¥ã«è¡ãããã16ãå½ã¯ç´è¿'}, {'url': 'https://www.olympics.com/ja/news/paris2024-basketball-men-france-united-states-of-america', 'content': 'ããª2024ãã¹ã±ãããã¼ã«ç·å決åãç¾å°æé8æ10æ¥ã«ãã«ã·ã¼ã»ã¢ãªã¼ãã§è¡ããããæ±ºåã«é²åºããã®ã¯éå¬å½ã»ãã©ã³ã¹ä»£è¡¨ã¨ããªãªã³ããã¯4é£è¦'}, {'url': 'https://www.yomiuri.co.jp/olympic/2024/20240811-OYT1T50061/', 'content': 'ããªãªãªã³ããã¯ã»ç·åãã¹ã±ãããã¼ã«ã®æ±ºåã10æ¥è¡ãããã¢ã¡ãªã«ã98â87ã§ãã©ã³ã¹ãç ´ãã5大ä¼é£ç¶17度ç®ã¨ãªãéã¡ãã«ãç²å¾ããã'}]
[3:writes] Finished step 3 with writes to 1 channel:
- messages -> [{'content': '以ä¸ã®ã³ã³ããã¹ãæ
å ±ã使ç¨ãã¦åçãã¦ãã ããã\n'
'\n'
'... åªåå½â¦ ã¢ã¡ãªã«åè¡å½ã®æ · ã¢ã¡ãªã«åè¡å½; ãªãªã³ããã¯éå¬å½â¦ ãã©ã³ã¹ã®æ · ãã©ã³ã¹. '
'ä¸çæçµäºé¸ã®çµã¿åããæ½é¸ã¯2023å¹´10æ5æ¥ã«è¡ãããã16ãå½ã¯ç´è¿\n'
'\n'
'ããª2024ãã¹ã±ãããã¼ã«ç·å決åãç¾å°æé8æ10æ¥ã«ãã«ã·ã¼ã»ã¢ãªã¼ãã§è¡ããããæ±ºåã«é²åºããã®ã¯éå¬å½ã»ãã©ã³ã¹ä»£è¡¨ã¨ããªãªã³ããã¯4é£è¦\n'
'\n'
'ããªãªãªã³ããã¯ã»ç·åãã¹ã±ãããã¼ã«ã®æ±ºåã10æ¥è¡ãããã¢ã¡ãªã«ã98â87ã§ãã©ã³ã¹ãç ´ãã5大ä¼é£ç¶17度ç®ã¨ãªãéã¡ãã«ãç²å¾ããã',
'role': 'user'}]
[3:checkpoint] State at the end of step 3:
{'messages': [HumanMessage(content='ããªãªãªã³ããã¯ã®ç·åãã¹ã±ãããã¼ã«ã®åªåå½ã¯ã©ãï¼', additional_kwargs={}, response_metadata={}, id='a1b7b778-d44c-439e-84a1-52e58ca0b5a0'),
HumanMessage(content='以ä¸ã®ã³ã³ããã¹ãæ
å ±ã使ç¨ãã¦åçãã¦ãã ããã\n\n... åªåå½â¦ ã¢ã¡ãªã«åè¡å½ã®æ · ã¢ã¡ãªã«åè¡å½; ãªãªã³ããã¯éå¬å½â¦ ãã©ã³ã¹ã®æ · ãã©ã³ã¹. ä¸çæçµäºé¸ã®çµã¿åããæ½é¸ã¯2023å¹´10æ5æ¥ã«è¡ãããã16ãå½ã¯ç´è¿\n\nããª2024ãã¹ã±ãããã¼ã«ç·å決åãç¾å°æé8æ10æ¥ã«ãã«ã·ã¼ã»ã¢ãªã¼ãã§è¡ããããæ±ºåã«é²åºããã®ã¯éå¬å½ã»ãã©ã³ã¹ä»£è¡¨ã¨ããªãªã³ããã¯4é£è¦\n\nããªãªãªã³ããã¯ã»ç·åãã¹ã±ãããã¼ã«ã®æ±ºåã10æ¥è¡ãããã¢ã¡ãªã«ã98â87ã§ãã©ã³ã¹ãç ´ãã5大ä¼é£ç¶17度ç®ã¨ãªãéã¡ãã«ãç²å¾ããã', additional_kwargs={}, response_metadata={}, id='e3b281ba-b89d-47c8-923d-99839b256499')],
'query': '2024 ããªãªãªã³ãã㯠ç·åãã¹ã±ãããã¼ã« åªåå½ã¯ã©ã'}
[4:tasks] Starting 1 task for step 4:
- generate_answer -> {'messages': [HumanMessage(content='ããªãªãªã³ããã¯ã®ç·åãã¹ã±ãããã¼ã«ã®åªåå½ã¯ã©ãï¼', additional_kwargs={}, response_metadata={}, id='a1b7b778-d44c-439e-84a1-52e58ca0b5a0'),
HumanMessage(content='以ä¸ã®ã³ã³ããã¹ãæ
å ±ã使ç¨ãã¦åçãã¦ãã ããã\n\n... åªåå½â¦ ã¢ã¡ãªã«åè¡å½ã®æ · ã¢ã¡ãªã«åè¡å½; ãªãªã³ããã¯éå¬å½â¦ ãã©ã³ã¹ã®æ · ãã©ã³ã¹. ä¸çæçµäºé¸ã®çµã¿åããæ½é¸ã¯2023å¹´10æ5æ¥ã«è¡ãããã16ãå½ã¯ç´è¿\n\nããª2024ãã¹ã±ãããã¼ã«ç·å決åãç¾å°æé8æ10æ¥ã«ãã«ã·ã¼ã»ã¢ãªã¼ãã§è¡ããããæ±ºåã«é²åºããã®ã¯éå¬å½ã»ãã©ã³ã¹ä»£è¡¨ã¨ããªãªã³ããã¯4é£è¦\n\nããªãªãªã³ããã¯ã»ç·åãã¹ã±ãããã¼ã«ã®æ±ºåã10æ¥è¡ãããã¢ã¡ãªã«ã98â87ã§ãã©ã³ã¹ãç ´ãã5大ä¼é£ç¶17度ç®ã¨ãªãéã¡ãã«ãç²å¾ããã', additional_kwargs={}, response_metadata={}, id='e3b281ba-b89d-47c8-923d-99839b256499')],
'query': '2024 ããªãªãªã³ãã㯠ç·åãã¹ã±ãããã¼ã« åªåå½ã¯ã©ã'}
[4:writes] Finished step 4 with writes to 1 channel:
- messages -> [AIMessage(content='ããªãªãªã³ããã¯ã®ç·åãã¹ã±ãããã¼ã«ã§åªåããå½ã¯ã¢ã¡ãªã«åè¡å½ã§ããæ±ºåæ¦ã§ã¯ãã©ã³ã¹ã98対87ã§ç ´ãã5大ä¼é£ç¶17度ç®ã®éã¡ãã«ãç²å¾ãã¾ããã', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 62, 'prompt_tokens': 230, 'total_tokens': 292, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_00428b782a', 'finish_reason': 'stop', 'logprobs': None}, id='run-9e37f769-7c99-46d1-aaae-de1c2b3a0021-0', usage_metadata={'input_tokens': 230, 'output_tokens': 62, 'total_tokens': 292, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]
[4:checkpoint] State at the end of step 4:
{'messages': [HumanMessage(content='ããªãªãªã³ããã¯ã®ç·åãã¹ã±ãããã¼ã«ã®åªåå½ã¯ã©ãï¼', additional_kwargs={}, response_metadata={}, id='a1b7b778-d44c-439e-84a1-52e58ca0b5a0'),
HumanMessage(content='以ä¸ã®ã³ã³ããã¹ãæ
å ±ã使ç¨ãã¦åçãã¦ãã ããã\n\n... åªåå½â¦ ã¢ã¡ãªã«åè¡å½ã®æ · ã¢ã¡ãªã«åè¡å½; ãªãªã³ããã¯éå¬å½â¦ ãã©ã³ã¹ã®æ · ãã©ã³ã¹. ä¸çæçµäºé¸ã®çµã¿åããæ½é¸ã¯2023å¹´10æ5æ¥ã«è¡ãããã16ãå½ã¯ç´è¿\n\nããª2024ãã¹ã±ãããã¼ã«ç·å決åãç¾å°æé8æ10æ¥ã«ãã«ã·ã¼ã»ã¢ãªã¼ãã§è¡ããããæ±ºåã«é²åºããã®ã¯éå¬å½ã»ãã©ã³ã¹ä»£è¡¨ã¨ããªãªã³ããã¯4é£è¦\n\nããªãªãªã³ããã¯ã»ç·åãã¹ã±ãããã¼ã«ã®æ±ºåã10æ¥è¡ãããã¢ã¡ãªã«ã98â87ã§ãã©ã³ã¹ãç ´ãã5大ä¼é£ç¶17度ç®ã¨ãªãéã¡ãã«ãç²å¾ããã', additional_kwargs={}, response_metadata={}, id='e3b281ba-b89d-47c8-923d-99839b256499'),
AIMessage(content='ããªãªãªã³ããã¯ã®ç·åãã¹ã±ãããã¼ã«ã§åªåããå½ã¯ã¢ã¡ãªã«åè¡å½ã§ããæ±ºåæ¦ã§ã¯ãã©ã³ã¹ã98対87ã§ç ´ãã5大ä¼é£ç¶17度ç®ã®éã¡ãã«ãç²å¾ãã¾ããã', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 62, 'prompt_tokens': 230, 'total_tokens': 292, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_00428b782a', 'finish_reason': 'stop', 'logprobs': None}, id='run-9e37f769-7c99-46d1-aaae-de1c2b3a0021-0', usage_metadata={'input_tokens': 230, 'output_tokens': 62, 'total_tokens': 292, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})],
'query': '2024 ããªãªãªã³ãã㯠ç·åãã¹ã±ãããã¼ã« åªåå½ã¯ã©ã'}
ãµãã«ã¼ã¨éç以å¤ã®è³ªåãªã®ã§ããã¼ã¿ã½ã¼ã¹ã¯åç §ãããWebæ¤ç´¢ãè¡ããæ£ããåçãå°ãåºãã¦ãããã¨ã確èªã§ãã¾ãã
ã¾ã¨ã
ä»åã¯LangGraphãç¨ããRAGãæ§ç¯ãã¦ã¿ã¾ãããLangGraphã¯è¨è¿°éãå¤ããªããã¡ã§ããããã©ãã¯ããã¯ã¹ãªãå¦çãæ¸ããã®ããããªã¨æã£ã¦ãã¾ããä»åã¯åç´ãªã«ã¼ãã£ã³ã°ã®ã¯ã¼ã¯ããã¼ã®RAGã§ããããæ¬¡ã¯ãã£ã¨èªå¾çã«è³ªåã«å¯¾å¦ãã¦ãããAgenticãªRAGãæ§ç¯ãã¦ã¿ããã§ãã