LangChain
에서 자주 사용되는 네 가지 메서드인 invoke
, ainvoke
, stream
, astream
의 차이점과 각각의 사용 사례에 대해 알아보자.
기본 코드
openai를 호출하는 간단한 예제로 테스트를 해보자. 아래는 기본 코드이다.
from dotenv import load_dotenv
from langchain.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
load_dotenv()
llm = ChatOpenAI(
temperature=0,
model_name="gpt-4o"
)
template = "{country}의 수도는?"
prompt = PromptTemplate.from_template(template=template)
chain = prompt | llm | StrOutputParser()
1. invoke
: 동기적 요청 처리
invoke
는 LangChain에서 가장 기본적인 메서드로, 동기적으로 언어 모델에 요청을 보내고 결과를 반환한다. 이 메서드는 요청을 보낸 후, 결과를 받을 때까지 코드가 멈추는 동기 방식으로 동작한다.
사용 예시
def invoke():
response = chain.invoke({"country": "서울"})
print(response)
invoke()
응답결과
서울은 대한민국의 수도입니다. 대한민국의 정치, 경제, 문화의 중심지로서 중요한 역할을 하고 있습니다. 추가로 궁금한 사항이 있으시면 말씀해 주세요!
장점
- 단순성: 코드가 순차적으로 실행되므로 이해하기 쉽고 디버깅이 간편하다.
- 명확한 흐름: 한 번에 하나의 요청을 처리하므로, 코드의 실행 흐름을 쉽게 추적할 수 있다.
단점
- 성능 제한: 여러 요청을 동시에 처리할 수 없으므로, 대량의 요청을 처리하는 데 시간이 오래 걸릴 수 있다.
- 블로킹: 응답이 올 때까지 코드 실행이 멈추므로, 긴 응답 시간이 걸릴 경우 프로그램의 다른 부분이 지연될 수 있다.
2. ainvoke
: 비동기적 요청 처리
ainvoke
는 비동기 방식으로 언어 모델에 요청을 보내고 결과를 반환한다. 이 메서드는 Python의 asyncio
라이브러리를 활용하여 동작하며, 요청을 비동기적으로 처리할 수 있다. 이는 여러 요청을 동시에 처리하거나, 다른 작업과 병렬로 실행할 수 있다는 장점이 있다.
사용 예시
async def ainvoke():
response = await chain.ainvoke({"country": "서울"})
print(response)
asyncio.run(ainvoke())
응답결과
서울은 대한민국의 수도입니다. 대한민국의 정치, 경제, 문화의 중심지로서 중요한 역할을 하고 있습니다. 추가로 궁금한 사항이 있으시면 말씀해 주세요!
장점
- 비동기 처리: 여러 요청을 동시에 보낼 수 있어, 대규모 요청을 처리하는 데 적합하다.
- 비블로킹: 요청 처리 중에도 다른 작업을 병렬로 수행할 수 있어, 응답 시간이 긴 경우에도 효율적으로 프로그램을 실행할 수 있다.
단점
- 복잡성 증가: 비동기 코드 작성 시,
await
키워드와 함께async
함수를 사용해야 하므로 코드가 복잡해질 수 있다. - 추적 어려움: 비동기 함수가 여러 개일 경우, 코드 흐름을 추적하는 데 어려움이 있을 수 있다.
3. stream
: 동기적 스트리밍
stream
은 동기적으로 언어 모델의 응답을 스트리밍 방식으로 처리할 수 있게 된다. 스트리밍은 모델이 전체 응답을 완료하기 전에 부분적으로 응답을 받을 수 있는 방법을 제공한다. 이는 특히 큰 응답을 처리할 때 유용하다.
사용 예시
def stream():
for chunk in chain.stream({"country": "서울"}):
print(chunk)
stream()
응답결과
서울
은
대한민국
의
수도
입니다
.
.....
하는
도시
입니다
.
만일 한줄로 표시하고 싶으면 아래처럼 사용하면 된다.
async def astream():
async for chunk in chain.stream({"country": "서울"}):
print(chunk, end="", flush=True)
응답결과
서울은 대한민국의 수도입니다. 대한민국의 정치, 경제, 문화의 중심지로서 중요한 역할을 하고 있습니다.
장점
- 효율적인 데이터 처리: 대용량 응답을 부분적으로 처리할 수 있어 메모리 사용량을 줄이고, 초기 응답을 빠르게 확인할 수 있다.
- 순차적 스트리밍: 전체 응답을 기다리지 않고, 실시간으로 데이터를 처리할 수 있다.
단점
- 순차적 데이터 처리: 응답을 부분적으로 처리하는 동안 코드 실행이 멈추므로, 비동기적으로 다른 작업을 수행하기 어렵다.
- 응답 관리 복잡성: 부분적으로 데이터를 처리하기 때문에, 전체 응답을 조합하거나 관리하는 데 추가 작업이 필요할 수 있다.
4. astream
: 비동기적 스트리밍
astream
은 비동기 방식으로 스트리밍 데이터를 처리할 수 있게 한다. 이는 stream
과 유사하지만, 비동기적으로 처리할 수 있어 여러 요청을 병렬로 스트리밍하거나, 다른 작업과 함께 실행할 수 있다.
사용 예시
async def astream():
async for chunk in chain.astream({"country": "서울"}):
print(chunk)
응답결과
서울
은
대한민국
의
수도
입니다
.
.....
하는
도시
입니다
.
만일 응답을 json형식으로 리턴하고 싶다면 아래와 같이 StrOutputParser를 json으로 리턴하면 된다.
chain = prompt | llm | {"result": StrOutputParser()}
async def astream_json():
async for chunk in chain.astream({"country": "서울"}):
print(chunk)
응답결과
{'result': ''}
{'result': '서울'}
{'result': '은'}
{'result': ' 대한민국'}
{'result': '의'}
{'result': ' 수도'}
{'result': '입니다'}
{'result': '.'}
{'result': ' 대한민국'}
{'result': '의'}
{'result': ' 정치'}
{'result': ','}
{'result': ' 경제'}
{'result': ','}
{'result': ' 문화'}
{'result': ' 중심'}
{'result': '지'}
{'result': '로'}
{'result': '서'}
{'result': ' 중요한'}
{'result': ' 역할'}
{'result': '을'}
{'result': ' 하고'}
{'result': ' 있습니다'}
{'result': '.'}
{'result': ''}
장점
- 비동기 스트리밍: 여러 스트리밍 요청을 동시에 처리할 수 있어, 대규모 데이터 처리에 적합하다.
- 병렬 처리: 스트리밍 데이터 처리 중에도 다른 작업을 병렬로 수행할 수 있어, 응답을 기다리는 동안 다른 작업을 동시에 실행할 수 있다.
단점
- 복잡한 코드: 비동기 스트리밍 코드는
ainvoke
와stream
의 복잡성을 모두 가지고 있어, 코드 작성과 유지보수가 어렵다. - 추적 어려움: 여러 비동기 스트림이 동시에 실행될 때, 코드 흐름을 관리하기 어려울 수 있다.
5. 언제 무엇을 사용할까?
invoke
를 사용할 때:
- 코드가 간단하고, 동기적으로 한 번에 하나의 요청만 처리해도 충분한 경우.
- 디버깅과 코드 이해가 쉬운 구조를 원할 때.
ainvoke
를 사용할 때:
- 여러 요청을 동시에 처리해야 하거나, 비동기적으로 실행할 필요가 있을 때.
- 다른 작업과 병렬로 요청을 처리하고자 할 때.
stream
을 사용할 때:
- 대용량 응답을 실시간으로 처리해야 하며, 전체 응답을 기다리지 않고 데이터를 받아보고 싶을 때.
- 동기적으로 순차적인 스트리밍이 필요한 경우.
astream
을 사용할 때:
- 비동기적으로 대용량 응답을 스트리밍하면서, 다른 작업을 동시에 수행해야 할 때.
- 여러 스트림을 병렬로 처리하고자 할 때.
요약
함수명 | 동기/비동기 | 설명 |
---|---|---|
invoke |
동기 | 입력을 받아 결과를 반환, 실행 중 다른 작업 불가능 |
ainvoke |
비동기 | 비동기적으로 입력을 처리, 다른 작업과 병행 가능 |
stream |
동기 | 데이터 스트리밍, 중간 결과를 실시간으로 반환 |
astream |
비동기 | 비동기적으로 데이터 스트리밍, 중간 결과를 실시간으로 반환 |