model_types = {# Premier models'codestral-2501': 'codestral-latest', # code generation model'mistral-large-2411': 'mistral-large-latest', # top-tier reasoning model for high-complexity tasks'pixtral-large-2411': 'pixtral-large-latest', # frontier-class multimodal model'mistral-saba-2502': 'mistral-saba-latest', # model for languages from the Middle East and South Asia'ministral-3b-2410': 'ministral-3b-latest', # edge model'ministral-8b-2410': 'ministral-8b-latest', # edge model with high performance/price ratio'mistral-embed-2312': 'mistral-embed', # embedding model'mistral-moderation-2411': 'mistral-moderation-latest', # moderation service to detect harmful text content'mistral-ocr-2503': 'mistral-ocr-latest', # OCR model to extract interleaved text and images# Free models (with weight availability)'mistral-small-2503': 'mistral-small-latest', # small model with image understanding capabilities# Research models'open-mistral-nemo-2407': 'open-mistral-nemo', # multilingual open source model}
def can_stream(m): return m in has_streaming_modelsdef can_set_system_prompt(m): return m in has_system_prompt_modelsdef can_set_temperature(m): return m in has_temperature_modelsdef can_use_image(m): return m in vision_models
# all models except codestral-mamba support custom structured outputs
model = models[1]; model
'mistral-large-2411'
Mistral SDK
cli = Mistral(api_key=MISTRAL_API_KEY)
This is what Mistral’s SDK provides for interacting with Python. To use it, pass it a list of messages, with content and a role. The roles should alternate between user and assistant.
messages = [ {"role": "user","content": [ {"type": "text","text": "What's in this image?" }, {"type": "image_url","image_url": "https://tripfixers.com/wp-content/uploads/2019/11/eiffel-tower-with-snow.jpeg" } ] }]# Get the chat responsechat_response = cli.chat.complete( model='pixtral-large-2411', messages=messages)
chat_response
ChatCompletionResponse(id='7b394afb791f4b00afba2da0f7e23814', object='chat.completion', model='pixtral-large-2411', usage=UsageInfo(prompt_tokens=1360, completion_tokens=133, total_tokens=1493), created=1743585871, choices=[ChatCompletionChoice(index=0, message=AssistantMessage(content="The image depicts the Eiffel Tower in Paris, France, on a snowy winter day. The iconic tower is prominently featured in the background, covered with a light layer of snow, giving it a picturesque and serene appearance. The surrounding area includes leafless trees, their branches also dusted with snow, enhancing the wintery ambiance. The ground is blanketed in white, and the scene is framed by a classic Parisian lamp post and a fenced pathway. The overall setting exudes a calm, peaceful, and almost magical winter atmosphere in one of the world's most famous landmarks.", tool_calls=None, prefix=False, role='assistant'), finish_reason='stop')])
messages = [ {"role": "user","content": [ {"type": "text","text": "What's in this image?" }, {"type": "image_url","image_url": "https://tripfixers.com/wp-content/uploads/2019/11/eiffel-tower-with-snow.jpeg" } ] }]# Get the chat responsestream_response = cli.chat.stream( model='pixtral-large-2411', messages=messages)
for chunk in stream_response:print(chunk.data.choices[0].delta.content)
The
image
features
a
snow
y
scene
of
the
E
iff
el
Tower
in
Paris
,
France
.
The
icon
ic
structure
is
seen
in
the
background
,
tow
ering
above
the
snow
-
covered
landscape
.
The
fore
ground
displays
a
pictures
que
path
way
lined
with
bare
trees
,
their
branches
lightly
dust
ed
with
snow
.
A
classic
Paris
ian
l
am
pp
ost
is
visible
along
the
path
way
,
adding
to
the
winter
charm
.
The
overall
atmosphere
is
ser
ene
and
magical
,
show
c
asing
the
beauty
of
Paris
during
the
winter
season
.
# Here are the list of different client methods:# - chat.complete (completion)# - chat.stream (completion streaming)# - chat.parse (structured output for instance)# - chat.fim.complete (fim: fill in middle / code generation)# - chat.ocr.process (ocr)# - chat.embeddings.create (embedding creation)
m = {'role': 'user', 'content': "I'm Jeremy"}r = cli.chat.complete(messages = [m], model = model)r
ChatCompletionResponse(id='b6ed028d89724a50a70df00904a1bf93', object='chat.completion', model='mistral-large-2411', usage=UsageInfo(prompt_tokens=7, completion_tokens=27, total_tokens=34), created=1743585882, choices=[ChatCompletionChoice(index=0, message=AssistantMessage(content='Hello Jeremy! Nice to meet you. How are you doing today? Is there something you would like to talk about or do?', tool_calls=None, prefix=False, role='assistant'), finish_reason='stop')])
print(r)
id='b6ed028d89724a50a70df00904a1bf93' object='chat.completion' model='mistral-large-2411' usage=UsageInfo(prompt_tokens=7, completion_tokens=27, total_tokens=34) created=1743585882 choices=[ChatCompletionChoice(index=0, message=AssistantMessage(content='Hello Jeremy! Nice to meet you. How are you doing today? Is there something you would like to talk about or do?', tool_calls=None, prefix=False, role='assistant'), finish_reason='stop')]
Formatting output
# Notes:# - assistant message with prefix true, should be last message# - assistant message with prefix false cannot be last.# Type of messages:# - system: instructions for the assistant (system prompt I guess - sp) (content, role='system')# - user: user message (content, role='user') # - assistant: assistant message (content, tool_calls, prefix, role='assistant')# - tool: tool call (content, tool_call_id, name, role='tool')# Check also:# - prefix# - safe_prompt (for guardrailing)
def find_block(r:abc.Mapping, # The message to look in ):"Find the message in `r`"ifisinstance(r, CompletionEvent): r = r.data # if async m = nested_idx(r, 'choices', 0)ifnot m: return mifhasattr(m, 'message'): return m.messagereturn m.delta
find_block(r)
AssistantMessage(content='Hello Jeremy! Nice to meet you. How are you doing today? Is there something you would like to talk about or do?', tool_calls=None, prefix=False, role='assistant')
def contents(r):"Helper to get the contents from response `r`." blk = find_block(r)ifnot blk: return rifhasattr(blk, 'content'): returngetattr(blk,'content')return blk
contents(r)
'Hello Jeremy! Nice to meet you. How are you doing today? Is there something you would like to talk about or do?'
Exported source
@patchdef _repr_markdown_(self:ChatCompletionResponse): det ='\n- '.join(f'{k}: {v}'for k,v indict(self).items()) res = contents(self)ifnot res: returnf"- {det}"returnf"""{contents(self)}<details>- {det}</details>"""
r
Hello Jeremy! Nice to meet you. How are you doing today? Is there something you would like to talk about or do?
choices: [ChatCompletionChoice(index=0, message=AssistantMessage(content=‘Hello Jeremy! Nice to meet you. How are you doing today? Is there something you would like to talk about or do?’, tool_calls=None, prefix=False, role=‘assistant’), finish_reason=‘stop’)]
Add together each of input_tokens and output_tokens
Exported source
@patchdef__add__(self:UsageInfo, b):"Add together each of `input_tokens` and `output_tokens`"return usage(self.prompt_tokens+b.prompt_tokens, self.completion_tokens+b.completion_tokens)
r.usage+r.usage
In: 14; Out: 54; Total: 68
# Is it relevant to Mistral AI: TBDdef wrap_latex(text, md=True):"Replace MistralAI LaTeX codes with markdown-compatible ones" text = re.sub(r"\\\((.*?)\\\)", lambda o: f"${o.group(1)}$", text) res = re.sub(r"\\\[(.*?)\\\]", lambda o: f"$${o.group(1)}$$", text, flags=re.DOTALL)if md: res = display.Markdown(res)return res
Creating message dictionaries manually can be tedious, so we’ll use helper functions from the msglm library.
We’ll use mk_msg to easily create messages like {'role': 'user', 'content': "I'm Jeremy"}. Since Mistral AI’s message format is compatible with OpenAI’s structure, we imported : from msglm import mk_msg_openai as mk_msg, mk_msgs_openai as mk_msgs
choices: [ChatCompletionChoice(index=0, message=AssistantMessage(content=“Hello Jeremy! Nice to meet you. How can I assist you today? Let’s have a friendly conversation. 😊 How are you doing?”, tool_calls=None, prefix=False, role=‘assistant’), finish_reason=‘stop’)]
We can pass more than just text messages to Mistral AI. As we’ll see later we can also pass images, SDK objects, etc. To handle these different data types we need to pass the type along with our content to OpenAI.
Here’s an example of a multimodal message containing text and images.
mk_msg infers the type automatically and creates the appropriate data structure.
LLMs, don’t actually have state, but instead dialogs are created by passing back all previous prompts and responses every time. With Mistral AI, they always alternate user and assistant. We’ll use mk_msgs from msglm to make it easier to build up these dialog lists.
msgs = mk_msgs([prompt, r, "I forgot my name. Can you remind me please?"]) msgs
[{'role': 'user', 'content': "I'm Jeremy"},
AssistantMessage(content="Hello Jeremy! Nice to meet you. How can I assist you today? Let's have a friendly conversation. 😊 How are you doing?", tool_calls=None, prefix=False, role='assistant'),
{'role': 'user', 'content': 'I forgot my name. Can you remind me please?'}]
r = cli.chat.complete(messages=msgs, model=model, max_tokens=100)r
choices: [ChatCompletionChoice(index=0, message=AssistantMessage(content=‘Of course! You just told me your name is Jeremy.’, tool_calls=None, prefix=False, role=‘assistant’), finish_reason=‘stop’)]
In addition to the standard ‘user’ and ‘assistant’ roles found in the OpenAI API for instance, Mistral AI’s API also supports ‘system’ roles for providing instructions to the model and ‘tool’ roles for tool-based interactions.
Let’s see it in action as demonstrated in Mistral AI’s guide on prefix use cases.
instruction ="""Let's roleplay.Always give a single reply.Roleplay only, using dialogue only.Do not send any comments.Do not send any notes.Do not send any disclaimers."""question ="""Hi there!"""prefix ="""Shakespeare: """r = cli.chat.complete( model="mistral-small-latest", messages=[ mk_msg(instruction, role="system"), mk_msg(question, role="user"), mk_msg(prefix, role="assistant", prefix=True), ], max_tokens=128,)r
Shakespeare: Good morrow! Who art thou that greetest me so?
choices: [ChatCompletionChoice(index=0, message=AssistantMessage(content=‘: morrow! Who art thou that greetest me so?’, tool_calls=None, prefix=False, role=‘assistant’), finish_reason=‘stop’)]
Client
# Note also the .fim (fill in middle) mistral method
class Client:def__init__(self, model, cli=None):"Basic LLM messages client."self.model,self.use = model,usage(0,0)self.text_only = model in text_only_modelsself.can_use_tools = model in tool_modelsself.c = (cli or Mistral(api_key=os.environ.get("MISTRAL_API_KEY")))
c = Client(model)
c.use
In: 0; Out: 0; Total: 0
Exported source
@patchdef _r(self:Client, r:ChatCompletionResponse):"Store the result of the message and accrue total usage."self.result = rifgetattr(r,'usage',None): self.use += r.usagereturn r
Note that mistralai.Chat.complete and mistralai.Chat.stream have the same signature, we delegate to mistralai.Chat.complete below to avoid obfuscating **kwargs parameters as explained in fastcore documentation.
@patch@delegates(mistralai.Chat.complete)def__call__(self:Client, msgs:list, # List of messages in the dialog sp:str='', # System prompt maxtok=4096, # Maximum tokens stream:bool=False, # Stream response?**kwargs):"Make a call to LLM."if'tools'in kwargs: assertself.can_use_tools, "Tool use is not supported by the current model type."ifany(c['type'] =='image_url'for msg in msgs ifisinstance(msg, dict) andisinstance(msg.get('content'), list) for c in msg['content']): assertnotself.text_only, "Images are not supported by the current model type."if sp andself.model in has_system_prompt_models: msgs = [mk_msg(sp, 'system')] +list(msgs) chat_args =dict(model=self.model, messages=msgs, max_tokens=maxtok, **kwargs) r =self.c.chat.stream(**chat_args) if stream elseself.c.chat.complete(**chat_args)returnself._r(r) ifnot stream else get_stream(map(self._r, r))
msgs = [mk_msg('Hi')]
c(msgs)
Hello! How can I assist you today? Let’s have a friendly conversation. 😊 How are you doing?
choices: [ChatCompletionChoice(index=0, message=AssistantMessage(content=“Hello! How can I assist you today? Let’s have a friendly conversation. 😊 How are you doing?”, tool_calls=None, prefix=False, role=‘assistant’), finish_reason=‘stop’)]
c.use
In: 59; Out: 44; Total: 103
for o in c(msgs, stream=True): print(o, end='')
Hello! How can I assist you today? If you're up for it, I'd love to share a fun fact or a interesting topic to kick things off. How does that sound?
c.use
In: 59; Out: 44; Total: 103
Tool use
def sums( a:int, # First thing to sum b:int# Second thing to sum) ->int: # The sum of the inputs"Adds a + b."print(f"Finding the sum of {a} and {b}")return a + b
def mk_toolres( r:abc.Mapping, # Tool use request response ns:Optional[abc.Mapping]=None, # Namespace to search for tools obj:Optional=None# Class to search for tools ):"Create a `tool_result` message from response `r`." r = mk_msg(r) tcs =getattr(r, 'tool_calls', []) res = [r]if ns isNone: ns =globals()if obj isnotNone: ns = mk_ns(obj)for tc in (tcs or []): func = tc.function cts =str(call_func_mistralai(func, ns=ns)) res.append(mk_msg(str(cts), 'tool', tool_call_id=tc.id, name=func.name))return res
class Dummy:def sums(self, a:int, # First thing to sum b:int=1# Second thing to sum ) ->int: # The sum of the inputs"Adds a + b."print(f"Finding the sum of {a} and {b}")return a + b
def _mock_id(): return''.join(choices(ascii_letters+digits, k=9))def mock_tooluse(name:str, # The name of the called function res, # The result of calling the function**kwargs): # The arguments to the function""id= _mock_id() func =dict(arguments=json.dumps(kwargs), name=name) tc =dict(id=id, function=func, type='function') req =dict(content=None, role='assistant', tool_calls=[tc]) resp = mk_msg(''if res isNoneelsestr(res), 'tool', tool_call_id=id, name=name)return [req,resp]
This function mocks the messages needed to implement tool use, for situations where you want to insert tool use messages into a dialog without actually calling into the model.
tu = mock_tooluse(name='sums', res=7063474, a=604542, b=6458932)r = c([mk_msg(pr)]+tu, tools=tools)r
Return the value of all tool calls (generally used for structured outputs)
Type
Default
Details
msgs
list
Prompt
tools
Optional
None
List of tools to make available to OpenAI model
obj
Optional
None
Class to search for tools
ns
Optional
None
Namespace to search for tools
sp
str
System prompt
maxtok
int
4096
Maximum tokens
stream
bool
False
Stream response?
temperature
OptionalNullable
top_p
Optional
None
max_tokens
OptionalNullable
stop
Union
None
random_seed
OptionalNullable
response_format
Union
None
tool_choice
Union
None
presence_penalty
Optional
None
frequency_penalty
Optional
None
n
OptionalNullable
prediction
Union
None
parallel_tool_calls
Optional
None
safe_prompt
Optional
None
retries
OptionalNullable
server_url
Optional
None
timeout_ms
Optional
None
http_headers
Optional
None
Exported source
@patch@delegates(Client.__call__)def structured(self:Client, msgs: list, # Prompt tools:Optional[list]=None, # List of tools to make available to OpenAI model obj:Optional=None, # Class to search for tools ns:Optional[abc.Mapping]=None, # Namespace to search for tools**kwargs):"Return the value of all tool calls (generally used for structured outputs)" tools = listify(tools)if ns isNone: ns=mk_ns(*tools) tools = [mk_mistralai_func(o) for o in tools]if obj isnotNone: ns = mk_ns(obj) res =self(msgs, tools=tools, tool_choice='required', **kwargs) cts =getattr(res, 'choices', []) tcs = [call_func_mistralai(t.function, ns=ns) for o in cts for t in (o.message.tool_calls or [])]return tcs
Mistral’s API doesn’t natively support response formats [TO BE CONFIRMED], so we introduce a structured method to handle tool calling for this purpose. In this setup, the tool’s result is sent directly to the user without being passed back to the model.
class Chat:def__init__(self, model:Optional[str]=None, # Model to use (leave empty if passing `cli`) cli:Optional[Client]=None, # Client to use (leave empty if passing `model`) sp='', # Optional system prompt tools:Optional[list]=None, # List of tools to make available tool_choice:Optional[str]=None): # Forced tool choice"OpenAI chat client."assert model or cliself.c = (cli or Client(model))self.h,self.sp,self.tools,self.tool_choice = [],sp,tools,tool_choice@propertydef use(self): returnself.c.use
sp ="Never mention what tools you use."chat = Chat(model, sp=sp)chat.c.use, chat.h
choices: [ChatCompletionChoice(index=0, message=AssistantMessage(content=‘You mentioned that your name is Jeremy! How can I assist you today, Jeremy?’, tool_calls=None, prefix=False, role=‘assistant’), finish_reason=‘stop’)]
chat.h
[{'role': 'user', 'content': "I'm Jeremy"},
AssistantMessage(content='Hello Jeremy! Nice to meet you. How are you today?', tool_calls=None, prefix=False, role='assistant'),
{'role': 'user', 'content': "What's my name?"},
AssistantMessage(content='You mentioned that your name is Jeremy! How can I assist you today, Jeremy?', tool_calls=None, prefix=False, role='assistant')]
chat = Chat(model, sp=sp)for o in chat("I'm Jeremy", stream=True): o = contents(o)if o andisinstance(o, str): print(o, end='')
Hello Jeremy! Nice to meet you. How are you today?
choices: [ChatCompletionChoice(index=0, message=AssistantMessage(content=‘604542 + 6458932 equals 7063474. Is there anything else I can help you with?’, tool_calls=None, prefix=False, role=‘assistant’), finish_reason=‘stop’)]
Images
As everyone knows, when testing image APIs you have to use a cute puppy.
# Image is Cute_dog.jpg from Wikimediafn = Path('samples/puppy.jpg')display.Image(filename=fn, width=200)
img = fn.read_bytes()
Mistral AI expects an image message to have the following structure
msglm automatically detects if a message is an image, encodes it, and generates the data structure above. All we need to do is a create a list containing our image and a query and then pass it to mk_msg.
Let’s try it out…
q ="In brief, what color flowers are in this image?"msg = [mk_msg(img), mk_msg(q)]
choices: [ChatCompletionChoice(index=0, message=AssistantMessage(content=‘The flowers in the image are purple with yellow centers.’, tool_calls=None, prefix=False, role=‘assistant’), finish_reason=‘stop’)]