8️⃣Expression Language(LCEL)

LCEL: LangChain Expression Language

LangChain Expression Language, LCEL은 체인을 쉽게 구성할 수 있는 선언적 방식입니다. LCEL은 가장 간단한 "프롬프트 + LLM" Chain부터 가장 복잡한 체인까지 코드 변경 없이 프로토타입을 프로덕션에 적용할 수 있도록 설계되었습니다.

LCEL을 사용하면 기본 구성 요소로 복잡한 체인을 쉽게 구축할 수 있으며 스트리밍, 병렬 처리, 로깅과 같은 기본 기능을 지원합니다.

LCEL을 사용하여 기본적인 Chain 구성을 실행해보고, ChatwithHistory를 실행해 보겠습니다.

Setup Environments

%pip install langchain-openai
import os
from dotenv import load_dotenv  

load_dotenv()
api_key = os.getenv("OPENAI_API_KEY")

Basic ChatPromptTemplate

from langchain_core.prompts import ChatPromptTemplate

prompt = ChatPromptTemplate.from_template(
    "{topic}에 대한 흥미로운 사실을 알려줄래?"
)
prompt_val = prompt.invoke(
    {"topic": "dog"}
)
print(prompt_val)
messages=[HumanMessage(content='dog에 대한 흥미로운 사실을 알려줄래?')]
print(prompt_val.to_messages())
[HumanMessage(content='dog에 대한 흥미로운 사실을 알려줄래?')]
from langchain_openai import ChatOpenAI

model = ChatOpenAI(
    model="gpt-3.5-turbo"
)
result = model.invoke(prompt_val)
result
AIMessage(content='개는 인간이 길들여진 가장 오래된 동물 중 하나입니다. 약 1만 3천년 전부터 사람들과 함께 살아왔으며, 진화의 과정에서 인간과 함께 살아가며 조화롭게 발전해왔습니다. 이는 개가 인간의 가장 가까운 친구 중 하나로 여겨지는 이유 중 하나입니다.', response_metadata={'token_usage': {'completion_tokens': 125, 'prompt_tokens': 29, 'total_tokens': 154}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_c2295e73ad', 'finish_reason': 'stop', 'logprobs': None}, id='run-4aebcc59-1495-41b9-b460-13f9d15f56bc-0')
from langchain_core.output_parsers import StrOutputParser

output_parser = StrOutputParser()
output_parser.invoke(result)
'개는 인간이 길들여진 가장 오래된 동물 중 하나입니다. 약 1만 3천년 전부터 사람들과 함께 살아왔으며, 진화의 과정에서 인간과 함께 살아가며 조화롭게 발전해왔습니다. 이는 개가 인간의 가장 가까운 친구 중 하나로 여겨지는 이유 중 하나입니다.'

LCEL(LangChain Expression Language) Chain

prompt = ChatPromptTemplate.from_template(
    "{topic}에 대한 흥미로운 사실을 알려줄래?"
)
model = ChatOpenAI()
output_parser = StrOutputParser()

# lcel로 multi-chain을 구성
basicchain = model | output_parser
basicchain.invoke("안녕!")
'안녕하세요! 무엇을 도와드릴까요?'
# lcel로 prompt-model-output_parser 구성
chain = prompt | model | output_parser

chain.invoke({"topic": "dog"})
'개는 인간의 가장 오래된 친구 중 하나로 알려져 있습니다. 실제로, 최근 연구에 따르면 15,000년에서 30,000년 전에 이미 인간과 함께 살았던 것으로 밝혀졌습니다. 이는 개가 인류의 가장 오래된 반렬족 중 하나로 여겨지는 이유 중 하나입니다.'

RAG with LCEL

from langchain.schema import Document
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
from langchain_core.runnables import RunnablePassthrough

# openAI Embedding 사용
embedding_function = OpenAIEmbeddings()

# prompt 구성
docs = [
    Document(
        page_content="개는 피자 먹는 것을 좋아한다.", metadata={"source": "animal.txt"}
    ),
    Document(
        page_content="고양이는 생선 먹는 것을 좋아한다.", metadata={"source": "animal.txt"}
    ),
]

# Chroma VectorStore 설정
db = Chroma.from_documents(
    docs, embedding_function
)
retriever = db.as_retriever()
retriever.get_relevant_documents(
    "개는 어떤 음식을 먹기를 원해?"
)
[Document(page_content='개는 피자 먹는 것을 좋아한다.', metadata={'source': 'animal.txt'}),
 Document(page_content='고양이는 생선 먹는 것을 좋아한다.', metadata={'source': 'animal.txt'}),
 Document(page_content='the dog loves to eat pizza', metadata={'source': 'animal.txt'}),
 Document(page_content='the cat loves to eat lasagna', metadata={'source': 'animal.txt'})]
retriever.invoke(
    "개는 어떤 음식을 먹기를 원해?"
)
[Document(page_content='개는 피자 먹는 것을 좋아한다.', metadata={'source': 'animal.txt'}),
 Document(page_content='고양이는 생선 먹는 것을 좋아한다.', metadata={'source': 'animal.txt'}),
 Document(page_content='the dog loves to eat pizza', metadata={'source': 'animal.txt'}),
 Document(page_content='the cat loves to eat lasagna', metadata={'source': 'animal.txt'})]
template = """Answer the question based only on the following context:
{context}

Question: {question}
"""

prompt = ChatPromptTemplate.from_template(template)
model = ChatOpenAI()
from operator import itemgetter

# Retrieval Chain으로 'context', 'question'을 구성한다.
retrieval_chain = (
    {
        "context": (lambda x: x["question"]) | retriever,
        # "question": lambda x: x["question"],
        "question": itemgetter("question"),
    }
    | prompt
    | model
    | StrOutputParser()
)
retrieval_chain.invoke(
    {"question": "개는 어떤 음식을 먹기를 원해?"}
)
'피자'

RunnablePassthrough()로 사용자 정의 함수를 대신 표현하자.

template = """Answer the question based only on the following context:
{context}

Question: {question}
"""
prompt = ChatPromptTemplate.from_template(template)
model = ChatOpenAI()

retrieval_chain = (
    {"context": retriever, "question": RunnablePassthrough()}
    | prompt
    | model
    | StrOutputParser()
)
retrieval_chain.invoke(
    "개는 어떤 음식을 먹기를 원해?"
)
'피자(replace pizza)'

Runnable Function

from langchain_core.runnables import RunnableParallel, RunnablePassthrough

runnable = RunnableParallel(
    passed=RunnablePassthrough(),
    extra=RunnablePassthrough.assign(upper=lambda x: x["input"].upper()),
    modified=lambda x: x["input"] * 3,
)

runnable.invoke({"input": "hello!"})
{'passed': {'input': 'hello!'},
 'extra': {'input': 'hello!', 'upper': 'HELLO!'},
 'modified': 'hello!hello!hello!'}


LCEL: ChatWithHistory

import os
from dotenv import load_dotenv  

load_dotenv()
api_key = os.getenv("OPENAI_API_KEY")
from langchain.schema import Document
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
import os
from dotenv import load_dotenv
# openAI Embedding 사용
embedding_function = OpenAIEmbeddings()

# prompt 구성
docs = [
    Document(
        page_content="개는 피자 먹는 것을 좋아한다.", metadata={"source": "animal.txt"}
    ),
    Document(
        page_content="고양이는 생선 먹는 것을 좋아한다.", metadata={"source": "animal.txt"}
    ),
]

# Chroma VectorStore 설정
db = Chroma.from_documents(
    docs, embedding_function
)
retriever = db.as_retriever()
retriever.invoke("정확히 무슨 뜻이야?")
Number of requested results 4 is greater than number of elements in index 2, updating n_results = 2
[Document(page_content='개는 피자 먹는 것을 좋아한다.', metadata={'source': 'animal.txt'}),
 Document(page_content='고양이는 생선 먹는 것을 좋아한다.', metadata={'source': 'animal.txt'})]
from langchain.prompts.prompt import PromptTemplate

rephrase_template = """다음 대화와 후속 질문이 주어졌을 때 후속 질문을 원래 언어로 독립된 질문으로 바꾸어 줄래.

Chat History: {chat_history}
Follow Up Input: {question}
Standalone question:"""

REPHRASE_TEMPLATE = PromptTemplate.from_template(rephrase_template)
from langchain_core.messages import AIMessage, HumanMessage
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser

rephrase_chain = REPHRASE_TEMPLATE | ChatOpenAI(temperature=0) | StrOutputParser()
rephrase_chain.invoke(
    {
        "question": "아니, 정말로?",
        "chat_history": [
            HumanMessage(content="개는 어떤 음식을 먹기를 원해?"),
            AIMessage(content="참치!"),
        ],
    }
)
'개는 어떤 음식을 먹기를 원해?'
from langchain_core.prompts import ChatPromptTemplate

template = """Answer the question based only on the following context:
{context}

Question: {question}
"""

ANSWER_PROMPT = ChatPromptTemplate.from_template(template)
from langchain_core.runnables import RunnablePassthrough

retrieval_chain = (
    {"context": retriever, "question": RunnablePassthrough()}
    | ANSWER_PROMPT
    | ChatOpenAI(temperature=0)
    | StrOutputParser()
)
final_chain = rephrase_chain | retrieval_chain
final_chain.invoke(
    {
        "question": "아니, 정말로?",
        "chat_history": [
            HumanMessage(content="개는 어떤 음식을 먹기를 원해?"),
            AIMessage(content="참치"),
        ],
    }
)
Number of requested results 4 is greater than number of elements in index 2, updating n_results = 2





'피자'

Chat with returning documents

retrieved_documents = {"docs": retriever, "question": RunnablePassthrough()}

final_inputs = {
    "context": lambda x: "\n".join(doc.page_content for doc in x["docs"]),
    "question": RunnablePassthrough(),
}
answer = {
    "answer": final_inputs | ANSWER_PROMPT | ChatOpenAI() | StrOutputParser(),
    "docs": RunnablePassthrough(),
}

final_chain = rephrase_chain | retrieved_documents | answer
result = final_chain.invoke(
    {
        "question": "아니, 정말로?",
        "chat_history": [
            HumanMessage(content="개는 어떤 음식을 먹기를 원해?"),
            AIMessage(content="참치!"),
        ],
    }
)
print(result)
Number of requested results 4 is greater than number of elements in index 2, updating n_results = 2


{'answer': '피자', 'docs': {'docs': [Document(page_content='개는 피자 먹는 것을 좋아한다.', metadata={'source': 'animal.txt'}), Document(page_content='고양이는 생선 먹는 것을 좋아한다.', metadata={'source': 'animal.txt'})], 'question': '개는 어떤 음식을 먹기를 원해?'}}
result["answer"]
'피자'
result["docs"]["docs"]
[Document(page_content='개는 피자 먹는 것을 좋아한다.', metadata={'source': 'animal.txt'}),
 Document(page_content='고양이는 생선 먹는 것을 좋아한다.', metadata={'source': 'animal.txt'})]

Last updated