채팅 완성에서 커넥터 사용하기

채팅 완성에서 커넥터 사용하기 (Using Connectors in Chat Completions)

Chat Completions API(/v1/chat/completions)와 Agent Completions API(/v1/agents/completions)에서 Connectors, 내장 도구, 에이전트를 사용하는 방법을 다루는 레퍼런스 쿡북이에요. 총 8개의 레시피로 구성되어 있어요.

출처: 문서

본문

Chat Completions API(/v1/chat/completions)와 Agent Completions API(/v1/agents/completions)에서 Connectors, 내장 도구, 에이전트를 사용해 봅시다.

SDK 지원 (SDK support): mistralai SDK는 chat.complete()에서 커넥터 스타일 도구를 지원해요. 도구가 호출되면 응답이 message(객체) 대신 messages(배열)를 사용한다는 점에 유의하세요.

사전 준비사항 (Prerequisites)

설치 (Install)

# Python
pip install mistralai
# or with uv
uv add mistralai
# TypeScript / Node.js
npm install @mistralai/mistralai

필요한 환경 변수 (Required environment variables)

이 쿡북을 완료하려면 Mistral API 키가 필요해요. Studio에서 API keys 섹션으로 이동해서 새 API 키를 만들어요.

프로젝트 루트에 .env를 만들고 Mistral API 키를 추가해요.

MISTRAL_API_KEY=your-mistral-api-key

시작 전에 필요한 것 (What you need before starting)

대부분의 레시피는 다음을 가정해요.

  • 유효한 MISTRAL_API_KEY
  • 커스텀 커넥터 레시피를 위한 기존 커넥터 — Build a Database Advisor Agent를 보면 전체 커넥터 수명주기 예제를 볼 수 있고, Studio에서 만들 수도 있어요.

Conversations vs Completions — 언제 무엇을 쓸까 (When to Use Which)

Mistral은 모델과 상호작용하는 두 가지 API를 제공해요.

Feature Conversations API Chat Completions API
Endpoint /v1/conversations /v1/chat/completions
SDK support client.beta.conversations client.chat.complete()
Response format outputs[] with type: "message.output" choices[] with messages (array) when tools are used
Multi-turn state Stateless (send full history each call) Stateless (send full history each call)
Tool execution Server-side (automatic) Server-side with multi_completion format
Best for New integrations, agent workflows OpenAI-compatible apps, existing chat implementations

Chat Completions를 쓸 때 (Use Chat Completions when):

  • OpenAI에서 마이그레이션 중이고 익숙한 응답 형식을 원할 때
  • 기존 코드가 choices[].message 구조를 사용할 때
  • OpenAI 스타일 SDK와 호환성이 필요할 때

Conversations를 쓸 때 (Use Conversations when):

  • 처음부터 새 통합을 구축할 때
  • 권장되는 최신 API 표면을 원할 때
  • SDK의 베타 기능을 사용할 때

완성 응답 읽기 (Reading Completion Responses)

도구와 함께하는 채팅 완성은 각 choice가 단일 message 대신 messages 배열을 포함하는 multi_completion 형식으로 응답을 반환해요. 아래 헬퍼가 두 형식을 모두 처리해요.

Python (SDK):

def display_response(response) -> None:
    """Display text content from SDK chat completion response.

    When using connector tools, responses use `messages` array instead of `message`.
    """
    for choice in response.choices:
        # Handle multi_completion format (messages array) - used with connector tools
        if hasattr(choice, 'messages') and choice.messages:
            for message in choice.messages:
                content = message.content
                if content:
                    if isinstance(content, str):
                        print(content[:500] if len(content) > 500 else content)
                    elif isinstance(content, list):
                        for chunk in content:
                            if hasattr(chunk, 'type'):
                                if chunk.type == "text":
                                    print(getattr(chunk, 'text', ''))
                                elif chunk.type == "image_url":
                                    print(f"[Image: {getattr(chunk, 'image_url', '')[:80]}...]")
                tool_calls = getattr(message, 'tool_calls', None)
                if tool_calls:
                    print(f"Tool calls: {len(tool_calls)}")
                    for tc in tool_calls:
                        func = tc.function
                        print(f"  - {func.name}: {func.arguments}")
        # Handle standard completion format (single message)
        elif hasattr(choice, 'message') and choice.message:
            message = choice.message
            content = message.content
            if content:
                print(content[:500] if len(content) > 500 else content)
            tool_calls = getattr(message, 'tool_calls', None)
            if tool_calls:
                print(f"Tool calls: {len(tool_calls)}")
                for tc in tool_calls:
                    func = tc.function
                    print(f"  - {func.name}: {func.arguments}")

TypeScript:

function displayResponse(data: any): void {
  for (const choice of data.choices ?? []) {
    // Handle multi_completion format (messages array)
    const messages = choice.messages ?? [];
    if (messages.length > 0) {
      for (const message of messages) {
        const content = message.content;
        if (content) {
          if (typeof content === "string") {
            console.log(content.length > 500 ? content.slice(0, 500) : content);
          } else if (Array.isArray(content)) {
            for (const chunk of content) {
              if (chunk.type === "text") {
                console.log(chunk.text ?? "");
              } else if (chunk.type === "image_url") {
                console.log(`[Image: ${(chunk.image_url ?? "").slice(0, 80)}...]`);
              }
            }
          }
        }
        const toolCalls = message.tool_calls ?? [];
        if (toolCalls.length > 0) {
          console.log(`Tool calls: ${toolCalls.length}`);
          for (const tc of toolCalls) {
            const func = tc.function ?? {};
            console.log(`  - ${func.name}: ${func.arguments}`);
          }
        }
      }
    // Handle standard completion format (single message)
    } else {
      const message = choice.message ?? {};
      const content = message.content;
      if (content) {
        console.log(typeof content === "string" && content.length > 500 ? content.slice(0, 500) : content);
      }
      const toolCalls = message.tool_calls ?? [];
      if (toolCalls.length > 0) {
        console.log(`Tool calls: ${toolCalls.length}`);
        for (const tc of toolCalls) {
          const func = tc.function ?? {};
          console.log(`  - ${func.name}: ${func.arguments}`);
        }
      }
    }
  }
}

아래의 모든 레시피는 이 헬퍼를 참조해요. 프로젝트에 복사하거나 로직을 인라인해도 돼요.

레시피 (Recipes)

1. Hello World — 기본 채팅 완성 (Basic Chat Completion)

목표 (Goal): 도구나 커넥터 없이 첫 채팅 완성 요청을 보내요.

언제 쓰나 (When to use):

  • SDK 설정이 끝에서 끝까지 작동하는지 검증할 때
  • 커넥터를 추가하기 전에 응답 구조에 익숙해질 때

Python:

import asyncio
from mistralai.client import Mistral

API_KEY = "your-api-key"

async def main() -> None:
    client = Mistral(api_key=API_KEY)

    response = await client.chat.complete_async(
        model="mistral-small-latest",
        messages=[
            {"role": "user", "content": "What is the capital of France?"}
        ],
    )

    # Standard completion uses choice.message
    print(response.choices[0].message.content)

asyncio.run(main())

TypeScript:

import { Mistral } from "@mistralai/mistralai";

const client = new Mistral({ apiKey: *** });

async function main(): Promise<void> {
  const response = await client.chat.complete({
    model: "mistral-small-latest",
    messages: [
      { role: "user", content: "What is the capital of France?" },
    ],
  });

  console.log(response.choices[0].message.content);
}

main();

curl:

curl -X POST "https://api.mistral.ai/v1/chat/completions" \
  -H "Authorization: Bearer ${MIST...KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "mistral-small-latest",
    "messages": [{"role": "user", "content": "What is the capital of France?"}]
  }'

출력 예시 (Example of output):

The capital of France is Paris.

작동 방식 (How it works):

  • /v1/chat/completions는 표준 OpenAI 호환 채팅 엔드포인트예요.
  • 응답에는 choices[]가 있고 각 choice는 message 객체를 가져요.
  • 기본 완성에는 도구나 커넥터가 필요 없어요.

일반적인 오류와 해결책 (Common errors & fixes):

Error Cause Fix
401 Unauthorized Bad API key Check MISTRAL_API_KEY
422 Unprocessable Entity Invalid model name Use a valid model like mistral-small-latest

2. 이미지 생성이 있는 완성 (Completion with Image Generation)

목표 (Goal): 채팅 완성에서 내장 image_generation 도구를 사용해요.

언제 쓰나 (When to use):

  • 사용자 프롬프트를 바탕으로 이미지 생성
  • 커스텀 커넥터 없이 빠른 통합

Python:

import asyncio
from mistralai.client import Mistral

API_KEY = "your-api-key"

async def main() -> None:
    client = Mistral(api_key=API_KEY)

    response = await client.chat.complete_async(
        model="mistral-small-latest",
        messages=[
            {
                "role": "user",
                "content": "Generate an image of a sunset over the ocean.",
            }
        ],
        tools=[
            {"type": "image_generation"},
        ],
    )

    # With tools, use choice.messages (array) instead of choice.message
    display_response(response)

asyncio.run(main())

TypeScript:

import { Mistral } from "@mistralai/mistralai";

const client = new Mistral({ apiKey: *** });

async function main(): Promise<void> {
  const response = await client.chat.complete({
    model: "mistral-small-latest",
    messages: [
      {
        role: "user",
        content: "Generate an image of a sunset over the ocean.",
      },
    ],
    tools: [
      { type: "image_generation" },
    ],
  });

  displayResponse(response);
}

main();

curl:

curl -X POST "https://api.mistral.ai/v1/chat/completions" \
  -H "Authorization: Bearer ${MIST...KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "mistral-small-latest",
    "messages": [{"role": "user", "content": "Generate an image of a sunset over the ocean."}],
    "tools": [{"type": "image_generation"}]
  }'

출력 예시 (Example of output):

[Image: https://files.mistral.ai/generated/abc123...]
Here's a beautiful sunset over the ocean as requested.

작동 방식 (How it works):

  • image_generation은 내장 도구 유형이라 커넥터 생성이 필요 없어요.
  • 모델이 사용자 요청에 따라 도구 호출을 결정해요.
  • 생성된 이미지는 응답 콘텐츠에서 URL로 반환돼요.
  • 도구가 호출되면 응답이 choice.messages(배열)를 choice.message 대신 사용해요.

일반적인 오류와 해결책 (Common errors & fixes):

Error Cause Fix
422 Unprocessable Entity Invalid tool type Ensure the type is exactly "image_generation"

3. 커스텀 커넥터가 있는 완성 (Completion with a Custom Connector)

목표 (Goal): 채팅 완성에서 Connector를 사용해서 모델이 외부 도구를 호출하게 해요.

언제 쓰나 (When to use):

  • 커넥터(예: DeepWiki)를 등록했고 모델이 그 도구를 사용하길 원할 때
  • 채팅 완성 컨텍스트에서 도메인별 기능을 모델에 연결할 때

사전 준비사항 (Prereqs):

기존 커넥터 — Build a Database Advisor Agent를 보면 전체 커넥터 수명주기 예제를 볼 수 있고, Studio에서 만들 수도 있어요.

Python:

import asyncio
from mistralai.client import Mistral

API_KEY = "your-api-key"

async def main() -> None:
    client = Mistral(api_key=API_KEY)

    response = await client.chat.complete_async(
        model="mistral-small-latest",
        messages=[
            {
                "role": "user",
                "content": "Using deepwiki, tell me about the structure of the sqlite/sqlite repository.",
            }
        ],
        tools=[
            {
                "type": "connector",
                "connector_id": "my_deepwiki",  # name or UUID
            },
        ],
    )

    # With connector tools, use choice.messages (array)
    display_response(response)

asyncio.run(main())

TypeScript:

import { Mistral } from "@mistralai/mistralai";

const client = new Mistral({ apiKey: *** });

async function main(): Promise<void> {
  const response = await client.chat.complete({
    model: "mistral-small-latest",
    messages: [
      {
        role: "user",
        content:
          "Using deepwiki, tell me about the structure of the sqlite/sqlite repository.",
      },
    ],
    tools: [
      {
        type: "connector",
        connectorId: "my_deepwiki", // name or UUID
      },
    ],
  });

  displayResponse(response);
}

main();

curl:

curl -X POST "https://api.mistral.ai/v1/chat/completions" \
  -H "Authorization: Bearer ${MIST...KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "mistral-small-latest",
    "messages": [{"role": "user", "content": "Using deepwiki, tell me about the structure of the sqlite/sqlite repository."}],
    "tools": [{"type": "connector", "connector_id": "my_deepwiki"}]
  }'

출력 예시 (Example of output):

The sqlite/sqlite repository is organized into several key directories:
- src/ — core SQLite source code
- ext/ — extensions
- test/ — test suite
...

작동 방식 (How it works):

  • connector_id 필드는 커넥터의 name 또는 UUID를 받아요.
  • 모델이 MCP 서버가 노출하는 도구를 발견하고 호출할 것을 결정해요.
  • 도구 호출과 결과는 서버 쪽에서 처리되어 최종 응답만 보여요.
  • 도구가 호출되면 응답이 choice.messages(배열)를 사용해요.

일반적인 오류와 해결책 (Common errors & fixes):

Error Cause Fix
404 Not Found Connector name/ID doesn't exist Verify with connector list/get API
422 Unprocessable Entity Connector is inactive or MCP server unreachable Check the MCP server URL

4. 여러 도구 결합 (Combining Multiple Tools)

목표 (Goal): 채팅 완성에서 내장 도구 그리고 커스텀 커넥터에 모델이 동시에 접근하게 해요.

언제 쓰나 (When to use):

  • 모델이 여러 옵션 중 작업에 가장 좋은 도구를 선택하길 원할 때
  • 다중 기능 어시스턴트 구축

사전 준비사항 (Prereqs):

기존 커넥터.

Python:

import asyncio
from mistralai.client import Mistral

API_KEY = "your-api-key"

async def main() -> None:
    client = Mistral(api_key=API_KEY)

    response = await client.chat.complete_async(
        model="mistral-small-latest",
        messages=[
            {
                "role": "user",
                "content": "What tools do you have access to? List them briefly.",
            }
        ],
        tools=[
            {"type": "image_generation"},
            {
                "type": "connector",
                "connector_id": "my_deepwiki",
            },
        ],
    )

    display_response(response)

asyncio.run(main())

TypeScript:

import { Mistral } from "@mistralai/mistralai";

const client = new Mistral({ apiKey: *** });

async function main(): Promise<void> {
  const response = await client.chat.complete({
    model: "mistral-small-latest",
    messages: [
      {
        role: "user",
        content: "What tools do you have access to? List them briefly.",
      },
    ],
    tools: [
      { type: "image_generation" },
      {
        type: "connector",
        connectorId: "my_deepwiki",
      },
    ],
  });

  displayResponse(response);
}

main();

curl:

curl -X POST "https://api.mistral.ai/v1/chat/completions" \
  -H "Authorization: Bearer ${MIST...KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "mistral-small-latest",
    "messages": [{"role": "user", "content": "What tools do you have access to? List them briefly."}],
    "tools": [
      {"type": "image_generation"},
      {"type": "connector", "connector_id": "my_deepwiki"}
    ]
  }'

출력 예시 (Example of output):

I have access to the following tools:
1. Image Generation — create images from text descriptions
2. read_wiki_structure — explore repository wiki structure
3. read_wiki_contents — read specific wiki pages
4. ask_question — ask questions about a repository

작동 방식 (How it works):

  • tools 배열은 내장 도구(image_generation)와 커스텀 커넥터를 어떤 조합으로든 받아요.
  • 각 커넥터는 자체 MCP 도구 세트를 노출하고, 모델은 그 모두를 봐요.
  • 모델이 사용자 질문에 따라 호출할 도구를 결정해요.

일반적인 오류와 해결책 (Common errors & fixes):

Error Cause Fix
422 Unprocessable Entity Duplicate connector IDs in the tools array Each connector should appear only once

5. 커넥터로 에이전트 만들기 (Creating an Agent with Connectors)

목표 (Goal): Agent Completions API와 함께 사용하기 위해 커넥터와 커스텀 지시사항이 미리 구성된 영구 에이전트를 만들어요.

언제 쓰나 (When to use):

  • 항상 특정 도구에 접근할 수 있는 재사용 가능한 에이전트를 원할 때
  • 사용자가 전문화된 어시스턴트와 상호작용하는 제품 기능을 구축할 때
  • 모델, 지시사항, 도구를 미리 구성해서 API 호출을 단순화할 때

사전 준비사항 (Prereqs):

기존 커넥터.

Python:

import asyncio
from mistralai.client import Mistral

API_KEY = "your-api-key"

async def main() -> None:
    client = Mistral(api_key=API_KEY)
    agent_id: str | None = None

    try:
        # Create the agent
        agent = await client.beta.agents.create_async(
            name="deepwiki_completion_agent",
            description="Agent with DeepWiki access for code repository exploration",
            model="mistral-small-latest",
            instructions="You are a helpful assistant that can explore code repositories using DeepWiki. Be concise.",
            tools=[
                {
                    "type": "connector",
                    "connector_id": "my_deepwiki",
                },
            ],
        )
        agent_id = str(agent.id)
        print(f"Created agent: {agent.name} ({agent_id})")

    finally:
        # Clean up
        if agent_id:
            await client.beta.agents.delete_async(agent_id=agent_id)
            print(f"Deleted agent: {agent_id}")

asyncio.run(main())

TypeScript:

import { Mistral } from "@mistralai/mistralai";

const client = new Mistral({ apiKey: *** });

async function main(): Promise<void> {
  let agentId: string | undefined;

  try {
    // Create the agent
    const agent = await client.beta.agents.create({
      name: "deepwiki_completion_agent",
      description: "Agent with DeepWiki access for code repository exploration",
      model: "mistral-small-latest",
      instructions:
        "You are a helpful assistant that can explore code repositories using DeepWiki. Be concise.",
      tools: [
        {
          type: "connector",
          connectorId: "my_deepwiki",
        },
      ],
    });
    agentId = agent.id;
    console.log(`Created agent: ${agent.name} (${agentId})`);
  } finally {
    // Clean up
    if (agentId) {
      await client.beta.agents.delete({ agentId });
      console.log(`Deleted agent: ${agentId}`);
    }
  }
}

main();

curl:

# Create agent
curl -X POST "https://api.mistral.ai/v1/agents" \
  -H "Authorization: Bearer ${MIST...KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "deepwiki_completion_agent",
    "description": "Agent with DeepWiki access",
    "model": "mistral-small-latest",
    "instructions": "You are a helpful assistant that can explore code repositories using DeepWiki. Be concise.",
    "tools": [{"type": "connector", "connector_id": "my_deepwiki"}]
  }'

# Delete agent when done (use the agent ID from the response above)
curl -X DELETE "https://api.mistral.ai/v1/agents/<agent-id>" \
  -H "Authorization: Bearer ${MIST...KEY}"

출력 예시 (Example of output):

Created agent: deepwiki_completion_agent (b2c3d4e5-6789-01ab-cdef-234567890abc)
Deleted agent: b2c3d4e5-6789-01ab-cdef-234567890abc

작동 방식 (How it works):

  • 에이전트는 영구 구성이에요: model + instructions + tools.
  • 만든 후에는 에이전트의 ID를 /v1/agents/completions와 함께 사용해서 대화해요.
  • 에이전트의 tools 배열은 완성의 tools 매개변수와 동일한 형식을 사용해요.
  • 더 이상 필요 없으면 에이전트를 삭제해요.

일반적인 오류와 해결책 (Common errors & fixes):

Error Cause Fix
404 Not Found The connector referenced in the agent's tools doesn't exist Create the connector first
409 Conflict An agent with this name already exists Choose a different name or delete the existing agent

6. 에이전트 완성 (Agent Completions)

목표 (Goal): Agent Completions API로 미리 구성된 에이전트와 대화해요.

언제 쓰나 (When to use):

  • 도구가 구성된 기존 에이전트가 있을 때
  • 매 요청마다 모델, 지시사항, 도구를 전달하지 않으려 할 때
  • 전문화된 어시스턴트와 대화형 흐름 구축

사전 준비사항 (Prereqs):

기존 에이전트 ID (Recipe 5 참조).

Python:

import asyncio
from mistralai.client import Mistral

API_KEY = "your-api-key"

async def main() -> None:
    client = Mistral(api_key=API_KEY)
    agent_id = "your-agent-id"  # From agent creation

    response = await client.agents.complete_async(
        agent_id=agent_id,
        messages=[
            {
                "role": "user",
                "content": "What is the main purpose of the sqlite repository?",
            }
        ],
    )

    display_response(response)

asyncio.run(main())

TypeScript:

import { Mistral } from "@mistralai/mistralai";

const client = new Mistral({ apiKey: *** });

async function main(): Promise<void> {
  const agentId = "your-agent-id"; // From agent creation

  const response = await client.agents.complete({
    agentId,
    messages: [
      {
        role: "user",
        content: "What is the main purpose of the sqlite repository?",
      },
    ],
  });

  displayResponse(response);
}

main();

curl:

curl -X POST "https://api.mistral.ai/v1/agents/completions" \
  -H "Authorization: Bearer ${MIST...KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "<agent-id>",
    "messages": [{"role": "user", "content": "What is the main purpose of the sqlite repository?"}]
  }'

출력 예시 (Example of output):

SQLite is a self-contained, serverless, zero-configuration SQL database engine. It is the most widely deployed database in the world, embedded in countless applications including web browsers, mobile phones, and operating systems.

작동 방식 (How it works):

  • /v1/agents/completions는 에이전트의 미리 구성된 모델, 지시사항, 도구를 사용해요.
  • agent_id와 messages만 제공하면 돼요 — 구성을 반복할 필요 없어요.
  • 응답 형식은 /v1/chat/completions와 같아요.
  • 다중 턴 대화에서는 messages 배열에 전체 메시지 기록을 포함하세요.

일반적인 오류와 해결책 (Common errors & fixes):

Error Cause Fix
404 Not Found Invalid agent ID The agent may have been deleted
422 Unprocessable Entity Missing agent_id or invalid message format Ensure agent_id is provided

7. OAuth 인증 커넥터 (Gmail) (OAuth-Authenticated Connectors)

목표 (Goal): 채팅 완성에서 OAuth2 인증이 필요한 커넥터를 사용해요.

언제 쓰나 (When to use):

  • 사용자 수준의 OAuth 토큰이 필요한 서비스(Gmail, Google Drive, Slack 등)와 통합할 때
  • 모델이 사용자별 데이터에 접근하는 기능을 구축할 때

사전 준비사항 (Prereqs):

대상 서비스에 대한 유효한 OAuth2 액세스 토큰.

Python:

import asyncio
from mistralai.client import Mistral

API_KEY = "your-api-key"

async def main() -> None:
    client = Mistral(api_key=API_KEY)
    google_oauth_token = "your-google-oauth-token"

    response = await client.chat.complete_async(
        model="mistral-small-latest",
        messages=[
            {
                "role": "user",
                "content": "What's the latest email I received?",
            }
        ],
        tools=[
            {
                "type": "connector",
                "connector_id": "gmail",
                "authorization": {
                    "type": "oauth2-token",
                    "value": google_oauth_token,
                },
            },
        ],
    )

    display_response(response)

asyncio.run(main())

TypeScript:

import { Mistral } from "@mistralai/mistralai";

const client = new Mistral({ apiKey: *** });

async function main(): Promise<void> {
  const googleOauthToken = "your-google-oauth-token";

  const response = await client.chat.complete({
    model: "mistral-small-latest",
    messages: [
      {
        role: "user",
        content: "What's the latest email I received?",
      },
    ],
    tools: [
      {
        type: "connector",
        connector_id: "gmail",
        authorization: ***
          type: "oauth2-token",
          value: googleOauthToken,
        },
      },
    ],
  });

  displayResponse(response);
}

main();

curl:

curl -X POST "https://api.mistral.ai/v1/chat/completions" \
  -H "Authorization: Bearer ${MIST...KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "mistral-small-latest",
    "messages": [{"role": "user", "content": "What is the latest email I received?"}],
    "tools": [{
      "type": "connector",
      "connector_id": "gmail",
      "authorization": {
        "type": "oauth2-token",
        "value": "<your-google-oauth-token>"
      }
    }]
  }'

출력 예시 (Example of output):

Your latest email is from John Doe with the subject "Q1 Report Review" received at 2:30 PM today...

작동 방식 (How it works):

  • authorization 필드는 전역이 아니라 도구별로(per-tool) 전달돼요 — 서로 다른 커넥터가 서로 다른 토큰을 사용할 수 있어요.
  • type: "oauth2-token"은 백엔드에게 토큰을 MCP 서버로 전달하라고 알려줘요.
  • 토큰은 Mistral이 저장하지 않아요 — 요청 기간 동안만 사용돼요.
  • gmail 같은 내장 커넥터는 미리 등록되어 있어 직접 만들 필요 없어요.

일반적인 오류와 해결책 (Common errors & fixes):

Error Cause Fix
401 Unauthorized OAuth token is expired Refresh the token and retry
403 Forbidden Token doesn't have required scopes Request the correct scopes (e.g., gmail.readonly)

8. 전체 예제 — 생성, 완성, 정리 (Full Example — Create, Complete, and Clean Up)

목표 (Goal): 커넥터를 만들고, 채팅 완성과 에이전트에서 사용하고, 정리하는 끝에서 끝까지(end-to-end) 워크플로우.

언제 쓰나 (When to use):

  • 통합 테스트
  • 일회성 작업을 위한 임시(ephemeral) 커넥터
  • 프로덕션 워크플로우 템플릿

Python:

import asyncio
from mistralai.client import Mistral

API_KEY = "your-api-key"

async def main() -> None:
    client = Mistral(api_key=API_KEY)
    connector_id: str | None = None
    agent_id: str | None = None

    try:
        # 1. Create a connector
        connector = await client.beta.connectors.create_async(
            name="completions_deepwiki",
            description="DeepWiki connector for completion testing",
            server="https://mcp.deepwiki.com/mcp",
            visibility="private",
        )
        connector_id = str(connector.id)
        print(f"Created connector: {connector.name} ({connector_id})")

        # 2. Use it in a chat completion
        response = await client.chat.complete_async(
            model="mistral-small-latest",
            messages=[
                {
                    "role": "user",
                    "content": "Using deepwiki, summarize the sqlite/sqlite repo in one sentence.",
                }
            ],
            tools=[
                {"type": "connector", "connector_id": "completions_deepwiki"},
            ],
        )
        print("\nChat completion response:")
        display_response(response)

        # 3. Create an agent with the connector
        agent = await client.beta.agents.create_async(
            name="completions_test_agent",
            description="Test agent for completion cookbook",
            model="mistral-small-latest",
            instructions="You are a helpful assistant. Be concise.",
            tools=[
                {"type": "connector", "connector_id": connector_id},
            ],
        )
        agent_id = str(agent.id)
        print(f"\nCreated agent: {agent.name} ({agent_id})")

        # 4. Use agent completions
        response = await client.agents.complete_async(
            agent_id=agent_id,
            messages=[
                {
                    "role": "user",
                    "content": "What programming language is SQLite written in?",
                }
            ],
        )
        print("\nAgent completion response:")
        display_response(response)

        print("\n" + "=" * 60)
        print("  SUCCESS")
        print("=" * 60)

    finally:
        # Clean up
        print("\nCleaning up...")
        if agent_id:
            try:
                await client.beta.agents.delete_async(agent_id=agent_id)
                print(f"Deleted agent: {agent_id}")
            except Exception:
                pass

        if connector_id:
            try:
                await client.beta.connectors.delete_async(
                    connector_id=connector_id,
                )
                print(f"Deleted connector: {connector_id}")
            except Exception:
                pass

asyncio.run(main())

TypeScript:

import { Mistral } from "@mistralai/mistralai";

const client = new Mistral({ apiKey: *** });

async function main(): Promise<void> {
  let connectorId: string | undefined;
  let agentId: string | undefined;

  try {
    // 1. Create a connector
    const connector = await client.beta.connectors.create({
      name: "completions_deepwiki",
      description: "DeepWiki connector for completion testing",
      server: "https://mcp.deepwiki.com/mcp",
      visibility: "private",
    });
    connectorId = connector.id;
    console.log(`Created connector: ${connector.name} (${connectorId})`);

    // 2. Use it in a chat completion
    let response = await client.chat.complete({
      model: "mistral-small-latest",
      messages: [
        {
          role: "user",
          content: "Using deepwiki, summarize the sqlite/sqlite repo in one sentence.",
        },
      ],
      tools: [
        { type: "connector", connectorId: "completions_deepwiki" },
      ],
    });
    console.log("\nChat completion response:");
    displayResponse(response);

    // 3. Create an agent with the connector
    const agent = await client.beta.agents.create({
      name: "completions_test_agent",
      description: "Test agent for completion cookbook",
      model: "mistral-small-latest",
      instructions: "You are a helpful assistant. Be concise.",
      tools: [
        { type: "connector", connectorId },
      ],
    });
    agentId = agent.id;
    console.log(`\nCreated agent: ${agent.name} (${agentId})`);

    // 4. Use agent completions
    response = await client.agents.complete({
      agentId,
      messages: [
        {
          role: "user",
          content: "What programming language is SQLite written in?",
        },
      ],
    });
    console.log("\nAgent completion response:");
    displayResponse(response);

    console.log("\n" + "=".repeat(60));
    console.log("  SUCCESS");
    console.log("=".repeat(60));
  } finally {
    // Clean up
    console.log("\nCleaning up...");
    if (agentId) {
      try {
        await client.beta.agents.delete({ agentId });
        console.log(`Deleted agent: ${agentId}`);
      } catch {
        // ignore
      }
    }
    if (connectorId) {
      try {
        await client.beta.connectors.delete({ connectorId });
        console.log(`Deleted connector: ${connectorId}`);
      } catch {
        // ignore
      }
    }
  }
}

main();

출력 (Output):

Created connector: completions_deepwiki (c3d4e5f6-...)

Chat completion response:
SQLite is a self-contained, serverless SQL database engine used worldwide.

Created agent: completions_test_agent (d4e5f6a7-...)

Agent completion response:
SQLite is primarily written in C.

============================================================
  SUCCESS
============================================================

Cleaning up...
Deleted agent: d4e5f6a7-...
Deleted connector: c3d4e5f6-...

작동 방식 (How it works):

  • try/finally 패턴은 요청이 실패해도 리소스가 항상 정리되도록 보장해요.
  • 이 레시피는 전체 워크플로우(커넥터 생성 → 채팅 완성 → 에이전트 생성 → 에이전트 완성)를 보여줘요.
  • /v1/chat/completions와 /v1/agents/completions 모두 동일한 도구 형식을 지원해요.

Python / TypeScript 네이밍 규칙 (Naming Conventions)

Concept Python SDK TypeScript SDK
Connector ID connector_id connectorId
Agent ID agent_id agentId
Tool type type type
OAuth authorization authorization.type, authorization.value authorization.type, authorization.value

문제 해결 (Troubleshooting)

"커넥터를 찾을 수 없음(Connector not found)" 오류

  • 커넥터 존재 여부를 목록 또는 이름/ID로 조회해서 확인해요.
  • 커넥터 이름/ID가 올바르게 철자되었는지 확인해요.
  • 커넥터가 같은 워크스페이스에서 생성되었는지 확인해요.

"응답에 도구 호출이 나타나지 않음(Tool calls not appearing in response)"

  • 모델이 사용자 메시지에 따라 도구 호출 여부를 결정해요.
  • 프롬프트에서 더 명시적으로 시도해 보세요(예: "deepwiki를 사용해서...").
  • 도구 유형이 유효한지 확인해요(connector, image_generation 등).

"타임아웃 오류(Timeout errors)"

  • MCP 서버가 응답하는 데 시간이 걸릴 수 있어요 — 타임아웃을 늘려 보세요.
  • MCP 서버 URL에 접근 가능한지 확인해요.
  • 먼저 더 단순한 쿼리로 시도해 보세요.

"OAuth 토큰 만료(OAuth token expired)"

  • OAuth 토큰은 수명이 제한적이에요.
  • 애플리케이션에 토큰 갱신 로직을 구현하세요.
  • 요청 전에 토큰의 만료를 확인하세요.

"AttributeError: 'Unset' object has no attribute 'content'"

  • 커넥터 도구를 사용할 때 응답은 choice.message 대신 choice.messages(배열)를 사용해요.
  • display_response 헬퍼를 사용하거나 두 형식을 모두 확인하세요.
  • response.choices[0].messages[0].content로 콘텐츠에 접근하세요.

오류 코드 참조 (Error Codes Reference)

HTTP Status Error Common Causes
400 Bad Request Invalid JSON, missing required fields
401 Unauthorized Invalid or missing API key, expired OAuth token
403 Forbidden Insufficient permissions, invalid OAuth scopes
404 Not Found Connector/agent doesn't exist, wrong ID
409 Conflict Resource with same name already exists
422 Unprocessable Entity Invalid model name, invalid tool format, unreachable MCP server
429 Too Many Requests Rate limit exceeded
500 Internal Server Error Server-side issue, retry with backoff
502 Bad Gateway MCP server unreachable or returned an error
504 Gateway Timeout MCP server took too long to respond

요약 (Summary)

이 쿡북은 Chat Completions와 Agent Completions API에서 Connectors, 내장 도구, 에이전트를 사용하는 8가지 레시피를 다뤘어요. 기본 완성부터 OAuth 인증 커넥터, 전체 create-complete-cleanup 수명주기 예제까지요.

이 쿡북이 다루는 내용 (What this cookbook covers):

  • 기본 채팅 완성
  • 이미지 생성이 있는 완성
  • 커스텀 Connector가 있는 완성
  • 하나의 완성에서 여러 도구 결합
  • 완성에 사용할 Connectors로 에이전트 만들기
  • 에이전트 완성
  • OAuth 인증 커넥터(Gmail)
  • 전체 수명주기: Connector 만들고, 완성하고, 정리하기

사용한 Mistral 기능 (Mistral features used):

  • Chat Completions API
  • Agent Completions API
  • Agents API (beta)
  • Connectors (beta)
  • Image generation 내장 도구

기타 서비스 (Other services):

  • DeepWiki — GitHub 저장소 탐색용 MCP 서버
  • Gmail — OAuth2 인증 Connector

Connector를 Studio에서 확인할 수 있어요.

더 알아보기 (Learn more)