[PYTHON/LANGCHAIN] StructuredTool 클래스 : invoke 메소드에서 AIMessage 객체의 tool_calls 속성 값을 사용해 컨텐트 구하기
■ StructuredTool 클래스의 invoke 메소드에서 AIMessage 객체의 tool_calls 속성 값을 사용해 컨텐트를 구하는 방법을 보여준다. ※ OPENAI_API_KEY 환경 변수 값은 .env 파일에
■ StructuredTool 클래스의 invoke 메소드에서 AIMessage 객체의 tool_calls 속성 값을 사용해 컨텐트를 구하는 방법을 보여준다. ※ OPENAI_API_KEY 환경 변수 값은 .env 파일에
■ StructuredTool 클래스의 invoke 메소드에서 AIMessage 객체의 tool_calls 속성 값을 사용해 ToolMessage 객체를 구하는 방법을 보여준다. ※ OPENAI_API_KEY 환경 변수 값은 .env
■ RunnableBindable 클래스의 invoke 메소드를 사용해 모델에서 도구를 호출하는 방법을 보여준다. ※ OPENAI_API_KEY 환경 변수 값은 .env 파일에 정의한다. ▶ main.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
import random from dotenv import load_dotenv from langchain_core.tools import tool from typing import Tuple from typing import List from langchain_openai import ChatOpenAI load_dotenv() @tool(response_format = "content_and_artifact") def generateRandomIntegerValues(minimumValue : int, maximumValue : int, count : int) -> Tuple[str, List[int]]: """Generate size random ints in the range [minimumValue, maximumValue].""" array = [random.randint(minimumValue, maximumValue) for _ in range(count)] content = f"Successfully generated array of {count} random ints in [{minimumValue}, {maximumValue}]." return content, array chatOpenAI = ChatOpenAI(model = "gpt-4o-mini") runnableBindable = chatOpenAI.bind_tools([generateRandomIntegerValues]) responseAIMessage = runnableBindable.invoke("generate 6 positive ints less than 25") print(responseAIMessage.tool_calls) """ [{'name': 'generateRandomIntegerValues', 'args': {'minimumValue': 1, 'maximumValue': 24, 'count': 6}, 'id': 'call_F9ZZFBIsd6HXz30bb0yZTWgl', 'type': 'tool_call'}] """ |
■ @tool 데코레이터에서 response_format 인자를 사용해 컨텐트/아티팩트를 반환하는 방법을 보여준다. ▶ main.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
import random from langchain_core.tools import tool from typing import Tuple from typing import List @tool(response_format = "content_and_artifact") def generateRandomIntegerValues(minimumValue : int, maximumValue : int, count : int) -> Tuple[str, List[int]]: """Generate size random ints in the range [minimumValue, maximumValue].""" array = [random.randint(minimumValue, maximumValue) for _ in range(count)] content = f"Successfully generated array of {count} random ints in [{minimumValue}, {maximumValue}]." return content, array responseToolMessage = generateRandomIntegerValues.invoke( { "name" : "generateRandomIntegerValues", "args" : {"minimumValue" : 0, "maximumValue" : 9, "count" : 10}, "id" : "123", # required "type" : "tool_call" # required } ) print(responseToolMessage) """ content='Successfully generated array of 10 random ints in [0, 9].' name='generateRandomIntegerValues' tool_call_id='123' artifact=[0, 8, 3, 5, 7, 1, 4, 4, 7, 3] """ |
▶ requirements.txt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
aiohappyeyeballs==2.4.0 aiohttp==3.10.5 aiosignal==1.3.1 annotated-types==0.7.0 anyio==4.4.0 attrs==24.2.0 certifi==2024.8.30 charset-normalizer==3.3.2 frozenlist==1.4.1 greenlet==3.1.0 h11==0.14.0 httpcore==1.0.5 httpx==0.27.2 idna==3.10 jsonpatch==1.33 jsonpointer==3.0.0 langchain==0.3.0 langchain-core==0.3.1 langchain-text-splitters==0.3.0 langsmith==0.1.122 multidict==6.1.0 numpy==1.26.4 orjson==3.10.7 packaging==24.1 pydantic==2.9.2 pydantic_core==2.23.4 PyYAML==6.0.2 requests==2.32.3 sniffio==1.3.1 SQLAlchemy==2.0.35 tenacity==8.5.0 typing_extensions==4.12.2 urllib3==2.2.3 yarl==1.11.1 |
※ pip install langchain 명령을 실행했다.
■ StructuredTool 클래스의 astream_events 메소드를 사용해 "on_chat_model_end" 이벤트를 수신하는 방법을 보여준다. ※ OPENAI_API_KEY 환경 변수 값은 .env 파일에 정의한다. ▶ main.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
import asyncio from dotenv import load_dotenv from langchain_core.prompts import ChatPromptTemplate from langchain_openai import ChatOpenAI from langchain_core.output_parsers import StrOutputParser from langchain_core.tools import tool load_dotenv() def reverse(text : str): return text[::-1] chatPromptTemplate = ChatPromptTemplate.from_template( "You are an expert writer. Summarize the following text in 10 words or less:\n\n{long_text}" ) chatOpenAI = ChatOpenAI(model = "gpt-4o-mini") strOutputParser = StrOutputParser() runnableSequence = chatPromptTemplate | chatOpenAI | strOutputParser | reverse @tool async def specialSummarizationTool(longText : str) -> str: """A tool that summarizes input text using advanced techniques.""" summary = await runnableSequence.ainvoke({"long_text" : longText}) return summary longText = """ NARRATOR: (Black screen with text; The sound of buzzing bees can be heard) According to all known laws of aviation, there is no way a bee should be able to fly. Its wings are too small to get its fat little body off the ground. The bee, of course, flies anyway because bees don't care what humans think is impossible. BARRY BENSON: (Barry is picking out a shirt) Yellow, black. Yellow, black. Yellow, black. Yellow, black. Ooh, black and yellow! Let's shake it up a little. JANET BENSON: Barry! Breakfast is ready! BARRY: Coming! Hang on a second. """ async def main(): asyncGenerator = specialSummarizationTool.astream_events({"longText" : longText}, version = "v2") async for eventDictionary in asyncGenerator: if eventDictionary["event"] == "on_chat_model_end": print(eventDictionary) asyncio.run(main()) """ { 'event' : 'on_chat_model_end', 'data' : { 'output' : AIMessage( content = 'Bee defies aviation laws; Barry prepares for breakfast.', additional_kwargs = {}, response_metadata = { 'finish_reason' : 'stop', 'model_name' : 'gpt-4o-mini-2024-07-18', 'system_fingerprint' : 'fp_f85bea6784' }, id = 'run-1b4d203f-eceb-4507-a321-12189a0b52da' ), 'input' : { 'messages' : [ [ HumanMessage( content = "You are an expert writer. Summarize the following text in 10 words or less:\n\n\nNARRATOR:\n(Black screen with text; The sound of buzzing bees can be heard)\nAccording to all known laws of aviation, there is no way a bee should be able to fly. Its wings are too small to get its fat little body off the ground. The bee, of course, flies anyway because bees don't care what humans think is impossible.\nBARRY BENSON:\n(Barry is picking out a shirt)\nYellow, black. Yellow, black. Yellow, black. Yellow, black. Ooh, black and yellow! Let's shake it up a little.\nJANET BENSON:\nBarry! Breakfast is ready!\nBARRY:\nComing! Hang on a second.\n", additional_kwargs = {}, response_metadata={} ) ] ] } }, 'run_id' : '1b4d203f-eceb-4507-a321-12189a0b52da', 'name' : 'ChatOpenAI', 'tags' : ['seq:step:2'], 'metadata' : { 'ls_provider' : 'openai', 'ls_model_name' : 'gpt-4o-mini', 'ls_model_type' : 'chat', 'ls_temperature' : 0.7 }, 'parent_ids' : [ '243969e3-7dee-42a0-9b62-278a42759950', '1d8806b0-e9b1-46dc-9a9f-d5d84791b935' ] } """ |
■ StructuredTool 클래스의 ainvoke 메소드를 사용해 도구를 호출하는 방법을 보여준다. ※ OPENAI_API_KEY 환경 변수 값은 .env 파일에 정의한다. ▶ main.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
import asyncio from dotenv import load_dotenv from langchain_core.prompts import ChatPromptTemplate from langchain_openai import ChatOpenAI from langchain_core.output_parsers import StrOutputParser from langchain_core.tools import tool load_dotenv() def reverse(text : str): return text[::-1] chatPromptTemplate = ChatPromptTemplate.from_template( "You are an expert writer. Summarize the following text in 10 words or less:\n\n{long_text}" ) chatOpenAI = ChatOpenAI(model = "gpt-4o-mini") strOutputParser = StrOutputParser() runnableSequence = chatPromptTemplate | chatOpenAI | strOutputParser | reverse @tool async def specialSummarizationTool(longText : str) -> str: """A tool that summarizes input text using advanced techniques.""" summary = await runnableSequence.ainvoke({"long_text" : longText}) return summary longText = """ NARRATOR: (Black screen with text; The sound of buzzing bees can be heard) According to all known laws of aviation, there is no way a bee should be able to fly. Its wings are too small to get its fat little body off the ground. The bee, of course, flies anyway because bees don't care what humans think is impossible. BARRY BENSON: (Barry is picking out a shirt) Yellow, black. Yellow, black. Yellow, black. Yellow, black. Ooh, black and yellow! Let's shake it up a little. JANET BENSON: Barry! Breakfast is ready! BARRY: Coming! Hang on a second. """ async def main(): responseString = await specialSummarizationTool.ainvoke({"longText" : longText}) print(responseString) asyncio.run(main()) |
▶
■ StructuredTool 클래스의 ainvoke 메소드에서 config 인자를 사용해 도구에서 RunnableConfig 객체를 참조하는 방법을 보여준다. ▶ main.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
import asyncio from langchain_core.runnables import RunnableConfig from langchain_core.tools import tool @tool async def reverseTool(text : str, runnableConfig : RunnableConfig) -> str: """A test tool that combines input text with a configurable parameter.""" return (text + runnableConfig["configurable"]["additional_field"])[::-1] print(type(reverseTool)) async def main(): responseString = await reverseTool.ainvoke( {"text" : "abc"}, config = {"configurable" : {"additional_field" : "123"}} ) print(responseString) asyncio.run(main()) |
▶ requirements.txt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
aiohappyeyeballs==2.4.2 aiohttp==3.10.7 aiosignal==1.3.1 annotated-types==0.7.0 anyio==4.6.0 attrs==24.2.0 certifi==2024.8.30 charset-normalizer==3.3.2 frozenlist==1.4.1 greenlet==3.1.1 h11==0.14.0 httpcore==1.0.5 httpx==0.27.2 idna==3.10 jsonpatch==1.33 jsonpointer==3.0.0 langchain==0.3.1 langchain-core==0.3.6 langchain-text-splitters==0.3.0 langsmith==0.1.129 multidict==6.1.0 numpy==1.26.4 orjson==3.10.7 packaging==24.1 pydantic==2.9.2 pydantic_core==2.23.4 PyYAML==6.0.2 requests==2.32.3 sniffio==1.3.1 SQLAlchemy==2.0.35 tenacity==8.5.0 typing_extensions==4.12.2 urllib3==2.2.3 yarl==1.13.1 |
※ pip
■ ChatOpenAI 클래스의 bind_tools 메소드에서 parallel_tool_calls 인자를 사용해 병렬 도구 호출을 비활성화하는 방법을 보여준다. ※ OPENAI_API_KEY 환경 변수 값은 .env 파일에 정의한다.
■ chatOpenAI 클래스의 bind_tools 메소드에서 tool_choice 인자를 사용해 도구 호출을 강제하는 방법을 보여준다. ※ OPENAI_API_KEY 환경 변수 값은 .env 파일에 정의한다. ▶
■ 도구 실행 오류시 재실행하는 방법을 보여준다. ※ OPENAI_API_KEY 환경 변수 값은 .env 파일에 정의한다. ▶ main.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
from dotenv import load_dotenv from langchain_core.tools import tool from langchain_core.messages import ToolCall from langchain_core.messages import AIMessage from langchain_core.runnables import RunnableConfig from langchain_core.runnables import Runnable from langchain_core.messages import ToolMessage from langchain_core.messages import HumanMessage from langchain_core.prompts import ChatPromptTemplate from langchain_openai import ChatOpenAI load_dotenv() @tool def complexTool(integerValue : int, floatValue : float, dictionaryValue : dict) -> int: """Do something complex with a complex tool.""" return integerValue * floatValue class CustomToolException(Exception): """Custom LangChain tool exception.""" def __init__(self, toolCall : ToolCall, exception : Exception) -> None: super().__init__() self.tool_call = toolCall self.exception = exception def complexToolWithCustomException(aiMessage : AIMessage, config : RunnableConfig) -> Runnable: try: return complexTool.invoke(aiMessage.tool_calls[0]["args"], config = config) except Exception as exception: raise CustomToolException(aiMessage.tool_calls[0], exception) def getInputDictionary(inputDictionary : dict) -> dict: exception = inputDictionary.pop("exception") messageList = [ AIMessage(content = "", tool_calls = [exception.tool_call]), ToolMessage(tool_call_id = exception.tool_call["id"], content = str(exception.exception)), HumanMessage(content = "The last tool call raised an exception. Try calling the tool again with corrected arguments. Do not repeat mistakes.") ] inputDictionary["last_output"] = messageList return inputDictionary # 우리는 프롬프트에 last_output MessagesPlaceholder를 추가한다. # 이 메시지는 전달되지 않으면 프롬프트에 전혀 영향을 미치지 않지만 필요한 경우 임의의 메시지 목록을 프롬프트에 삽입할 수 있는 옵션을 제공한다. # 이를 재시도 시 사용하여 오류 메시지를 삽입한다. chatPromptTemplate = ChatPromptTemplate.from_messages( [ ("human" , "{input}" ), ("placeholder", "{last_output}") ] ) chatOpenAI = ChatOpenAI(model="gpt-4o-mini") runnableBindable = chatOpenAI.bind_tools([complexTool]) runnableSequence = chatPromptTemplate | runnableBindable | complexToolWithCustomException selfCorrectingRunnableSequence = runnableSequence.with_fallbacks([getInputDictionary | runnableSequence], exception_key = "exception") responseValue = selfCorrectingRunnableSequence.invoke({"input" : "use complex tool. the args are 5, 2.1, empty dictionary. don't forget dictionaryValue"}) print(responseValue) |
▶ requirements.txt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
annotated-types==0.7.0 anyio==4.6.0 certifi==2024.8.30 charset-normalizer==3.3.2 colorama==0.4.6 distro==1.9.0 h11==0.14.0 httpcore==1.0.5 httpx==0.27.2 idna==3.10 jiter==0.5.0 jsonpatch==1.33 jsonpointer==3.0.0 langchain-core==0.3.5 langchain-openai==0.2.0 langsmith==0.1.128 openai==1.47.1 orjson==3.10.7 packaging==24.1 pydantic==2.9.2 pydantic_core==2.23.4 python-dotenv==1.0.1 PyYAML==6.0.2 regex==2024.9.11 requests==2.32.3 sniffio==1.3.1 tenacity==8.5.0 tiktoken==0.7.0 tqdm==4.66.5 typing_extensions==4.12.2 urllib3==2.2.3 |
※
■ RunnableSequence 클래스의 with_fallbacks 메소드를 사용해 도구 오류시 대체 모델을 사용하는 방법을 보여준다. ※ OPENAI_API_KEY 환경 변수 값은 .env 파일에 정의한다. ▶
■ try … except 구문을 사용해 도구 사용시 오류를 처리하는 방법을 보여준다. ※ OPENAI_API_KEY 환경 변수 값은 .env 파일에 정의한다. ▶ main.py
■ RunnableSequence 클래스의 batch 메소드에서 config 인자를 사용해 동시 작업 수를 설정하는 방법을 보여준다. ※ OPENAI_API_KEY 환경 변수 값은 .env 파일에 정의한다.
■ PromptTemplate 클래스의 from_template 정적 메소드를 사용해 PromptTemplate 객체를 만드는 방법을 보여준다. ※ OPENAI_API_KEY 환경 변수 값은 .env 파일에 정의한다. ▶ main.py
■ RunnableSequence 클래스의 invoke 메소드를 사용해 LCEL 체인을 실행하는 방법을 보여준다. ※ OPENAI_API_KEY 환경 변수 값은 .env 파일에 정의한다. ▶ main.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
from dotenv import load_dotenv from langchain_core.prompts import PromptTemplate from langchain_openai import ChatOpenAI load_dotenv() templateString = "{country}의 수도는 어디입니까?" promptTemplate = PromptTemplate.from_template(templateString) chatOpenAI = ChatOpenAI( model_name = "gpt-4o-mini", # 모델명 max_tokens = 2048, # 최대 토큰 수 temperature = 0.1, # 창의성 (0.0 ~ 2.0) ) runnableSequence = promptTemplate | chatOpenAI responseAIMessage = runnableSequence.invoke({"country" : "미국"}) print(responseAIMessage.content) """ 미국의 수도는 워싱턴 D.C.입니다. """ |
■ ChatOpenAI 클래스의 bind 메소드에서 logprobs 인자를 사용해 주어진 토큰 확률 로그 값을 받는 방법을 보여준다. ※ OPENAI_API_KEY 환경 변수 값은 .env
■ ChatOpenAI 클래스의 invoke 메소드를 사용해 질문하는 방법을 보여준다. ※ OPENAI_API_KEY 환경 변수 값은 .env 파일에 정의한다. ▶ main.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
from dotenv import load_dotenv from langchain_openai import ChatOpenAI load_dotenv() chatOpenAI = ChatOpenAI( model_name = "gpt-4o-mini", # 모델명 temperature = 0.1 # 창의성 (0.0 ~ 2.0) ) requestString = "대한민국의 수도는 어디입니까?" responseAIMessage = chatOpenAI.invoke(requestString) print(responseAIMessage.content) |
▶ requirements.txt
■ Ollama 클래스를 사용해 텍스트를 생성하는 방법을 보여준다. ▶ main.py
1 2 3 4 5 6 7 8 9 10 11 12 13 |
from langchain_community.llms import Ollama ollama = Ollama( model = "llama3.1:8b-instruct-q2_K", temperature = 0.7, repeat_penalty = 1.3 ) responseString = ollama.invoke("python으로 시간을 출력하는 코드를 만들어주세요.") print(responseString) |
▶ requirements.txt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
aiohappyeyeballs==2.4.0 aiohttp==3.10.5 aiosignal==1.3.1 annotated-types==0.7.0 anyio==4.6.0 attrs==24.2.0 certifi==2024.8.30 charset-normalizer==3.3.2 dataclasses-json==0.6.7 frozenlist==1.4.1 greenlet==3.1.1 h11==0.14.0 httpcore==1.0.5 httpx==0.27.2 idna==3.10 jsonpatch==1.33 jsonpointer==3.0.0 langchain==0.3.0 langchain-community==0.3.0 langchain-core==0.3.5 langchain-text-splitters==0.3.0 langsmith==0.1.125 marshmallow==3.22.0 multidict==6.1.0 mypy-extensions==1.0.0 numpy==1.26.4 orjson==3.10.7 packaging==24.1 pydantic==2.9.2 pydantic-settings==2.5.2 pydantic_core==2.23.4 python-dotenv==1.0.1 PyYAML==6.0.2 requests==2.32.3 sniffio==1.3.1 SQLAlchemy==2.0.35 tenacity==8.5.0 typing-inspect==0.9.0 typing_extensions==4.12.2 urllib3==2.2.3 yarl==1.11.1 |
※ pip install langchain-community 명령을 실행했다.
■ 도구 호출시 오류를 처리하는 방법을 보여준다. ※ OPENAI_API_KEY 환경 변수 값은 .env 파일에 정의한다. ▶ main.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
from dotenv import load_dotenv from langchain_core.tools import tool from langchain_core.runnables import RunnableConfig from langchain_core.runnables import Runnable from langchain_openai import ChatOpenAI load_dotenv() @tool def _execute(integerValue : int, floatValue : float, dictionary1 : dict) -> int: """Do something complex with a complex tool.""" return integerValue * floatValue def execute(tool_args : dict, config : RunnableConfig) -> Runnable: try: _execute.invoke(tool_args, config = config) except Exception as e: return f"Calling tool with arguments:\n\n{tool_args}\n\nraised the following error:\n\n{type(e)}: {e}" chatOpenAI = ChatOpenAI(model = "gpt-4o-mini") runnableBindable = chatOpenAI.bind_tools([_execute]) runnableSequence = runnableBindable | (lambda aiMessage : aiMessage.tool_calls[0]["args"]) | execute resultValue = runnableSequence.invoke("use complex tool. the args are 5, 2.1, empty dictionary. don't forget dictionary1") print(resultValue) """ Calling tool with arguments: {'integerValue': 5, 'floatValue': 2.1} raised the following error: <class 'pydantic_core._pydantic_core.ValidationError'>: 1 validation error for _execute dictionary1 Field required [type=missing, input_value={'integerValue': 5, 'floatValue': 2.1}, input_type=dict] For further information visit https://errors.pydantic.dev/2.9/v/missing """ |
▶ requirements.txt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
annotated-types==0.7.0 anyio==4.6.0 certifi==2024.8.30 charset-normalizer==3.3.2 colorama==0.4.6 distro==1.9.0 h11==0.14.0 httpcore==1.0.5 httpx==0.27.2 idna==3.10 jiter==0.5.0 jsonpatch==1.33 jsonpointer==3.0.0 langchain-core==0.3.5 langchain-openai==0.2.0 langsmith==0.1.125 openai==1.47.0 orjson==3.10.7 packaging==24.1 pydantic==2.9.2 pydantic_core==2.23.4 python-dotenv==1.0.1 PyYAML==6.0.2 regex==2024.9.11 requests==2.32.3 sniffio==1.3.1 tenacity==8.5.0 tiktoken==0.7.0 tqdm==4.66.5 typing_extensions==4.12.2 urllib3==2.2.3 |
※
■ 도구 호출시 사용자 승인 절차를 추가하는 방법을 보여준다. ※ OPENAI_API_KEY 환경 변수 값은 .env 파일에 정의한다. ▶ main.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
import json from dotenv import load_dotenv from langchain_core.tools import tool from langchain_openai import ChatOpenAI from langchain_core.messages import AIMessage from typing import Dict from typing import List load_dotenv() @tool def countEmail(lastDayCount : int) -> int: """Dummy function to count number of e-mails. Returns 2 * last_n_days.""" return lastDayCount * 2 @tool def sendEmail(message : str, recipient : str) -> str: """Dummy function for sending an e-mail.""" return f"Successfully sent email to {recipient}." toolList = [countEmail, sendEmail] chatOpenAI = ChatOpenAI(model = "gpt-4o-mini") runnableBinding = chatOpenAI.bind_tools(toolList) def callTool(aiMessage : AIMessage) -> List[Dict]: """Simple sequential tool calling helper.""" toolMapDictionary = {tool.name : tool for tool in toolList} toolCallList = aiMessage.tool_calls.copy() for toolCall in toolCallList: toolCall["output"] = toolMapDictionary[toolCall["name"]].invoke(toolCall["args"]) return toolCallList class NotApproved(Exception): """Custom exception.""" def approve(aiMessage : AIMessage) -> AIMessage: """Responsible for passing through its input or raising an exception. Args: aiMessage : output from the chat model Returns: aiMessage : original output from the msg """ toolCallListJSON = "\n\n".join(json.dumps(toolCall, indent = 2) for toolCall in aiMessage.tool_calls) inputString = f"Do you approve of the following tool invocations\n\n{toolCallListJSON}\n\nAnything except 'Y'/'Yes' (case-insensitive) will be treated as a no.\n >>>" outputString = input(inputString) if outputString.lower() not in ("yes", "y"): raise NotApproved(f"Tool invocations not approved:\n\n{toolCallListJSON}") return aiMessage runnableSequence = runnableBinding | approve | callTool try: toolCallList = runnableSequence.invoke("how many emails did i get in the last 5 days?") print(toolCallList) except NotApproved as e: print() print(e) |
▶ requirements.txt
■ ChatOllama 클래스를 사용해 사용자 질문을 분류하는 방법을 보여준다. ▶ main.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
from chat_classifier import ChatClassifier # 사용 예시 if __name__ == "__main__": chatClassifier = ChatClassifier(baseURL = "http://localhost:11434", modelName = "llama3.1:8b-instruct-q2_K", temperature = 0) questionList = [ "파이썬으로 크롬 브라우저를 실행하는 프로그램을 작성해줘", "메모장을 실행해줘", "크롬에서 현재 보고 있는 웹페이지를 요약해줘", "메모장 열어", "오늘 날씨 어때?", "대한민국 수도는 어디인가요?", "웹 브라우저 내용 정리해줘", "python으로 시간을 출력하는 프로그램을 작성해 주세요.", "실행중인 크롬 브라우저의 활성탭에서 내용을 요약해줘", "크롬 브라우저 내용 요약해", ] for question in questionList: result = chatClassifier.classify(question) print(question) print(result.strip()) print() """ 파이썬으로 크롬 브라우저를 실행하는 프로그램을 작성해줘 NONE 메모장을 실행해줘 노트패드실행 크롬에서 현재 보고 있는 웹페이지를 요약해줘 브라우저요약 메모장 열어 노트패드실행 오늘 날씨 어때? NONE 대한민국 수도는 어디인가요? NONE 웹 브라우저 내용 정리해줘 NONE python으로 시간을 출력하는 프로그램을 작성해 주세요. NONE 실행중인 크롬 브라우저의 활성탭에서 내용을 요약해줘 브라우저요약 크롬 브라우저 내용 요약해 브라우저요약 """ |
▶ chat_classifier.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
from langchain.prompts import PromptTemplate from langchain_community.chat_models import ChatOllama from langchain_core.output_parsers import StrOutputParser class ChatClassifier: def __init__(self, baseURL, modelName, temperature): self.baseURL = baseURL self.modelName = modelName self.temperature = temperature self.promptTemplateString = """ 당신은 AI 어시스턴트입니다. 사용자의 질문을 분석하고 사용자의 의도를 판단해 아래 3가지 그룹 중 하나의 그룹으로 분류합니다 : 1. 사용자가 윈도우즈에서 메모장을 실행하라고 요청하는 경우 '노트패드실행' 그룹으로 분류합니다. 예 : "메모장을 열어줘", "메모장 실행", "노트패드 실행해", "노트패드 오픈" 2. 사용자가 윈도우즈에서 실행 중인 크롬 브라우저나 엣지 브라우저들 중 메인 브라우저의 활성 탭에 있는 웹 페이지 내용을 요약해달라고 요청하는 경우 '브라우저요약' 그룹으로 분류합니다. 예 : "크롬 브라우저의 현재 탭 내용을 요약해줘", "엣지에서 보고 있는 페이지를 요약해", "웹 브라우저의 웹 페이지 요약해줘" 3. 나머지 사용자 질문은 모두 'NONE' 그룹으로 분류합니다. 당신은 다른 설명이나 추가 텍스트 없이 '노트패드실행', '브라우저요약', 'NONE' 단어 중 하나의 단어만 응답합니다. 사용자 질문 : {question}""" self.promptTemplate = PromptTemplate( input_variables = ["question"], template = self.promptTemplateString ) self.chatOllama = ChatOllama( base_url = self.baseURL, model = self.modelName, temperature = self.temperature, # 생성 텍스트의 무작위성 정도 (0.0 ~ 1.0) top_p = 0.9, # 상위 확률 임계값 num_ctx = 4096, # 컨텍스트 길이 repeat_penalty = 1.1, # 반복 패널티 stop = ["Human:", "AI:"], # 생성 중지 토큰 seed = 42, # 난수 생성 시드 verbose = False ) self.runnableSequence = self.promptTemplate | self.chatOllama | StrOutputParser() def classify(self, question): result = self.runnableSequence.invoke({"question" : question}) result = result.strip() if result == "노트패드실행" or result == "브라우저요약": return result return "NONE" |
▶ requirements.txt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
aiohappyeyeballs==2.4.0 aiohttp==3.10.5 aiosignal==1.3.1 annotated-types==0.7.0 anyio==4.5.0 attrs==24.2.0 certifi==2024.8.30 charset-normalizer==3.3.2 dataclasses-json==0.6.7 frozenlist==1.4.1 greenlet==3.1.0 h11==0.14.0 httpcore==1.0.5 httpx==0.27.2 idna==3.10 jsonpatch==1.33 jsonpointer==3.0.0 langchain==0.3.0 langchain-community==0.3.0 langchain-core==0.3.2 langchain-text-splitters==0.3.0 langsmith==0.1.125 marshmallow==3.22.0 multidict==6.1.0 mypy-extensions==1.0.0 numpy==1.26.4 orjson==3.10.7 packaging==24.1 pydantic==2.9.2 pydantic-settings==2.5.2 pydantic_core==2.23.4 python-dotenv==1.0.1 PyYAML==6.0.2 requests==2.32.3 sniffio==1.3.1 SQLAlchemy==2.0.35 tenacity==8.5.0 typing-inspect==0.9.0 typing_extensions==4.12.2 urllib3==2.2.3 yarl==1.11.1 |
※ pip install langchain-community
■ InjectedToolArg 클래스를 사용해 도구에 런타임 값을 전달하는 방법을 보여준다. ※ 런타임에만 알려진 도구에 값을 바인딩해야 할 수도 있다. ※ 예를 들어,
■ RunnableBinding 클래스의 invoke 메소드를 사용해 도구 호출 메시지 반환받기 ※ OPENAI_API_KEY 환경 변수 값은 .env 파일에 정의한다. ▶ main.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
from dotenv import load_dotenv from langchain_core.tools import tool from typing import List from typing_extensions import Annotated from langchain_core.tools import InjectedToolArg from langchain_openai import ChatOpenAI load_dotenv() userDictionary = {} @tool(parse_docstring = True) def updateFavoritePets(petList : List[str], userID : Annotated[str, InjectedToolArg]) -> None: """Add the list of favorite pets. Args: petList : List of favorite pets to set. userID : User's ID. """ userDictionary[userID] = petList @tool(parse_docstring = True) def deleteFavoritePets(userID : Annotated[str, InjectedToolArg]) -> None: """Delete the list of favorite pets. Args: userID : User's ID. """ if userID in userDictionary: del userDictionary[userID] @tool(parse_docstring = True) def listFavoritePets(userID : Annotated[str, InjectedToolArg]) -> None: """List favorite pets if any. Args: userID : User's ID. """ return userDictionary.get(userID, []) toolList = [ updateFavoritePets, deleteFavoritePets, listFavoritePets, ] chatOpenAI = ChatOpenAI(model = "gpt-4o-mini") runnableBinding = chatOpenAI.bind_tools(toolList) resultAIMessage = runnableBinding.invoke("my favorite animals are cats and parrots") print(resultAIMessage) """ content = '' additional_kwargs = { 'tool_calls' : [ { 'id' : 'call_K5SPmsNARej3KoY3Q5MfhUA9', 'function' : { 'arguments' : '{"petList" : ["cats", "parrots"]}', 'name' : 'updateFavoritePets' }, 'type' : 'function' } ], 'refusal' : None } response_metadata = { 'token_usage' : { 'completion_tokens' : 20, 'prompt_tokens' : 93, 'total_tokens' : 113, 'completion_tokens_details' : {'reasoning_tokens' : 0} }, 'model_name' : 'gpt-4o-mini-2024-07-18', 'system_fingerprint' : 'fp_1bb46167f9', 'finish_reason' : 'tool_calls', 'logprobs' : None } id = 'run-cf06bb22-dc56-42ca-a36c-6457535a2c48-0' tool_calls = [ { 'name' : 'updateFavoritePets', 'args' : {'petList' : ['cats', 'parrots']}, 'id' : 'call_K5SPmsNARej3KoY3Q5MfhUA9', 'type' : 'tool_call' } ] usage_metadata = { 'input_tokens' : 93, 'output_tokens' : 20, 'total_tokens' : 113 } """ |
▶
■ StructuredTool 클래스의 invoke 메소드를 사용해 도구를 호출하는 방법을 보여준다. ▶ main.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
from langchain_core.tools import tool from typing import List from typing_extensions import Annotated from langchain_core.tools import InjectedToolArg userDictionary = {} @tool(parse_docstring = True) def updateFavoritePets(petList : List[str], userID : Annotated[str, InjectedToolArg]) -> None: """Add the list of favorite pets. Args: petList : List of favorite pets to set. userID : User's ID. """ userDictionary[userID] = petList @tool(parse_docstring = True) def deleteFavoritePets(userID : Annotated[str, InjectedToolArg]) -> None: """Delete the list of favorite pets. Args: userID : User's ID. """ if userID in userDictionary: del userDictionary[userID] @tool(parse_docstring = True) def listFavoritePets(userID : Annotated[str, InjectedToolArg]) -> None: """List favorite pets if any. Args: userID : User's ID. """ return userDictionary.get(userID, []) userID = "123" updateFavoritePets.invoke({"petList" : ["lizard", "dog"], "userID" : userID}) petList = listFavoritePets.invoke({"userID" : userID}) print(userDictionary) print(petList) """ {'123': ['lizard', 'dog']} ['lizard', 'dog'] """ |
▶ requirements.txt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
aiohappyeyeballs==2.4.0 aiohttp==3.10.5 aiosignal==1.3.1 annotated-types==0.7.0 anyio==4.5.0 attrs==24.2.0 certifi==2024.8.30 charset-normalizer==3.3.2 frozenlist==1.4.1 greenlet==3.1.0 h11==0.14.0 httpcore==1.0.5 httpx==0.27.2 idna==3.10 jsonpatch==1.33 jsonpointer==3.0.0 langchain==0.3.0 langchain-core==0.3.2 langchain-text-splitters==0.3.0 langsmith==0.1.124 multidict==6.1.0 numpy==1.26.4 orjson==3.10.7 packaging==24.1 pydantic==2.9.2 pydantic_core==2.23.4 PyYAML==6.0.2 requests==2.32.3 sniffio==1.3.1 SQLAlchemy==2.0.35 tenacity==8.5.0 typing_extensions==4.12.2 urllib3==2.2.3 yarl==1.11.1 |
※ pip install langchain 명령을 실행했다.