[MODEL I/O - Langauge Models] LLM How to - 2
- 출처 : https://python.langchain.com/docs/modules/model_io/
- 이 블로그 글은 LangChain API document의 글을 기반으로 번역되었으며 이 과정에서 약간의 내용이 추가되었습니다.
- MODEL I/O는 Prompts, Language Model, Output Parser로 이루어져 있습니다.
- How-to에서는 Langchain에서 LLM을 다루는 여러 가지 방법에 대해 설명합니다.
- 본 글에서는 Caching, Serialization, Streaming, Tracking token usage에 대해 다룹니다.
1. Caching
LangChain은 LLM에 대한 caching layer 옵션을 제공합니다. 이를 사용하는 이유는 크게 2가지가 있습니다.
- 1. LLM provider(ex.OpenAI)가 제공하는 API call의 횟수를 줄여 비용을 감소시킬 수 있습니다. 이는 같은 응답을 여러번 요구하는 경우 유용합니다.
- 2. LLM provider(ex.OpenAI)가 제공하는 API call의 횟수를 줄여 App의 응답 속도를 높일 수 있습니다.
1-1. InMemoryCache
먼저, 간단하게 InMemoryCache를 사용한 예시를 다뤄보겠습니다.
import langchain
from langchain.llms import OpenAI
from langchain.cache import InMemoryCache
import time
llm = OpenAI(model_name = 'text-davinci-002', n=2, best_of=2)
langchain.llm_cache = InMemoryCache()
# 첫 번째 코드에서는 아직 캐시에 있지 않기 때문에 실행 시간이 오래 걸립니다.
s = time.time()
print(llm('Tell me a joke'))
print(time.time() - s)
Two guys stole a calendar. They got six months each.
0.6765744686126709
# 두 번째 코드는 더 빠르게 실행됩니다.
s = time.time()
print(llm('Tell me a joke'))
print(time.time() - s)
Two guys stole a calendar. They got six months each.
0.0
1-2. Optional Caching in Chains
chain의 특정 노드에 대해 caching 기능을 끄는 것도 가능합니다. 일부 인터페이스의 제약이 있어, chain을 먼저 구성하고 LLM을 편집하는 것이 더 쉬울 수 있습니다.
summarizer map_reduce chain을 로드하여 map-step의 결과를 caching하고, combine-step에 대해서는 caching하지 않는 것을 다뤄보겠습니다. 이 방식을 적용하면, 속도는 증가하되 마지막 대답은 유동적으로 만들 수 있습니다.
먼저, cache 기능이 존재하는 LLM과 cache 기능이 없는 LLM을 불러옵니다. 각 LLM은 map단계와 reduce단계에서 사용됩니다.이후 요약할 문서를 가져와 글자수에 따라 나눕니다.
from langchain.text_splitter import CharacterTextSplitter
from langchain.chains.mapreduce import MapReduceChain
llm = OpenAI(model_name="text-davinci-002")
no_cache_llm = OpenAI(model_name="text-davinci-002", cache=False)
text_splitter = CharacterTextSplitter()
with open('./state_of_the_union.txt') as f:
state_of_the_union = f.read()
texts = text_splitter.split_text(state_of_the_union)
print(texts[0])
다음으로 주어진 문서를 요약하는 툴을 구현합니다. 이 때 위에서 언급한 것과 같이 map 단계에는 cache가 있는 LLM을 사용하고, reduce 단계에서는 cache가 없는 LLM을 사용합니다. 동일하게 설정한다면, 더 빠른 속도로 다양한 대답을 출력하는 요약 툴을 구현할 수 있습니다.
from langchain.docstore.document import Document
from langchain.chains.summarize import load_summarize_chain
docs = [Document(page_content=t) for t in texts[:3]]
# Map step은 llm을, reduce step은 no_cache_llm을 사용
chain = load_summarize_chain(llm, chain_type="map_reduce", reduce_llm=no_cache_llm)
s = time.time()
print(chain.run(docs))
print(time.time() - s)
s = time.time()
print(chain.run(docs))
print(time.time() - s)
President Biden is announcing sanctions against Russia in response to Putin's aggression in Ukraine. He also announces that the United States will provide military, economic, and humanitarian assistance to Ukraine. The President discusses the American Rescue Plan and the bipartisan Infrastructure Law, both of which will create jobs and help Americans.
3.9160373210906982
President Biden discusses the recent aggression by Vladimir Putin in Ukraine and the response by the free world. He notes that Putin is now isolated and facing economic sanctions. He also warns Russian oligarchs that the U.S. Department of Justice is assembling a task force to go after them.
1.362579107284546
2. Serialization
이번 세션에서는 LLM Configuration을 디스크에 쓰고 읽는 방법에 대해 다룹니다. 이는 주어진 LLM에 대한 Configuration을 저장하고 싶을 때 유용합니다.
먼저, LLM에 대한 Configuration을 저장합니다.
from langchain.llms import OpenAI
llm = OpenAI()
llm.save('llm.json')
llm.save('llm.yaml')
Configuration을 로드하는 방법도 거의 동일합니다.
from langchain.llms.loading import load_llm
llm_from_json = load_llm('./llm.json')
llm_from_yaml = load_llm('./llm.yaml')
3. streaming
LangChain은 LLM에 대한 스트리밍 지원을 제공합니다. 즉, 전체 응답이 반환되는 것을 기다리는 것 대신 생성되는 순서대로 즉시 볼 수 있습니다. 이는 App을 사용하는 유저에게 응답이 생성, 처리되고 있음을 보여주고 싶을 때 유용합니다.
현재, OpenAI, ChatOpenAI 및 ChatAnthropic에 대해서만 스트리밍을 지원하고 있습니다. 스트리밍을 활용하려면 on_llm_new_token을 구현하는 CallbackHandler를 사용하면 됩니다. 이 세션에서는 StreamingStdOutCallbackHandler를 사용합니다.
from langchain.llms import OpenAI
from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
llm = OpenAI(streaming=True, callbacks=[StreamingStdOutCallbackHandler()], temperature=0)
resp = llm("Write me a song about sparkling water.")
Verse 1
I'm sippin' on sparkling water,
It's so refreshing and light,
It's the perfect way to quench my thirst
On a hot summer night.
Chorus
Sparkling water, sparkling water,
It's the best way to stay hydrated,
It's so crisp and so clean,
It's the perfect way to stay refreshed.
# ... 생략
스트리밍을 사용하는 경우에도 generate를 사용하면 LLMResult에 접근할 수 있습니다. 하지만, 이 때 token_usage를 확인할 수 없습니다.
llm.generate(['Tell me a joke.'])
Q: What did the fish say when it hit the wall?
A: Dam!
LLMResult(generations=[[Generation(text='\n\nQ: What did the fish say when it hit the wall?\nA: Dam!', generation_info={'finish_reason': 'stop', 'logprobs': None})]], llm_output={'token_usage': {}, 'model_name': 'text-davinci-003'}, run=RunInfo(run_id=UUID('33d59bb4-e60f-4bd8-9ebe-907f91e553f6')))
4. Tracking token usage
이 세션에선 특정 call에서 어떻게 토큰 사용량을 추적하는지에 대해 다룹니다. LangChain은 현재 OpenAI의 API에 대해서만 토큰 사용량을 추적할 수 있습니다. single LLM call에 대한 토큰 사용량을 측정하는 아주 간단한 예시로 시작하여, 여러개의 단계로 이루어진 chain, agent에 대한 예시를 다루겠습니다.
먼저 간단한 예시에 대해 get_openai_callback을 사용하여 토큰 사용량을 측정하겠습니다.
from langchain.llms import OpenAI
from langchain.callbacks import get_openai_callback
llm = OpenAI(model_name="text-davinci-002", n=2, best_of=2,cache=False)
with get_openai_callback() as cb:
result = llm("Tell me a joke")
print(cb)
Tokens Used: 42
Prompt Tokens: 4
Completion Tokens: 38
Successful Requests: 1
Total Cost (USD): $0.00084
단일 call이 아닌, 여러 개의 call에 대한 토큰 사용량 역시 추적이 가능합니다.
with get_openai_callback() as cb:
result = llm("Tell me one joke")
result2 = llm("Tell me two joke")
print(cb)
Tokens Used: 113
Prompt Tokens: 8
Completion Tokens: 105
Successful Requests: 2
Total Cost (USD): $0.0022600000000000003
다음으로는 여러 개의 단계로 이루어진 agent를 사용할 때, 토큰 사용량을 확인해보겠습니다.
from langchain.agents import load_tools
from langchain.agents import initialize_agent
from langchain.agents import AgentType
from langchain.llms import OpenAI
llm = OpenAI(temperature=0, cache=False)
tools = load_tools(["serpapi", "llm-math"], llm=llm)
agent = initialize_agent(
tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True
)
with get_openai_callback() as cb:
response = agent.run(
"Who is Olivia Wilde's boyfriend? What is his current age raised to the 0.23 power?"
)
print(f"Total Tokens: {cb.total_tokens}")
print(f"Prompt Tokens: {cb.prompt_tokens}")
print(f"Completion Tokens: {cb.completion_tokens}")
print(f"Total Cost (USD): ${cb.total_cost}")
> Entering new chain...
I need to find out who Olivia Wilde's boyfriend is and then calculate his age raised to the 0.23 power.
Action: Search
Action Input: "Olivia Wilde boyfriend"
Observation: Olivia Wilde started dating Harry Styles after ending her years-long engagement to Jason Sudeikis — see their relationship timeline.
Thought: I need to find out Harry Styles' age.
Action: Search
Action Input: "Harry Styles age"
Observation: 29 years
Thought: I need to calculate 29 raised to the 0.23 power.
Action: Calculator
Action Input: 29^0.23
Observation: Answer: 2.169459462491557
Thought: I now know the final answer.
Final Answer: Harry Styles, Olivia Wilde's boyfriend, is 29 years old and his age raised to the 0.23 power is 2.169459462491557.
> Finished chain.
Total Tokens: 1498
Prompt Tokens: 1340
Completion Tokens: 158
Total Cost (USD): $0.029960000000000004