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회) 반복해요:

  1. 두 도구를 사용 가능하게 하여 사용자 질문을 Mistral에 물어보기
  2. 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",
        ),
    ]

더 알아보기 (Learn more)