Mistral AI로 Chainlit 앱 구축하기
Mistral AI로 Chainlit 앱 구축하기 (Build a Chainlit App with Mistral AI)
Mistral AI의 API 위에 Chainlit 애플리케이션을 구축하는 방법을 배우는 문서예요. 자기 반성(self-reflective) 에이전트가 중첩된 사용자 질문에 답하기에 충분한 정보를 모았는지 판단하는, Mistral LLM의 추론(reasoning) 능력을 강조합니다.
출처: 문서
본문
이 쿡북의 목표는 Mistral AI의 API 위에 Chainlit 애플리케이션을 구축하는 방법을 보여주는 거예요!
"나폴레옹의 고향 날씨는?" 같은 중첩된 사용자 질문에 답하기에 충분한 정보를 모았는지 스스로 반성하는 에이전트(self-reflective agent)를 통해 Mistral LLM의 추론 능력을 강조할게요. 이런 질문에 답하려면 여러 단계의 추론이 필요해요: 먼저 나폴레옹의 고향을 구하고, 그 위치의 날씨를 가져오는 식이죠.
이 노트북을 읽거나, 코드 전체가 app.py에 있으므로 chainlit run app.py로 바로 실행할 수 있어요. 전체 애플리케이션 코드는 다음 부분으로 나뉘어요:
- [설정 (Setup)]
- [사용 가능한 도구 정의 (Define available tools)]
- [에이전트 로직 (Agent logic)]
- [메시지 콜백 (On message callback)]
- [시작 질문 (Starter questions)]
설정 (Setup)
mistralai, chainlit, python-dotenv를 설치해요. MISTRAL_API_KEY= 다음에 Mistral AI API 키를 넣은 .env 파일을 반드시 만들어야 해요.
!pip install mistralai chainlit python-dotenv
선택 사항 - 트레이싱 (Tracing)
[Literal AI]에서 LITERAL_API_KEY를 얻어 애플리케이션 흐름을 트레이싱하고 시각화할 수 있어요. 코드 안에서 Chainlit은 함수를 트레이싱하는 @chainlit.step 데코레이터와 chainlit.instrument_mistralai()를 통한 Mistral API의 자동 계측을 제공해요. 이 노트북 예시의 트레이스는 [https://cloud.getliteral.ai/thread/ea173d7d-a53f-4eaf-a451-82090b07e6ff]에서 볼 수 있어요.
사용 가능한 도구 정의 (Define available tools)
에이전트에 제공할 도구와 그 JSON 정의를 정의해요. 두 개의 도구가 있어요:
get_current_weather-> 위치(location)를 입력받음get_home_town-> 사람 이름(person's name)을 입력받음
선택적으로 도구 정의를 @cl.step()으로 데코레이션해 [Literal AI]에서 시각화할 트레이스를 구성할 수 있어요.
import json
import chainlit as cl
@cl.step(type="tool", name="get_current_weather")
async def get_current_weather(location):
# Make an actual API call! To open-meteo.com for instance.
return json.dumps({
"location": location,
"temperature": "29",
"unit": "celsius",
"forecast": ["sunny"],
})
@cl.step(type="tool", name="get_home_town")
async def get_home_town(person: str) -> str:
"""Get the hometown of a person"""
return "Ajaccio, Corsica"
"""
JSON tool definitions provided to the LLM.
"""
tools = [
{
"type": "function",
"function": {
"name": "get_home_town",
"description": "Get the home town of a specific person",
"parameters": {
"type": "object",
"properties": {
"person": {
"type": "string",
"description": "The name of a person (first and last names) to identify."
}
},
"required": ["person"]
},
},
},
{
"type": "function",
"function": {
"name": "get_current_weather",
"description": "Get the current weather in a given location",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state, e.g. San Francisco, CA",
},
},
"required": ["location"],
},
},
}
]
# This helper function runs multiple tool calls in parallel, asynchronously.
async def run_multiple(tool_calls):
"""
Execute multiple tool calls asynchronously.
"""
available_tools = {
"get_current_weather": get_current_weather,
"get_home_town": get_home_town
}
async def run_single(tool_call):
function_name = tool_call.function.name
function_to_call = available_tools[function_name]
function_args = json.loads(tool_call.function.arguments)
function_response = await function_to_call(**function_args)
return {
"tool_call_id": tool_call.id,
"role": "tool",
"name": function_name,
"content": function_response,
}
# Run tool calls in parallel.
tool_results = await asyncio.gather(
*(run_single(tool_call) for tool_call in tool_calls)
)
return tool_results
에이전트 로직 (Agent logic)
에이전트 로직은 다음 패턴을 (최대 5회) 반복해요:
- 두 도구를 사용 가능하게 하여 사용자 질문을 Mistral에 물어보기
- Mistral이 요청하면 도구를 실행하고, 그렇지 않으면 메시지를 반환
type="run"과 선택적 태그가 있는 선택적 @cl.step을 추가해 [Literal AI]에서 호출을 트레이싱할 수 있어요.
import os
import chainlit as cl
from mistralai.client import MistralClient
mai_client = MistralClient(api_key=os.environ["MISTRAL_API_KEY"])
@cl.step(type="run", tags=["to_score"])
async def run_agent(user_query: str):
messages = [
{
"role": "system",
"content": "If needed, leverage the tools at your disposal to answer the user question, otherwise provide the answer."
},
{
"role": "user",
"content": user_query
}
]
number_iterations = 0
answer_message_content = None
while number_iterations < 5:
completion = mai_client.chat(
model="mistral-large-latest",
messages=messages,
tool_choice="auto", # use `any` to force a tool call
tools=tools,
)
message = completion.choices[0].message
messages.append(message)
answer_message_content = message.content
if not message.tool_calls:
# The LLM deemed no tool calls necessary,
# we break out of the loop and display the returned message
break
tool_results = await run_multiple(message.tool_calls)
messages.extend(tool_results)
number_iterations += 1
return answer_message_content
메시지 콜백 (On message callback)
@cl.on_message으로 주석 처리된 콜백은 모든 새 사용자 메시지마다 run_agent 함수가 호출되도록 보장해요.
import chainlit as cl
@cl.on_message
async def main(message: cl.Message):
"""
Main message handler for incoming user messages.
"""
answer_message = await run_agent(message.content)
await cl.Message(content=answer_message).send()
시작 질문 (Starter questions)
사용자가 애플리케이션을 쉽게 시도할 수 있도록 시작 질문을 정의할 수 있어요. 인증, 피드백, Slack/Discord 통합 등 Chainlit의 다른 기능도 많으니, 커스텀 LLM 애플리케이션을 구축하고 Mistral의 LLM 능력을 최대한 활용할 수 있어요. 더 자세한 내용은 Chainlit 문서를 확인하세요.
async def set_starters():
return [
cl.Starter(
label="What's the weather in Napoleon's hometown",
message="What's the weather in Napoleon's hometown?",
icon="/images/idea.svg",
),
cl.Starter(
label="What's the weather in Paris, TX?",
message="What's the weather in Paris, TX?",
icon="/images/learn.svg",
),
cl.Starter(
label="What's the weather in Michel-Angelo's hometown?",
message="What's the weather in Michel-Angelo's hometown?",
icon="/images/write.svg",
),
]