Model

from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4-1106-preview")

Tools

from typing import Annotated, List, Tuple, Union
from langchain.tools import BaseTool, StructuredTool, Tool
from langchain_experimental.tools import PythonREPLTool
from langchain_core.tools import tool
import random

python_repl_tool = PythonREPLTool()

@tool("lower_case", return_direct=False)
def to_lower_case(input:str) -> str:
  """Returns the input as all lower case."""
  return input.lower()

@tool("random_number", return_direct=False)
def random_number_maker(input:str) -> str:
    """Returns a random number between 0-100. input the word 'random'"""
    return random.randint(0, 100)

tools = [to_lower_case,random_number_maker,python_repl_tool]

Helper Utils

LangChain 라이브러리를 사용하여, 사용자 지정 도구(agent, executor)를 통해 자연어 이해 및 생성 작업에 활용할 수 있는 대화형 에이전트를 생성

  1. ChatOpenAI에서 가져온 LLM(Language Model)을 생성
    1. 입력 메시지와 시스템 프롬프트에 기반한 ChatPromptTemplate 객체와 에이전트의 행동과 스크래치패드를 포함
    2. create_openai_tools_agent 함수를 사용하여 지정된 LLM, 도구 리스트 그리고 시스템 프롬프트를 바탕으로 에이전트 생성
    3. AgentExecutor 에이전트와 함께 제공된 도구로 객체를 초기화
  2. agent_node() 주어진 상태를 기반으로 에이전트의 동작을 실행하고 결과 메시지를 반환
from langchain.agents import AgentExecutor, create_openai_tools_agent
from langchain_core.messages import BaseMessage, HumanMessage
from langchain_openai import ChatOpenAI

# 대화형 에이전트 생성
def create_agent(llm: ChatOpenAI, tools: list, system_prompt: str):
    prompt = ChatPromptTemplate.from_messages(
        [
            (
                "system",
                system_prompt,
            ),
            MessagesPlaceholder(variable_name="messages"), 
            MessagesPlaceholder(variable_name="agent_scratchpad"),
        ]
    )
    agent = create_openai_tools_agent(llm, tools, prompt)
    executor = AgentExecutor(agent=agent, tools=tools) 
    
    return executor

# 에이전트 실행 및 결과 메시지 반환
def agent_node(state, agent, name):
    result = agent.invoke(state)

    return {"messages": [HumanMessage(content=result["output"], name=name)]}

Create Agent Supervisor

members

시스템(슈퍼바이저)의 프롬프트 작성

프롬프트 템플릿 구성

작업 순서를 결정하는 체인을 생성

from langchain.output_parsers.openai_functions import JsonOutputFunctionsParser
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

members = ["Lotto_Manager", "Coder"]

system_prompt = (
    "You are a supervisor tasked with managing a conversation between the"
    " following workers:  {members}. Given the following user request,"
    " respond with the worker to act next. Each worker will perform a"
    " task and respond with their results and status. When finished,"
    " respond with FINISH."
)

options = ["FINISH"] + members

function_def = {
    "name": "route",
    "description": "Select the next role.",
    "parameters": {
        "title": "routeSchema",
        "type": "object",
        "properties": {
            "next": {
                "title": "Next",
                "anyOf": [
                    {"enum": options},
                ],
            }
        },
        "required": ["next"],
    },
}

prompt = ChatPromptTemplate.from_messages(
    [
        ("system", system_prompt),
        MessagesPlaceholder(variable_name="messages"),
        (
            "system",
            "Given the conversation above, who should act next?"
            " Or should we FINISH? Select one of: {options}",
        ),
    ]
).partial(options=str(options), members=", ".join(members))

supervisor_chain = (
    prompt
    | llm.bind_functions(functions=[function_def], function_call="route")
    | JsonOutputFunctionsParser()
)

Create the AgnetState and Graph

AgentState는 에이전트의 상태 구조를 정의하는 딕셔너리 정의

앞에 선언한 Helper Utils 를 사용하여 ‘Lotto_Manager’, ‘Coder’ 멤버들에 대한 에이전트와 노드를 생성한다.

lotto

agent - 언어 모델(llm), 도구, 역할 설명을 매개변수 lotto_agent 객체를 생성

node - 에이전트 노드의 자세한 사항을 저장하는 lotto_node라는 부분 함수를 생성

code

lotto와 동일하게 agent, node 함수를 생성하는데 단지 다른 점은, 로컬에서 코드를 작성하기 위해 tools가 아닌 python_repl_tool를 사용하고 있다는 점이다.

AgentState 타입의 상태 그래프로 workflow를 생성

workflow에 앞에 선언했던 ‘Lotto_Manager’, ‘Coder’ 와 supervisor_chain을 추가

import operator
from typing import Annotated, Any, Dict, List, Optional, Sequence, TypedDict
import functools
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langgraph.graph import StateGraph, END

class AgentState(TypedDict):
    messages: Annotated[Sequence[BaseMessage], operator.add]
    next: str

lotto_agent = create_agent(llm, tools, "You are a senior lotto manager. you run the lotto and get random numbers")
lotto_node = functools.partial(agent_node, agent=lotto_agent, name="Lotto_Manager")

code_agent = create_agent(llm, [python_repl_tool], "You may generate safe python code to analyze data and generate charts using matplotlib.")
code_node = functools.partial(agent_node, agent=code_agent, name="Coder")

workflow = StateGraph(AgentState)
workflow.add_node("Lotto_Manager", lotto_node)
workflow.add_node("Coder", code_node)
workflow.add_node("supervisor", supervisor_chain)

Edges

반복문을 통해 각 멤버를 순회?하면서, 멤버와 'supervisor' 노드 사이에 경로를 추가

대화 중 다음 단계로 넘어갈 수 있는 선택지를 결정하는 규칙을 담은 conditional_map 생성

conditional_map에 조건을 포함하는 supervisor를 추가하고 시작점으로 세팅한다

그동안 생성했던 워크플로우를 컴파일한 최종 그래프를 생성한다

for member in members:
    workflow.add_edge(member, "supervisor") 

conditional_map = {k: k for k in members}
conditional_map["FINISH"] = END 

workflow.add_conditional_edges("supervisor", lambda x: x["next"], conditional_map)
workflow.set_entry_point("supervisor")

graph = workflow.compile()

Run it

재귀 호출의 최대 제한을 20으로 설정한 후

데모 용 메시지()를 입력하고 출력을 기다린다.

그래프를 스트리밍하고 결과를 출력합니다.

graph.stream - 출력 결과는 "end" 키가 없는 상태만 출력됩니다.

final_response - 같은 메시지를 사용하여 그래프의 최종응답을 취득합니다.

최종 메시지는 출력 내용 중 두번째 메시지에 담겨있다.['messages'][1]

# 'stream'을 사용하여 단계별로 결과를 확인
config = {"recursion_limit": 20}

for s in graph.stream(
    {
        "messages": [
            HumanMessage(content="무작위 로또 번호 10개를 가져와서 히스토그램에 10개의 빈으로 표시하고 마지막에 10개의 숫자가 무엇인지 알려주세요.")
        ]
    }, config=config
):
    if "__end__" not in s:
        print(s)
        print("----")

# 'invoke'를 사용하여 최종 결과를 얻음
final_response = graph.invoke(
    {
        "messages": [
            HumanMessage(content="무작위 로또 번호 10개를 가져와서 히스토그램에 10개의 빈으로 표시하고 마지막에 10개의 숫자가 무엇인지 알려주세요.")
        ]
    }, config=config
)

final_response['messages'][1].content

Colab 03.