Skip to content

Instantly share code, notes, and snippets.

@mahm
Last active January 30, 2024 01:07
Show Gist options
  • Save mahm/c74fa81357f21099080103402952568a to your computer and use it in GitHub Desktop.
Save mahm/c74fa81357f21099080103402952568a to your computer and use it in GitHub Desktop.
LangGraphによるブログ記事作成エージェントの作成デモ
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"id": "initial_id",
"metadata": {
"collapsed": true,
"ExecuteTime": {
"end_time": "2024-01-30T01:05:37.944479Z",
"start_time": "2024-01-30T01:05:37.908706Z"
}
},
"outputs": [],
"source": [
"from getpass import getpass\n",
"import os\n",
"\n",
"\n",
"def _set_if_undefined(var: str):\n",
" if not os.environ.get(var):\n",
" os.environ[var] = getpass(f\"Please provide your {var}\")\n",
"\n",
"\n",
"# 必須APIキーの確認\n",
"_set_if_undefined(\"OPENAI_API_KEY\")\n",
"_set_if_undefined(\"LANGCHAIN_API_KEY\")\n",
"_set_if_undefined(\"TAVILY_API_KEY\")\n",
"\n",
"# LangSmithの設定\n",
"os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
"os.environ[\"LANGCHAIN_PROJECT\"] = \"blog_supervisor_dev\"\n",
"\n",
"LLM_SMART_MODEL = \"gpt-4-turbo-preview\""
]
},
{
"cell_type": "markdown",
"source": [
"## エージェントを生成するユーティリティ関数の定義\n",
"エージェントの実装を簡単にするため、LangChainのAgentExecutorを利用している。"
],
"metadata": {
"collapsed": false
},
"id": "c3ab688113dc357d"
},
{
"cell_type": "code",
"outputs": [],
"source": [
"from langchain_core.runnables import Runnable\n",
"from langchain.output_parsers.openai_functions import JsonOutputFunctionsParser\n",
"from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n",
"from langchain.agents import AgentExecutor, create_openai_functions_agent\n",
"from langchain_openai import ChatOpenAI\n",
"from typing import TypedDict\n",
"\n",
"\n",
"class AgentDescription(TypedDict):\n",
" name: str\n",
" description: str\n",
" \n",
"def create_agent(\n",
" llm: ChatOpenAI,\n",
" tools: list,\n",
" system_prompt: str,\n",
") -> AgentExecutor:\n",
" system_prompt += \"\\nWork autonomously according to your specialty, using the tools available to you.\"\n",
" \" Do not ask for clarification.\"\n",
" \" Your other team members (and other teams) will collaborate with you with their own specialties.\"\n",
" \" You are chosen for a reason! You are one of the following team members: {team_members}.\"\n",
" prompt = ChatPromptTemplate.from_messages(\n",
" [\n",
" (\n",
" \"system\",\n",
" system_prompt,\n",
" ),\n",
" MessagesPlaceholder(variable_name=\"messages\"),\n",
" MessagesPlaceholder(variable_name=\"agent_scratchpad\"),\n",
" ]\n",
" )\n",
" agent = create_openai_functions_agent(llm, tools, prompt)\n",
" return AgentExecutor(agent=agent, tools=tools)\n",
"\n",
"\n",
"def create_team_supervisor(\n",
" llm: ChatOpenAI,\n",
" system_prompt: str,\n",
" members: list[AgentDescription]\n",
") -> Runnable:\n",
" member_names = [member[\"name\"] for member in members]\n",
" team_members = []\n",
" for member in members:\n",
" team_members.append(f\"name: {member['name']}\\ndescription: {member['description']}\")\n",
" options = [\"FINISH\"] + member_names\n",
" function_def = {\n",
" \"name\": \"route\",\n",
" \"description\": \"Select the next role.\",\n",
" \"parameters\": {\n",
" \"title\": \"routeSchema\",\n",
" \"type\": \"object\",\n",
" \"properties\": {\n",
" \"next\": {\n",
" \"title\": \"Next\",\n",
" \"anyOf\": [\n",
" {\"enum\": options},\n",
" ],\n",
" },\n",
" },\n",
" \"required\": [\"next\"],\n",
" },\n",
" }\n",
" prompt = ChatPromptTemplate.from_messages(\n",
" [\n",
" (\"system\", system_prompt),\n",
" MessagesPlaceholder(variable_name=\"messages\"),\n",
" (\n",
" \"system\",\n",
" \"Given the conversation above, who should act next?\"\n",
" \" Or should we FINISH? Select one of option: {options}\",\n",
" ),\n",
" ]\n",
" ).partial(options=str(options), team_members=\"\\n\\n\".join(team_members))\n",
" return (\n",
" prompt\n",
" | llm.bind_functions(functions=[function_def], function_call=\"route\")\n",
" | JsonOutputFunctionsParser()\n",
" )\n"
],
"metadata": {
"collapsed": false,
"ExecuteTime": {
"end_time": "2024-01-30T01:05:39.262385Z",
"start_time": "2024-01-30T01:05:37.950390Z"
}
},
"id": "7cd85f98a25444d8",
"execution_count": 2
},
{
"cell_type": "markdown",
"source": [
"## Researchエージェントの定義\n",
"Web検索にはTavilyを利用している。\n",
"TavilyもしくはURLの直接アクセスをサポートするために、\n",
"function callingを利用したエージェントを利用している。"
],
"metadata": {
"collapsed": false
},
"id": "9906fb0da9f65eda"
},
{
"cell_type": "code",
"outputs": [],
"source": [
"from langchain_community.document_loaders import WebBaseLoader\n",
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
"from langchain_core.tools import tool\n",
"\n",
"llm = ChatOpenAI(model_name=LLM_SMART_MODEL)\n",
"tavily_tool = TavilySearchResults(max_results=5)\n",
"\n",
"\n",
"@tool\n",
"def scrape_webpages(urls: list[str]) -> str:\n",
" \"\"\"Use requests and bs4 to scrape the provided web pages for detailed information.\"\"\"\n",
" loader = WebBaseLoader(urls)\n",
" docs = loader.load()\n",
" return \"\\n\\n\".join(\n",
" [\n",
" f'<Document name=\"{doc.metadata.get(\"title\", \"\")}\">\\n{doc.page_content}\\n</Document>'\n",
" for doc in docs\n",
" ]\n",
" )\n",
"\n",
"\n",
"def create_research_agent() -> Runnable:\n",
" prompt = \"You are a research assistant who can search for up-to-date info using the tavily search engine.\"\n",
" return create_agent(llm, [tavily_tool, scrape_webpages], prompt)\n"
],
"metadata": {
"collapsed": false,
"ExecuteTime": {
"end_time": "2024-01-30T01:05:39.351450Z",
"start_time": "2024-01-30T01:05:39.264081Z"
}
},
"id": "51bb405efa6d5c2c",
"execution_count": 3
},
{
"cell_type": "markdown",
"source": [
"## LangGraphに設定するノードの定義\n",
"ノードには関数またはRunnableが設定できる。\n",
"今回はステートの更新差分を分かりやすくするために関数を設定している。"
],
"metadata": {
"collapsed": false
},
"id": "bfa5346a8f0f9294"
},
{
"cell_type": "code",
"outputs": [],
"source": [
"import operator\n",
"from typing import Annotated\n",
"from langchain_core.output_parsers import StrOutputParser\n",
"from langchain_core.runnables import RunnablePassthrough\n",
"from langchain_core.messages import BaseMessage, AIMessage\n",
"\n",
"# 各ノードの名称を定義\n",
"RESEARCH_NODE = \"research\"\n",
"QUALITY_ASSURANCE_NODE = \"quality_assurance\"\n",
"WRITER_NODE = \"writer\"\n",
"SUPERVISOR_NODE = \"supervisor\"\n",
"\n",
"# チームメンバーの定義\n",
"team_members = [\n",
" {\"name\": RESEARCH_NODE,\n",
" \"description\": \"Search the web for necessary information and write articles as requested by users.\"},\n",
" {\"name\": QUALITY_ASSURANCE_NODE,\n",
" \"description\": f\"Check that the quality of the article meets the criteria. If not, return feedback. The acceptability of deliverables is always determined by {QUALITY_ASSURANCE_NODE}.\"},\n",
" {\"name\": WRITER_NODE,\n",
" \"description\": \"Write articles that even the world's best AI checkers would judge as written by a human.\"},\n",
"]\n",
"\n",
"\n",
"# トップレベルのステートを定義\n",
"class TopState(TypedDict):\n",
" messages: Annotated[list[BaseMessage], operator.add]\n",
" output: str\n",
" next: str\n",
"\n",
"\n",
"def create_llm_chain(llm: ChatOpenAI, system_message: str) -> Runnable:\n",
" prompt = ChatPromptTemplate.from_messages(\n",
" [(\"system\", system_message), (\"user\", \"{message}\")]\n",
" )\n",
" chain = {\"message\": RunnablePassthrough()} | prompt | llm | StrOutputParser()\n",
" return chain\n",
"\n",
"\n",
"# Researcherノードの定義\n",
"def research_node(state: TopState) -> dict:\n",
" last_message = state[\"messages\"][-1]\n",
" result = create_research_agent().invoke({\"messages\": [last_message]})\n",
" return {\n",
" \"output\": result[\"output\"],\n",
" \"messages\": [AIMessage(content=result[\"output\"])]\n",
" }\n",
"\n",
"\n",
"# Writerノードの定義\n",
"def writer_node(state: TopState) -> dict:\n",
" system_message = \"\"\"You are a writing specialist.\n",
"You can write sentences that even the world's best AI checkers would judge as written by a human.\"\"\"\n",
" latest_message = state[\"messages\"][-1].content\n",
" chain = create_llm_chain(llm, system_message)\n",
" result = chain.invoke(latest_message)\n",
" return {\n",
" \"output\": result,\n",
" \"messages\": [AIMessage(content=result)]\n",
" }\n",
"\n",
"\n",
"# 品質チェックノードの定義\n",
"def qa_node(state: TopState) -> dict:\n",
" system_message = \"\"\"You are a specialist in inspecting the quality of articles. \n",
"Inspect the article to see if it meets the following requirements:\n",
"\n",
"- The article is written in Japanese.\n",
"- The writing style MUST be such that the AI sentence checker determines that it was written by a HUMAN.\n",
"- The article MUST be written in a way that is easy to understand.\n",
"- The article MUST meet the user's requirements.\n",
"\n",
"If the evaluation criteria are met, write \"Acceptable\".\n",
"In addition, write the reason why you judged that the evaluation criteria are met.\n",
"\n",
"If the evaluation criteria are not met, write \"Not Acceptable\".\n",
"In addition, provide feedback on what needs to be done to meet the evaluation criteria.\n",
"\n",
"DO NOT make excuses such as \"I can't make a decision because I am an AI\".\n",
"\n",
"The quality of your articles is relevant to your career.\n",
"Please be as rigorous as possible in your inspections and make sure that your feedback is helpful in making corrections.\n",
"\"\"\"\n",
" latest_output = state[\"output\"]\n",
" chain = create_llm_chain(llm, system_message)\n",
" result = chain.invoke(latest_output)\n",
" result_with_original_article = f\"feedback: {result}\\n\\n-----\\n{latest_output}\"\n",
" return {\"messages\": [AIMessage(content=result_with_original_article)]}\n",
"\n",
"\n",
"# Supervisorノードの定義\n",
"def supervisor_node(state: TopState) -> Runnable:\n",
" prompt = \"\"\"You are a supervisor tasked with managing a conversation between the following teams:\n",
"{team_members}\n",
" \n",
"Given the following user request, respond with the worker to act next. \n",
"Each worker will perform a task and respond with their results and status.\n",
"When finished, respond with FINISH.\"\"\"\n",
" return create_team_supervisor(llm, prompt, team_members)"
],
"metadata": {
"collapsed": false,
"ExecuteTime": {
"end_time": "2024-01-30T01:05:39.358908Z",
"start_time": "2024-01-30T01:05:39.355368Z"
}
},
"id": "e5f301e58710e450",
"execution_count": 4
},
{
"cell_type": "markdown",
"source": [
"## LangGraphの定義\n",
"LangGraphのStateGraphにノードと、ノード間を繋ぐエッジを設定する。\n",
"SUPERVISOR_NODEは次にどのノードに遷移するかを決定するため、条件付きエッジを設定する。"
],
"metadata": {
"collapsed": false
},
"id": "8834812af32196bd"
},
{
"cell_type": "code",
"outputs": [],
"source": [
"from langchain_core.messages import HumanMessage\n",
"from langgraph.graph import StateGraph, END\n",
"\n",
"graph = StateGraph(TopState)\n",
"\n",
"graph.add_node(RESEARCH_NODE, research_node)\n",
"graph.add_node(QUALITY_ASSURANCE_NODE, qa_node)\n",
"graph.add_node(WRITER_NODE, writer_node)\n",
"graph.add_node(SUPERVISOR_NODE, supervisor_node)\n",
"\n",
"graph.add_edge(RESEARCH_NODE, SUPERVISOR_NODE)\n",
"graph.add_edge(QUALITY_ASSURANCE_NODE, SUPERVISOR_NODE)\n",
"graph.add_edge(WRITER_NODE, SUPERVISOR_NODE)\n",
"graph.add_conditional_edges(\n",
" SUPERVISOR_NODE,\n",
" lambda x: x[\"next\"],\n",
" {\n",
" RESEARCH_NODE: RESEARCH_NODE,\n",
" QUALITY_ASSURANCE_NODE: QUALITY_ASSURANCE_NODE,\n",
" WRITER_NODE: WRITER_NODE,\n",
" \"FINISH\": END,\n",
" }\n",
")\n",
"\n",
"graph.set_entry_point(SUPERVISOR_NODE)\n",
"blog_writer = {\"messages\": lambda x: [HumanMessage(content=x)]} | graph.compile()"
],
"metadata": {
"collapsed": false,
"ExecuteTime": {
"end_time": "2024-01-30T01:05:39.378957Z",
"start_time": "2024-01-30T01:05:39.357804Z"
}
},
"id": "819c54492e4f8688",
"execution_count": 5
},
{
"cell_type": "markdown",
"source": [
"## LangGraphの実行\n",
"処理の流れが分かりやすいようにstream関数で実行している。\n",
"LangSmithが利用できる場合はLangSmith上で確認すると、より分かりやすい。"
],
"metadata": {
"collapsed": false
},
"id": "ef24ba39e4a501e8"
},
{
"cell_type": "code",
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'supervisor': {'next': 'research'}}\n",
"---\n",
"{'research': {'output': 'デジタルメディアの最新の研究によると、昨年、子どもたちはYouTubeよりもTikTokでの時間を60%多く過ごしたことが明らかになりました。これは、ビジネス界にとって無視できない変化です。YouTubeが依然としてこの年齢層で最も大きなストリーミングアプリであるにもかかわらず、子どもたちは平均して毎日112分をTikTokで過ごしており、その人気は急速に高まっています。\\n\\nさらに、20%の子どもたちがOpenAIのChatGPTを試したことも報告されています。これは、AI技術への関心が若年層の間で高まっていることを示しており、今後のビジネス戦略においてAIの活用を考慮する必要があることを示唆しています。\\n\\nこのデータは、ビジネスパーソンにとって何を意味するのでしょうか?\\n\\nまず、マーケティング戦略において、TikTokのようなプラットフォームへの注力が今後さらに重要になることが予想されます。特に若年層をターゲットとするビジネスにとって、彼らがどのように時間を過ごしているかを理解し、その傾向に合わせたコンテンツを提供することが成功の鍵となります。\\n\\nまた、AI技術への関心の高まりは、製品やサービスの開発、顧客サービス、マーケティング戦略において、AIをどのように活用していくかを考える良い機会を提供します。特にChatGPTのような技術が若年層に受け入れられていることから、教育やエンターテインメント、カスタマーサポートなど、さまざまな分野での活用が考えられます。\\n\\nこのような変化はビジネス環境において大きな影響を及ぼす可能性があり、ビジネスパーソンはこれらの傾向を注視し、迅速に適応する必要があります。デジタルメディアとAI技術の進化は、新しいビジネスチャンスを生み出すと同時に、既存のビジネスモデルに挑戦することもあります。このような環境下では、革新的で柔軟なビジネス戦略が成功への鍵となるでしょう。', 'messages': [AIMessage(content='デジタルメディアの最新の研究によると、昨年、子どもたちはYouTubeよりもTikTokでの時間を60%多く過ごしたことが明らかになりました。これは、ビジネス界にとって無視できない変化です。YouTubeが依然としてこの年齢層で最も大きなストリーミングアプリであるにもかかわらず、子どもたちは平均して毎日112分をTikTokで過ごしており、その人気は急速に高まっています。\\n\\nさらに、20%の子どもたちがOpenAIのChatGPTを試したことも報告されています。これは、AI技術への関心が若年層の間で高まっていることを示しており、今後のビジネス戦略においてAIの活用を考慮する必要があることを示唆しています。\\n\\nこのデータは、ビジネスパーソンにとって何を意味するのでしょうか?\\n\\nまず、マーケティング戦略において、TikTokのようなプラットフォームへの注力が今後さらに重要になることが予想されます。特に若年層をターゲットとするビジネスにとって、彼らがどのように時間を過ごしているかを理解し、その傾向に合わせたコンテンツを提供することが成功の鍵となります。\\n\\nまた、AI技術への関心の高まりは、製品やサービスの開発、顧客サービス、マーケティング戦略において、AIをどのように活用していくかを考える良い機会を提供します。特にChatGPTのような技術が若年層に受け入れられていることから、教育やエンターテインメント、カスタマーサポートなど、さまざまな分野での活用が考えられます。\\n\\nこのような変化はビジネス環境において大きな影響を及ぼす可能性があり、ビジネスパーソンはこれらの傾向を注視し、迅速に適応する必要があります。デジタルメディアとAI技術の進化は、新しいビジネスチャンスを生み出すと同時に、既存のビジネスモデルに挑戦することもあります。このような環境下では、革新的で柔軟なビジネス戦略が成功への鍵となるでしょう。')]}}\n",
"---\n",
"{'supervisor': {'next': 'quality_assurance'}}\n",
"---\n",
"{'quality_assurance': {'messages': [AIMessage(content='feedback: Acceptable\\n\\n判断理由:\\n- 記事は日本語で書かれています。\\n- 文章は人間によって書かれたとAI文書チェッカーが判断するような自然で流暢な書き方であることが読み取れます。\\n- 内容は簡潔で分かりやすく、デジタルメディアとAI技術の最新の研究結果やそのビジネスへの影響について明確に説明しています。\\n- 記事はユーザーの要件に沿っており、デジタルメディアとAI技術の影響に関する情報を提供しています。\\n\\n-----\\nデジタルメディアの最新の研究によると、昨年、子どもたちはYouTubeよりもTikTokでの時間を60%多く過ごしたことが明らかになりました。これは、ビジネス界にとって無視できない変化です。YouTubeが依然としてこの年齢層で最も大きなストリーミングアプリであるにもかかわらず、子どもたちは平均して毎日112分をTikTokで過ごしており、その人気は急速に高まっています。\\n\\nさらに、20%の子どもたちがOpenAIのChatGPTを試したことも報告されています。これは、AI技術への関心が若年層の間で高まっていることを示しており、今後のビジネス戦略においてAIの活用を考慮する必要があることを示唆しています。\\n\\nこのデータは、ビジネスパーソンにとって何を意味するのでしょうか?\\n\\nまず、マーケティング戦略において、TikTokのようなプラットフォームへの注力が今後さらに重要になることが予想されます。特に若年層をターゲットとするビジネスにとって、彼らがどのように時間を過ごしているかを理解し、その傾向に合わせたコンテンツを提供することが成功の鍵となります。\\n\\nまた、AI技術への関心の高まりは、製品やサービスの開発、顧客サービス、マーケティング戦略において、AIをどのように活用していくかを考える良い機会を提供します。特にChatGPTのような技術が若年層に受け入れられていることから、教育やエンターテインメント、カスタマーサポートなど、さまざまな分野での活用が考えられます。\\n\\nこのような変化はビジネス環境において大きな影響を及ぼす可能性があり、ビジネスパーソンはこれらの傾向を注視し、迅速に適応する必要があります。デジタルメディアとAI技術の進化は、新しいビジネスチャンスを生み出すと同時に、既存のビジネスモデルに挑戦することもあります。このような環境下では、革新的で柔軟なビジネス戦略が成功への鍵となるでしょう。')]}}\n",
"---\n",
"{'supervisor': {'next': 'FINISH'}}\n",
"---\n"
]
}
],
"source": [
"query = \"\"\"以下のニュースについてビジネスパーソン向けの解説記事を日本語で書きなさい。AI文章チェッカーによる判定で、AIが書いたと判定されないように書くこと。\n",
"https://techcrunch.com/2024/01/25/kids-spent-60-more-time-on-tiktok-than-youtube-last-year-20-tried-openais-chatgpt/\n",
"\"\"\"\n",
"\n",
"latest_output = \"\"\n",
"for s in blog_writer.stream(query, {\"recursion_limit\": 100}):\n",
" if \"__end__\" not in s:\n",
" print(s)\n",
" print(\"---\")\n",
" writing_output = (\n",
" s.get(RESEARCH_NODE, {}).get(\"output\") or\n",
" s.get(WRITER_NODE, {}).get(\"output\")\n",
" )\n",
" if writing_output:\n",
" latest_output = writing_output"
],
"metadata": {
"collapsed": false,
"ExecuteTime": {
"end_time": "2024-01-30T01:06:43.414791Z",
"start_time": "2024-01-30T01:05:39.377534Z"
}
},
"id": "d6189a9477f08bd1",
"execution_count": 6
},
{
"cell_type": "code",
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"デジタルメディアの最新の研究によると、昨年、子どもたちはYouTubeよりもTikTokでの時間を60%多く過ごしたことが明らかになりました。これは、ビジネス界にとって無視できない変化です。YouTubeが依然としてこの年齢層で最も大きなストリーミングアプリであるにもかかわらず、子どもたちは平均して毎日112分をTikTokで過ごしており、その人気は急速に高まっています。\n",
"\n",
"さらに、20%の子どもたちがOpenAIのChatGPTを試したことも報告されています。これは、AI技術への関心が若年層の間で高まっていることを示しており、今後のビジネス戦略においてAIの活用を考慮する必要があることを示唆しています。\n",
"\n",
"このデータは、ビジネスパーソンにとって何を意味するのでしょうか?\n",
"\n",
"まず、マーケティング戦略において、TikTokのようなプラットフォームへの注力が今後さらに重要になることが予想されます。特に若年層をターゲットとするビジネスにとって、彼らがどのように時間を過ごしているかを理解し、その傾向に合わせたコンテンツを提供することが成功の鍵となります。\n",
"\n",
"また、AI技術への関心の高まりは、製品やサービスの開発、顧客サービス、マーケティング戦略において、AIをどのように活用していくかを考える良い機会を提供します。特にChatGPTのような技術が若年層に受け入れられていることから、教育やエンターテインメント、カスタマーサポートなど、さまざまな分野での活用が考えられます。\n",
"\n",
"このような変化はビジネス環境において大きな影響を及ぼす可能性があり、ビジネスパーソンはこれらの傾向を注視し、迅速に適応する必要があります。デジタルメディアとAI技術の進化は、新しいビジネスチャンスを生み出すと同時に、既存のビジネスモデルに挑戦することもあります。このような環境下では、革新的で柔軟なビジネス戦略が成功への鍵となるでしょう。\n"
]
}
],
"source": [
"print(latest_output)"
],
"metadata": {
"collapsed": false,
"ExecuteTime": {
"end_time": "2024-01-30T01:06:43.419012Z",
"start_time": "2024-01-30T01:06:43.412909Z"
}
},
"id": "5909ffdd06ec8f66",
"execution_count": 7
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 2
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython2",
"version": "2.7.6"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment