Try an interactive version of this dialog: Sign up at solve.it.com, click Upload, and pass this URL.
Tools available from fastcore.tools:
- &
rg: Run thergcommand with the args inargstr(no need to backslash escape) - &
sed: Run thesedcommand with the args inargstr(e.g for reading a section of a file) - &
view: View directory or file contents with optional line range and numbers - &
create: Creates a new file with the given content at the specified path - &
insert: Insert new_str at specified line number - &
str_replace: Replace first occurrence of old_str with new_str in file - &
strs_replace: Replace for each str pair in old_strs,new_strs - &
replace_lines: Replace lines in file using start and end line-numbers
I want to learn how lisette's tool loop works. Can you please read the documentation at https://lisette.answer.ai/core.html to see how to use it, and then use your tools to investigate the source in the current directory and show me how it's all put together? As you use your tools, detail what you're doing and exactly what you find out along the way.
I'd be happy to help you explore how lisette's tool loop works! Let me start by reading the documentation, then we'll investigate the source code together.
Let me first check out the documentation:
{
"id": "toolu_013aAWPEwPhdt9XAiZTQKm4m",
"call": {
"function": "read_url",
"arguments": {
"url": "https://lisette.answer.ai/core.html"
}
},
"result": "'[ ](./index.html) [ lisette ](./index.html)\\n\\n__\\n\\n 1. [Core](./core.html)\\n\\n[ ](./index.html)\\n\\n * [ Lisette](./index.html)\\n\\n * [ Core](./core.html)\\n\\n## On this page\\n\\n * LiteLLM\\n * Deterministic outputs\\n * patch_litellm\\n * Completion\\n * Messages formatting\\n * remove_cache_ckpts\\n * mk_msg\\n * Caching\\n * Reconstructing formatted outputs\\n * fmt2hist\\n * `mk_msgs`\\n * mk_msgs\\n * Streaming\\n * stream_with_complete\\n * Tools\\n * lite_mk_func\\n * ToolResponse\\n * Search\\n * Citations\\n * cite_footnotes\\n * cite_footnote\\n * Chat\\n * Chat\\n * Examples\\n * History tracking\\n * Synthetic History Creation\\n * Chat.print_hist\\n * random_tool_id\\n * mk_tc\\n * mk_tc_req\\n * mk_tc_result\\n * mk_tc_results\\n * Images\\n * Prefill\\n * Streaming\\n * Tool use\\n * Thinking w tool use\\n * Search\\n * Multi tool calling\\n * Tool call exhaustion\\n * Async\\n * AsyncChat\\n * astream_with_complete\\n * AsyncChat\\n * Examples\\n * Async Streaming Display\\n * mk_tr_details\\n * AsyncStreamFormatter\\n * adisplay_stream\\n * Streaming examples\\n * Tool call\\n * Thinking tool call\\n * Multiple tool calls\\n * Search\\n * Caching\\n\\n * [__Report an issue](https://github.com/AnswerDotAI/lisette/issues/new)\\n\\n## Other Formats\\n\\n * [ __CommonMark](core.html.md)\\n\\n# Core\\n\\nLisette Core\\n\\n# LiteLLM\\n\\n## Deterministic outputs\\n\\nLiteLLM `ModelResponse(Stream)` objects have `id` and `created_at` fields that are generated dynamically. Even when we use [`cachy`](https://github.com/answerdotai/cachy) to cache the LLM response these dynamic fields create diffs which makes code review more challenging. The patches below ensure that `id` and `created_at` fields are fixed and won\u2019t generate diffs.\\n\\n* * *\\n\\n[source](https://github.com/AnswerDotAI/lisette/blob/main/lisette/core.py#L24)\\n\\n### patch_litellm\\n\\n>\\n```\\n> patch_litellm (seed=0)\\n```\\n\\n_Patch litellm.ModelResponseBase such that`id` and `created` are fixed._\\n\\n```\\npatch_litellm()\\n```\\n\\n__\\n\\n## Completion\\n\\nLiteLLM provides an convenient unified interface for most big LLM providers. Because it\u2019s so useful to be able to switch LLM providers with just one argument. We want to make it even easier to by adding some more convenience functions and classes.\\n\\nThis is very similar to our other wrapper libraries for popular AI providers: [claudette](https://claudette.answer.ai/) (Anthropic), [gaspard](https://github.com/AnswerDotAI/gaspard) (Gemini), [cosette](https://answerdotai.github.io/cosette/) (OpenAI).\\n\\n```\\n# litellm._turn_on_debug()\\n```\\n\\n__\\n\\n```\\nms = [\"gemini/gemini-2.5-flash\", \"claude-sonnet-4-5\", \"openai/gpt-4.1\"]\\nmsg = [{\\'role\\':\\'user\\',\\'content\\':\\'Hey there!\\', \\'cache_control\\': {\\'type\\': \\'ephemeral\\'}}]\\nfor m in ms:\\n display(Markdown(f\\'**{m}:**\\'))\\n display(completion(m,msg))\\n```\\n\\n__\\n\\n**gemini/gemini-2.5-flash:**\\n\\nHey there! How can I help you today?\\n\\n * id: `chatcmpl-xxx`\\n * model: `gemini-2.5-flash`\\n * finish_reason: `stop`\\n * usage: `Usage(completion_tokens=153, prompt_tokens=4, total_tokens=157, completion_tokens_details=CompletionTokensDetailsWrapper(accepted_prediction_tokens=None, audio_tokens=None, reasoning_tokens=143, rejected_prediction_tokens=None, text_tokens=10), prompt_tokens_details=PromptTokensDetailsWrapper(audio_tokens=None, cached_tokens=None, text_tokens=4, image_tokens=None))`\\n\\n**claude-sonnet-4-5:**\\n\\nHello! How can I help you today?\\n\\n * id: `chatcmpl-xxx`\\n * model: `claude-sonnet-4-5-20250929`\\n * finish_reason: `stop`\\n * usage: `Usage(completion_tokens=12, prompt_tokens=10, total_tokens=22, completion_tokens_details=None, prompt_tokens_details=PromptTokensDetailsWrapper(audio_tokens=None, cached_tokens=0, text_tokens=None, image_tokens=None), cache_creation_input_tokens=0, cache_read_input_tokens=0)`\\n\\n**openai/gpt-4.1:**\\n\\nHello! How can I help you today? \ud83d\ude0a\\n\\n * id: `chatcmpl-xxx`\\n * model: `gpt-4.1-2025-04-14`\\n * finish_reason: `stop`\\n * usage: `Usage(completion_tokens=10, prompt_tokens=10, total_tokens=20, completion_tokens_details=CompletionTokensDetailsWrapper(accepted_prediction_tokens=0, audio_tokens=0, reasoning_tokens=0, rejected_prediction_tokens=0, text_tokens=None), prompt_tokens_details=PromptTokensDetailsWrapper(audio_tokens=0, cached_tokens=0, text_tokens=None, image_tokens=None))`\\n\\n## Messages formatting\\n\\nLet\u2019s start with making it easier to pass messages into litellm\u2019s `completion` function (including images, and pdf files).\\n\\n* * *\\n\\n[source](https://github.com/AnswerDotAI/lisette/blob/main/lisette/core.py#L99)\\n\\n### remove_cache_ckpts\\n\\n>\\n```\\n> remove_cache_ckpts (msg)\\n```\\n\\n_remove cache checkpoints and return msg._\\n\\n* * *\\n\\n[source](https://github.com/AnswerDotAI/lisette/blob/main/lisette/core.py#L110)\\n\\n### mk_msg\\n\\n>\\n```\\n> mk_msg (content, role=\\'user\\', cache=False, ttl=None)\\n```\\n\\n_Create a LiteLLM compatible message._\\n\\n| **Type** | **Default** | **Details** \\n---|---|---|--- \\ncontent | | | Content: str, bytes (image), list of mixed content, or dict w \u2018role\u2019 and \u2018content\u2019 fields \\nrole | str | user | Message role if content isn\u2019t already a dict/Message \\ncache | bool | False | Enable Anthropic caching \\nttl | NoneType | None | Cache TTL: \u20185m\u2019 (default) or \u20181h\u2019 \\n \\nNow we can use mk_msg to create different types of messages.\\n\\nSimple text:\\n\\n```\\nmsg = mk_msg(\"hey\")\\nmsg\\n```\\n\\n __\\n\\n```\\n{\\'role\\': \\'user\\', \\'content\\': \\'hey\\'}\\n```\\n\\nWhich can be passed to litellm\u2019s `completion` function like this:\\n\\n```\\nmodel = ms[1]\\n```\\n\\n__\\n\\n```\\nres = completion(model, [msg])\\nres\\n```\\n\\n __\\n\\nHey! How\u2019s it going? What\u2019s on your mind?\\n\\n * id: `chatcmpl-xxx`\\n * model: `claude-sonnet-4-5-20250929`\\n * finish_reason: `stop`\\n * usage: `Usage(completion_tokens=16, prompt_tokens=8, total_tokens=24, completion_tokens_details=None, prompt_tokens_details=PromptTokensDetailsWrapper(audio_tokens=None, cached_tokens=0, text_tokens=None, image_tokens=None), cache_creation_input_tokens=0, cache_read_input_tokens=0)`\\n\\nWe\u2019ll add a little shortcut to make examples and testing easier here:\\n\\n```\\ndef c(msgs, **kw):\\n msgs = [msgs] if isinstance(msgs,dict) else listify(msgs)\\n return completion(model, msgs, **kw)\\n```\\n\\n__\\n\\n```\\nc(msg)\\n```\\n\\n__\\n\\nHey! How\u2019s it going? What\u2019s on your mind?\\n\\n * id: `chatcmpl-xxx`\\n * model: `claude-sonnet-4-5-20250929`\\n * finish_reason: `stop`\\n * usage: `Usage(completion_tokens=16, prompt_tokens=8, total_tokens=24, completion_tokens_details=None, prompt_tokens_details=PromptTokensDetailsWrapper(audio_tokens=None, cached_tokens=0, text_tokens=None, image_tokens=None), cache_creation_input_tokens=0, cache_read_input_tokens=0)`\\n\\nLists w just one string element are flattened for conciseness:\\n\\n```\\ntest_eq(mk_msg(\"hey\"), mk_msg([\"hey\"]))\\n```\\n\\n__\\n\\n(LiteLLM ignores these fields when sent to other providers)\\n\\nText and images:\\n\\n```\\nimg_fn = Path(\\'samples/puppy.jpg\\')\\nImage(filename=img_fn, width=200)\\n```\\n\\n__\\n\\n\\n\\n```\\nmsg = mk_msg([\\'hey what in this image?\\',img_fn.read_bytes()])\\nprint(json.dumps(msg,indent=1)[:200]+\"...\")\\n```\\n\\n__\\n\\n```\\n{\\n \"role\": \"user\",\\n \"content\": [\\n {\\n \"type\": \"text\",\\n \"text\": \"hey what in this image?\"\\n },\\n {\\n \"type\": \"image_url\",\\n \"image_url\": \"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/4gxUSU...\\n```\\n\\n```\\nc(msg)\\n```\\n\\n__\\n\\nThis image shows an adorable **Cavalier King Charles Spaniel puppy**! The puppy has the breed\u2019s characteristic features:\\n\\n * **Coloring** : Brown (chestnut) and white coat\\n * **Sweet expression** : Large, dark eyes and a gentle face\\n * **Setting** : The puppy is lying on grass near some purple flowers (appear to be asters or similar blooms)\\n\\nThe puppy looks very young and has that irresistibly cute, innocent look that Cavalier puppies are famous for. The photo has a professional quality with nice lighting and composition, capturing the puppy\u2019s endearing personality perfectly!\\n\\n * id: `chatcmpl-xxx`\\n * model: `claude-sonnet-4-5-20250929`\\n * finish_reason: `stop`\\n * usage: `Usage(completion_tokens=139, prompt_tokens=104, total_tokens=243, completion_tokens_details=None, prompt_tokens_details=PromptTokensDetailsWrapper(audio_tokens=None, cached_tokens=0, text_tokens=None, image_tokens=None), cache_creation_input_tokens=0, cache_read_input_tokens=0)`\\n\\nLet\u2019s also demonstrate this for PDFs\\n\\n```\\npdf_fn = Path(\\'samples/solveit.pdf\\')\\nmsg = mk_msg([\\'Who is the author of this pdf?\\', pdf_fn.read_bytes()])\\nc(msg)\\n```\\n\\n__\\n\\nThe author of this PDF is **Jeremy Howard** from fast.ai. He explicitly introduces himself in the document with \u201cHi, I\u2019m Jeremy Howard, from fast.ai\u201d and goes on to describe his work co-founding fast.ai with Rachel Thomas eight years ago.\\n\\n * id: `chatcmpl-xxx`\\n * model: `claude-sonnet-4-5-20250929`\\n * finish_reason: `stop`\\n * usage: `Usage(completion_tokens=59, prompt_tokens=1610, total_tokens=1669, completion_tokens_details=None, prompt_tokens_details=PromptTokensDetailsWrapper(audio_tokens=None, cached_tokens=0, text_tokens=None, image_tokens=None), cache_creation_input_tokens=0, cache_read_input_tokens=0)`\\n\\n### Caching\\n\\nSome providers such as Anthropic require manually opting into caching. Let\u2019s try it:\\n\\n```\\ndef cpr(i): return f\\'{i} \\'*1024 + \\'This is a caching test. Report back only what number you see repeated above.\\'\\n```\\n\\n__\\n\\n```\\ndisable_cachy()\\n```\\n\\n__\\n\\n```\\nmsg = mk_msg(cpr(1), cache=True)\\nres = c(msg)\\nres\\n```\\n\\n __\\n\\n1\\n\\n * id: `chatcmpl-xxx`\\n * model: `claude-sonnet-4-5-20250929`\\n * finish_reason: `stop`\\n * usage: `Usage(completion_tokens=5, prompt_tokens=3, total_tokens=8, completion_tokens_details=None, prompt_tokens_details=PromptTokensDetailsWrapper(audio_tokens=None, cached_tokens=0, text_tokens=None, image_tokens=None), cache_creation_input_tokens=2070, cache_read_input_tokens=0)`\\n\\nAnthropic has a maximum of 4 cache checkpoints, so we remove previous ones as we go:\\n\\n```\\nres = c([remove_cache_ckpts(msg), mk_msg(res), mk_msg(cpr(2), cache=True)])\\nres\\n```\\n\\n __\\n\\n2\\n\\n * id: `chatcmpl-xxx`\\n * model: `claude-sonnet-4-5-20250929`\\n * finish_reason: `stop`\\n * usage: `Usage(completion_tokens=5, prompt_tokens=2073, total_tokens=2078, completion_tokens_details=None, prompt_tokens_details=PromptTokensDetailsWrapper(audio_tokens=None, cached_tokens=2070, text_tokens=None, image_tokens=None), cache_creation_input_tokens=2074, cache_read_input_tokens=2070)`\\n\\nWe see that the first message was cached, and this extra message has been written to cache:\\n\\n```\\nres.usage.prompt_tokens_details\\n```\\n\\n __\\n\\n```\\nPromptTokensDetailsWrapper(audio_tokens=None, cached_tokens=2070, text_tokens=None, image_tokens=None)\\n```\\n\\nWe can add a bunch of large messages in a loop to see how the number of cached tokens used grows.\\n\\nWe do this for 25 times to ensure it still works for more than >20 content blocks, [which is a known anthropic issue](https://docs.claude.com/en/docs/build-with-claude/prompt-caching).\\n\\nThe code below is commented by default, because it\u2019s slow. Please uncomment when working on caching.\\n\\n```\\n# h = []\\n# msg = mk_msg(cpr(1), cache=True)\\n\\n# for o in range(2,25):\\n# h += [remove_cache_ckpts(msg), mk_msg(res)]\\n# msg = mk_msg(cpr(o), cache=True)\\n# res = c(h+[msg])\\n# detls = res.usage.prompt_tokens_details\\n# print(o, detls.cached_tokens, detls.cache_creation_tokens, end=\\'; \\')\\n```\\n\\n__\\n\\n```\\nenable_cachy()\\n```\\n\\n__\\n\\n### Reconstructing formatted outputs\\n\\nLisette can call multiple tools in a loop. Further down this notebook, we\u2019ll provide convenience functions for formatting such a sequence of toolcalls and responses into one formatted output string.\\n\\nFor now, we\u2019ll show an example and show how to transform such a formatted output string back into a valid LiteLLM history.\\n\\n```\\nfmt_outp = \\'\\'\\'\\nI\\'ll solve this step-by-step, using parallel calls where possible.\\n\\n<details class=\\'tool-usage-details\\'>\\n\\n```json\\n{\\n \"id\": \"toolu_01KjnQH2Nsz2viQ7XYpLW3Ta\",\\n \"call\": { \"function\": \"simple_add\", \"arguments\": { \"a\": 10, \"b\": 5 } },\\n \"result\": \"15\"\\n}\\n```\\n\\n</details>\\n\\n<details class=\\'tool-usage-details\\'>\\n\\n```json\\n{\\n \"id\": \"toolu_01Koi2EZrGZsBbnQ13wuuvzY\",\\n \"call\": { \"function\": \"simple_add\", \"arguments\": { \"a\": 2, \"b\": 1 } },\\n \"result\": \"3\"\\n}\\n```\\n\\n</details>\\n\\nNow I need to multiply 15 * 3 before I can do the final division:\\n\\n<details class=\\'tool-usage-details\\'>\\n\\n```json\\n{\\n \"id\": \"toolu_0141NRaWUjmGtwxZjWkyiq6C\",\\n \"call\": { \"function\": \"multiply\", \"arguments\": { \"a\": 15, \"b\": 3 } },\\n \"result\": \"45\"\\n}\\n```\\n\\n</details>\\n\\'\\'\\'\\n```\\n\\n__\\n\\nWe can split into chunks of (text,toolstr,json):\\n\\n```\\nsp = re_tools.split(fmt_outp)\\nfor o in list(chunked(sp, 3, pad=True)): print(\\'- \\', o)\\n```\\n\\n__\\n\\n```\\n- [\"\\\\nI\\'ll solve this step-by-step, using parallel calls where possible.\\\\n\\\\n\", \\'<details class=\\\\\\'tool-usage-details\\\\\\'>\\\\n\\\\n```json\\\\n{\\\\n \"id\": \"toolu_01KjnQH2Nsz2viQ7XYpLW3Ta\",\\\\n \"call\": { \"function\": \"simple_add\", \"arguments\": { \"a\": 10, \"b\": 5 } },\\\\n \"result\": \"15\"\\\\n}\\\\n```\\\\n\\\\n</details>\\', \\'{\\\\n \"id\": \"toolu_01KjnQH2Nsz2viQ7XYpLW3Ta\",\\\\n \"call\": { \"function\": \"simple_add\", \"arguments\": { \"a\": 10, \"b\": 5 } },\\\\n \"result\": \"15\"\\\\n}\\']\\n- [\\'\\\\n\\\\n\\', \\'<details class=\\\\\\'tool-usage-details\\\\\\'>\\\\n\\\\n```json\\\\n{\\\\n \"id\": \"toolu_01Koi2EZrGZsBbnQ13wuuvzY\",\\\\n \"call\": { \"function\": \"simple_add\", \"arguments\": { \"a\": 2, \"b\": 1 } },\\\\n \"result\": \"3\"\\\\n}\\\\n```\\\\n\\\\n</details>\\', \\'{\\\\n \"id\": \"toolu_01Koi2EZrGZsBbnQ13wuuvzY\",\\\\n \"call\": { \"function\": \"simple_add\", \"arguments\": { \"a\": 2, \"b\": 1 } },\\\\n \"result\": \"3\"\\\\n}\\']\\n- [\\'\\\\n\\\\nNow I need to multiply 15 * 3 before I can do the final division:\\\\n\\\\n\\', \\'<details class=\\\\\\'tool-usage-details\\\\\\'>\\\\n\\\\n```json\\\\n{\\\\n \"id\": \"toolu_0141NRaWUjmGtwxZjWkyiq6C\",\\\\n \"call\": { \"function\": \"multiply\", \"arguments\": { \"a\": 15, \"b\": 3 } },\\\\n \"result\": \"45\"\\\\n}\\\\n```\\\\n\\\\n</details>\\', \\'{\\\\n \"id\": \"toolu_0141NRaWUjmGtwxZjWkyiq6C\",\\\\n \"call\": { \"function\": \"multiply\", \"arguments\": { \"a\": 15, \"b\": 3 } },\\\\n \"result\": \"45\"\\\\n}\\']\\n- [\\'\\\\n\\', None, None]\\n```\\n\\n* * *\\n\\n[source](https://github.com/AnswerDotAI/lisette/blob/main/lisette/core.py#L139)\\n\\n### fmt2hist\\n\\n>\\n```\\n> fmt2hist (outp:str)\\n```\\n\\n_Transform a formatted output into a LiteLLM compatible history_\\n\\nSee how we can turn that one formatted output string back into a list of Messages:\\n\\n```\\nfrom pprint import pprint\\n```\\n\\n __\\n\\n```\\nh = fmt2hist(fmt_outp)\\npprint(h)\\n```\\n\\n__\\n\\n```\\n[Message(content=\"I\\'ll solve this step-by-step, using parallel calls where possible.\", role=\\'assistant\\', tool_calls=[ChatCompletionMessageToolCall(function=Function(arguments=\\'{\"a\":10,\"b\":5}\\', name=\\'simple_add\\'), id=\\'toolu_01KjnQH2Nsz2viQ7XYpLW3Ta\\', type=\\'function\\')], function_call=None, provider_specific_fields=None),\\n {\\'content\\': \\'15\\',\\n \\'name\\': \\'simple_add\\',\\n \\'role\\': \\'tool\\',\\n \\'tool_call_id\\': \\'toolu_01KjnQH2Nsz2viQ7XYpLW3Ta\\'},\\n Message(content=\\'\\', role=\\'assistant\\', tool_calls=[ChatCompletionMessageToolCall(function=Function(arguments=\\'{\"a\":2,\"b\":1}\\', name=\\'simple_add\\'), id=\\'toolu_01Koi2EZrGZsBbnQ13wuuvzY\\', type=\\'function\\')], function_call=None, provider_specific_fields=None),\\n {\\'content\\': \\'3\\',\\n \\'name\\': \\'simple_add\\',\\n \\'role\\': \\'tool\\',\\n \\'tool_call_id\\': \\'toolu_01Koi2EZrGZsBbnQ13wuuvzY\\'},\\n Message(content=\\'Now I need to multiply 15 * 3 before I can do the final division:\\', role=\\'assistant\\', tool_calls=[ChatCompletionMessageToolCall(function=Function(arguments=\\'{\"a\":15,\"b\":3}\\', name=\\'multiply\\'), id=\\'toolu_0141NRaWUjmGtwxZjWkyiq6C\\', type=\\'function\\')], function_call=None, provider_specific_fields=None),\\n {\\'content\\': \\'45\\',\\n \\'name\\': \\'multiply\\',\\n \\'role\\': \\'tool\\',\\n \\'tool_call_id\\': \\'toolu_0141NRaWUjmGtwxZjWkyiq6C\\'},\\n Message(content=\\'.\\', role=\\'assistant\\', tool_calls=None, function_call=None, provider_specific_fields=None)]\\n```\\n\\n### [`mk_msgs`](https://lisette.answer.ai/core.html#mk_msgs)\\n\\nWe will skip tool use blocks and tool results during caching\\n\\nNow lets make it easy to provide entire conversations:\\n\\n* * *\\n\\n[source](https://github.com/AnswerDotAI/lisette/blob/main/lisette/core.py#L162)\\n\\n### mk_msgs\\n\\n>\\n```\\n> mk_msgs (msgs, cache=False, cache_idxs=[-1], ttl=None)\\n```\\n\\n_Create a list of LiteLLM compatible messages._\\n\\n| **Type** | **Default** | **Details** \\n---|---|---|--- \\nmsgs | | | List of messages (each: str, bytes, list, or dict w \u2018role\u2019 and \u2018content\u2019 fields) \\ncache | bool | False | Enable Anthropic caching \\ncache_idxs | list | [-1] | Cache breakpoint idxs \\nttl | NoneType | None | Cache TTL: \u20185m\u2019 (default) or \u20181h\u2019 \\n \\nWith [`mk_msgs`](https://lisette.answer.ai/core.html#mk_msgs) you can easily provide a whole conversation:\\n\\n```\\nmsgs = mk_msgs([\\'Hey!\\',\"Hi there!\",\"How are you?\",\"I\\'m doing fine and you?\"])\\nmsgs\\n```\\n\\n __\\n\\n```\\n[{\\'role\\': \\'user\\', \\'content\\': \\'Hey!\\'},\\n {\\'role\\': \\'assistant\\', \\'content\\': \\'Hi there!\\'},\\n {\\'role\\': \\'user\\', \\'content\\': \\'How are you?\\'},\\n {\\'role\\': \\'assistant\\', \\'content\\': \"I\\'m doing fine and you?\"}]\\n```\\n\\nBy defualt the last message will be cached when `cache=True`:\\n\\n```\\nmsgs = mk_msgs([\\'Hey!\\',\"Hi there!\",\"How are you?\",\"I\\'m doing fine and you?\"], cache=True)\\nmsgs\\n```\\n\\n __\\n\\n```\\n[{\\'role\\': \\'user\\', \\'content\\': \\'Hey!\\'},\\n {\\'role\\': \\'assistant\\', \\'content\\': \\'Hi there!\\'},\\n {\\'role\\': \\'user\\', \\'content\\': \\'How are you?\\'},\\n {\\'role\\': \\'assistant\\',\\n \\'content\\': [{\\'type\\': \\'text\\',\\n \\'text\\': \"I\\'m doing fine and you?\",\\n \\'cache_control\\': {\\'type\\': \\'ephemeral\\'}}]}]\\n```\\n\\n```\\ntest_eq(\\'cache_control\\' in msgs[-1][\\'content\\'][0], True)\\n```\\n\\n__\\n\\nAlternatively, users can provide custom `cache_idxs`. Tool call blocks and results are skipped during caching:\\n\\n```\\nmsgs = mk_msgs([\\'Hello!\\',\\'Hi! How can I help you?\\',\\'Call some functions!\\',fmt_outp], cache=True, cache_idxs=[0,-2,-1])\\nmsgs\\n```\\n\\n __\\n\\n```\\n[{\\'role\\': \\'user\\',\\n \\'content\\': [{\\'type\\': \\'text\\',\\n \\'text\\': \\'Hello!\\',\\n \\'cache_control\\': {\\'type\\': \\'ephemeral\\'}}]},\\n {\\'role\\': \\'assistant\\', \\'content\\': \\'Hi! How can I help you?\\'},\\n {\\'role\\': \\'user\\',\\n \\'content\\': [{\\'type\\': \\'text\\',\\n \\'text\\': \\'Call some functions!\\',\\n \\'cache_control\\': {\\'type\\': \\'ephemeral\\'}}]},\\n Message(content=\"I\\'ll solve this step-by-step, using parallel calls where possible.\", role=\\'assistant\\', tool_calls=[ChatCompletionMessageToolCall(function=Function(arguments=\\'{\"a\":10,\"b\":5}\\', name=\\'simple_add\\'), id=\\'toolu_01KjnQH2Nsz2viQ7XYpLW3Ta\\', type=\\'function\\')], function_call=None, provider_specific_fields=None),\\n {\\'role\\': \\'tool\\',\\n \\'tool_call_id\\': \\'toolu_01KjnQH2Nsz2viQ7XYpLW3Ta\\',\\n \\'name\\': \\'simple_add\\',\\n \\'content\\': \\'15\\'},\\n Message(content=\\'\\', role=\\'assistant\\', tool_calls=[ChatCompletionMessageToolCall(function=Function(arguments=\\'{\"a\":2,\"b\":1}\\', name=\\'simple_add\\'), id=\\'toolu_01Koi2EZrGZsBbnQ13wuuvzY\\', type=\\'function\\')], function_call=None, provider_specific_fields=None),\\n {\\'role\\': \\'tool\\',\\n \\'tool_call_id\\': \\'toolu_01Koi2EZrGZsBbnQ13wuuvzY\\',\\n \\'name\\': \\'simple_add\\',\\n \\'content\\': \\'3\\'},\\n Message(content=\\'Now I need to multiply 15 * 3 before I can do the final division:\\', role=\\'assistant\\', tool_calls=[ChatCompletionMessageToolCall(function=Function(arguments=\\'{\"a\":15,\"b\":3}\\', name=\\'multiply\\'), id=\\'toolu_0141NRaWUjmGtwxZjWkyiq6C\\', type=\\'function\\')], function_call=No<TRUNCATED>"
}
Great! Now I can see the documentation. Let me explore the source code to understand how the tool loop works. First, let me see what files are in the current directory:
{
"id": "toolu_01BmANHMXAZnZGknU6u6nyP8",
"call": {
"function": "view",
"arguments": {
"path": "."
}
},
"result": "'Directory contents of /usr/local/lib/python3.12/site-packages/lisette:\\n/usr/local/lib/python3.12/site-packages/lisette/core.py\\n/usr/local/lib/python3.12/site-packages/lisette/__init__.py\\n/usr/local/lib/python3.12/site-packages/lisette/_modidx.py\\n/usr/local/lib/python3.12/site-packages/lisette/__pycache__\\n/usr/local/lib/python3.12/site-packages/lisette/__pycache__/core.cpython-312.pyc\\n/usr/local/lib/python3.12/site-packages/lisette/__pycache__/_modidx.cpython-312.pyc\\n/usr/local/lib/python3.12/site-packages/lisette/__pycache__/__init__.cpython-312.pyc'"
}
The main code is in core.py. From the documentation, I can see that the Chat class is the key component that handles tool loops with the max_steps parameter. Let me look at the Chat class implementation:
{
"id": "toolu_01MgZeytiEAcvCxsuLb5HQzt",
"call": {
"function": "rg",
"arguments": {
"argstr": "-n \"^class Chat\""
}
},
"result": "'core.py:229:class Chat:\\n'"
}
Now let me view the Chat class to see its structure:
{
"id": "toolu_01JTL75kBNiMpZFz3AKxzWJt",
"call": {
"function": "view",
"arguments": {
"path": "core.py",
"view_range": "[229, 320]",
"nums": "True"
}
},
"result": "' 229 \u2502 class Chat:\\n 230 \u2502 def __init__(\\n 231 \u2502 self,\\n 232 \u2502 model:str, # LiteLLM compatible model name \\n 233 \u2502 sp=\\'\\', # System prompt\\n 234 \u2502 temp=0, # Temperature\\n 235 \u2502 search=False, # Search (l,m,h), if model supports it\\n 236 \u2502 tools:list=None, # Add tools\\n 237 \u2502 hist:list=None, # Chat history\\n 238 \u2502 ns:Optional[dict]=None, # Custom namespace for tool calling \\n 239 \u2502 cache=False, # Anthropic prompt caching\\n 240 \u2502 cache_idxs:list=[-1], # Anthropic cache breakpoint idxs, use `0` for sys prompt if provided\\n 241 \u2502 ttl=None, # Anthropic prompt caching ttl\\n 242 \u2502 ):\\n 243 \u2502 \"LiteLLM chat client.\"\\n 244 \u2502 self.model = model\\n 245 \u2502 hist,tools = mk_msgs(hist,cache,cache_idxs,ttl),listify(tools)\\n 246 \u2502 if ns is None and tools: ns = mk_ns(tools)\\n 247 \u2502 elif ns is None: ns = globals()\\n 248 \u2502 self.tool_schemas = [lite_mk_func(t) for t in tools] if tools else None\\n 249 \u2502 store_attr()\\n 250 \u2502 \\n 251 \u2502 def _prep_msg(self, msg=None, prefill=None):\\n 252 \u2502 \"Prepare the messages list for the API call\"\\n 253 \u2502 sp = [{\"role\": \"system\", \"content\": self.sp}] if self.sp else []\\n 254 \u2502 if sp:\\n 255 \u2502 if 0 in self.cache_idxs: sp[0] = _add_cache_control(sp[0])\\n 256 \u2502 cache_idxs = L(self.cache_idxs).filter().map(lambda o: o-1 if o>0 else o)\\n 257 \u2502 else:\\n 258 \u2502 cache_idxs = self.cache_idxs\\n 259 \u2502 if msg: self.hist = mk_msgs(self.hist+[msg], self.cache, cache_idxs, self.ttl)\\n 260 \u2502 pf = [{\"role\":\"assistant\",\"content\":prefill}] if prefill else []\\n 261 \u2502 return sp + self.hist + pf\\n 262 \u2502 \\n 263 \u2502 def _call(self, msg=None, prefill=None, temp=None, think=None, search=None, stream=False, max_steps=2, step=1, final_prompt=None, tool_choice=None, **kwargs):\\n 264 \u2502 \"Internal method that always yields responses\"\\n 265 \u2502 if step>max_steps: return\\n 266 \u2502 if not get_model_info(self.model).get(\"supports_assistant_prefill\"): prefill=None\\n 267 \u2502 if _has_search(self.model) and (s:=ifnone(search,self.search)): kwargs[\\'web_search_options\\'] = {\"search_context_size\": effort[s]}\\n 268 \u2502 else: _=kwargs.pop(\\'web_search_options\\',None)\\n 269 \u2502 res = completion(model=self.model, messages=self._prep_msg(msg, prefill), stream=stream, \\n 270 \u2502 tools=self.tool_schemas, reasoning_effort = effort.get(think), tool_choice=tool_choice,\\n 271 \u2502 # temperature is not supported when reasoning\\n 272 \u2502 temperature=None if think else ifnone(temp,self.temp),\\n 273 \u2502 **kwargs)\\n 274 \u2502 if stream:\\n 275 \u2502 if prefill: yield _mk_prefill(prefill)\\n 276 \u2502 res = yield from stream_with_complete(res,postproc=cite_footnotes)\\n 277 \u2502 m = res.choices[0].message\\n 278 \u2502 if prefill: m.content = prefill + m.content\\n 279 \u2502 self.hist.append(m)\\n 280 \u2502 yield res\\n 281 \u2502 \\n 282 \u2502 if tcs := m.tool_calls:\\n 283 \u2502 tool_results=[_lite_call_func(tc, ns=self.ns) for tc in tcs]\\n 284 \u2502 self.hist+=tool_results\\n 285 \u2502 for r in tool_results: yield r\\n 286 \u2502 if step>=max_steps-1: prompt,tool_choice,search = final_prompt,\\'none\\',False\\n 287 \u2502 else: prompt = None\\n 288 \u2502 yield from self._call(\\n 289 \u2502 prompt, prefill, temp, think, search, stream, max_steps, step+1,\\n 290 \u2502 final_prompt, tool_choice, **kwargs)\\n 291 \u2502 \\n 292 \u2502 def __call__(self,\\n 293 \u2502 msg=None, # Message str, or list of multiple message parts\\n 294 \u2502 prefill=None, # Prefill AI response if model supports it\\n 295 \u2502 temp=None, # Override temp set on chat initialization\\n 296 \u2502 think=None, # Thinking (l,m,h)\\n 297 \u2502 search=None, # Override search set on chat initialization (l,m,h)\\n 298 \u2502 stream=False, # Stream results\\n 299 \u2502 max_steps=2, # Maximum number of tool calls\\n 300 \u2502 final_prompt=_final_prompt, # Final prompt when tool calls have ran out \\n 301 \u2502 return_all=False, # Returns all intermediate ModelResponses if not streaming and has tool calls\\n 302 \u2502 **kwargs):\\n 303 \u2502 \"Main call method - handles streaming vs non-streaming\"\\n 304 \u2502 result_gen = self._call(msg, prefill, temp, think, search, stream, max_steps, 1, final_prompt, **kwargs) \\n 305 \u2502 if stream: return result_gen # streaming\\n 306 \u2502 elif return_all: return list(result_gen) # toolloop behavior\\n 307 \u2502 else: return last(result_gen) # normal chat behavior\\n 308 \u2502 \\n 309 \u2502 # %% ../nbs/00_core.ipynb\\n 310 \u2502 @patch\\n 311 \u2502 def print_hist(self:Chat):\\n 312 \u2502 \"Print each message on a different line\"\\n 313 \u2502 for r in self.hist: print(r, end=\\'\\\\n\\\\n\\')\\n 314 \u2502 \\n 315 \u2502 # %% ../nbs/00_core.ipynb\\n 316 \u2502 def random_tool_id():\\n 317 \u2502 \"Generate a random tool ID with \\'toolu_\\' prefix\"\\n 318 \u2502 random_part = \\'\\'.join(random.choices(string.ascii_letters + string.digits, k=25))\\n 319 \u2502 return f\\'toolu_{random_part}\\'\\n 320 \u2502 '"
}
Perfect! Now I can see how the tool loop works. Let me also look at the _lite_call_func function that's called to execute tools:
{
"id": "toolu_01EneJEQZYXpdf97nojX2Bmy",
"call": {
"function": "rg",
"arguments": {
"argstr": "-n \"def _lite_call_func\""
}
},
"result": "'core.py:197:def _lite_call_func(tc,ns,raise_on_err=True):\\n'"
}
{
"id": "toolu_014vztVfqjjfvy9zhxNkgYVF",
"call": {
"function": "view",
"arguments": {
"path": "core.py",
"view_range": "[197, 227]",
"nums": "True"
}
},
"result": "' 197 \u2502 def _lite_call_func(tc,ns,raise_on_err=True):\\n 198 \u2502 try: fargs = json.loads(tc.function.arguments)\\n 199 \u2502 except Exception as e: raise ValueError(f\"Failed to parse function arguments: {tc.function.arguments}\") from e\\n 200 \u2502 res = call_func(tc.function.name, fargs,ns=ns)\\n 201 \u2502 return {\"tool_call_id\": tc.id, \"role\": \"tool\", \"name\": tc.function.name, \"content\": str(res)}\\n 202 \u2502 \\n 203 \u2502 # %% ../nbs/00_core.ipynb\\n 204 \u2502 def _has_search(m):\\n 205 \u2502 i = get_model_info(m)\\n 206 \u2502 return bool(i.get(\\'search_context_cost_per_query\\') or i.get(\\'supports_web_search\\'))\\n 207 \u2502 \\n 208 \u2502 # %% ../nbs/00_core.ipynb\\n 209 \u2502 def cite_footnote(msg):\\n 210 \u2502 if not (delta:=nested_idx(msg, \\'choices\\', 0, \\'delta\\')): return\\n 211 \u2502 if citation:= nested_idx(delta, \\'provider_specific_fields\\', \\'citation\\'):\\n 212 \u2502 title = citation[\\'title\\'].replace(\\'\"\\', \\'\\\\\\\\\"\\')\\n 213 \u2502 delta.content = f\\'[*]({citation[\"url\"]} \"{title}\") \\'\\n 214 \u2502 \\n 215 \u2502 def cite_footnotes(stream_list):\\n 216 \u2502 \"Add markdown footnote citations to stream deltas\"\\n 217 \u2502 for msg in stream_list: cite_footnote(msg)\\n 218 \u2502 \\n 219 \u2502 # %% ../nbs/00_core.ipynb\\n 220 \u2502 effort = AttrDict({o[0]:o for o in (\\'low\\',\\'medium\\',\\'high\\')})\\n 221 \u2502 \\n 222 \u2502 # %% ../nbs/00_core.ipynb\\n 223 \u2502 def _mk_prefill(pf): return ModelResponseStream([StreamingChoices(delta=Delta(content=pf,role=\\'assistant\\'))])\\n 224 \u2502 \\n 225 \u2502 # %% ../nbs/00_core.ipynb\\n 226 \u2502 _final_prompt = \"You have no more tool uses. Please summarize your findings. If you did not complete your goal please tell the user what further work needs to be done so they can choose how best to proceed.\"\\n 227 \u2502 '"
}
Excellent! Now let me trace through what happens. Let me also check what _final_prompt is and look at the imports to understand dependencies:
{
"id": "toolu_01GVmJpkc2BMueGknHrK2xNo",
"call": {
"function": "view",
"arguments": {
"path": "core.py",
"view_range": "[1, 30]",
"nums": "True"
}
},
"result": "' 1 \u2502 \"\"\"Lisette Core\"\"\"\\n 2 \u2502 \\n 3 \u2502 # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/00_core.ipynb.\\n 4 \u2502 \\n 5 \u2502 # %% auto 0\\n 6 \u2502 __all__ = [\\'sonn45\\', \\'detls_tag\\', \\'re_tools\\', \\'effort\\', \\'patch_litellm\\', \\'remove_cache_ckpts\\', \\'mk_msg\\', \\'fmt2hist\\', \\'mk_msgs\\',\\n 7 \u2502 \\'stream_with_complete\\', \\'lite_mk_func\\', \\'cite_footnote\\', \\'cite_footnotes\\', \\'Chat\\', \\'random_tool_id\\', \\'mk_tc\\',\\n 8 \u2502 \\'mk_tc_req\\', \\'mk_tc_result\\', \\'mk_tc_results\\', \\'astream_with_complete\\', \\'AsyncChat\\', \\'mk_tr_details\\',\\n 9 \u2502 \\'AsyncStreamFormatter\\', \\'adisplay_stream\\']\\n 10 \u2502 \\n 11 \u2502 # %% ../nbs/00_core.ipynb\\n 12 \u2502 import asyncio, base64, json, litellm, mimetypes, random, string\\n 13 \u2502 from typing import Optional\\n 14 \u2502 from html import escape\\n 15 \u2502 from litellm import (acompletion, completion, stream_chunk_builder, Message,\\n 16 \u2502 ModelResponse, ModelResponseStream, get_model_info, register_model, Usage)\\n 17 \u2502 from litellm.utils import function_to_dict, StreamingChoices, Delta, ChatCompletionMessageToolCall, Function, Choices\\n 18 \u2502 from toolslm.funccall import mk_ns, call_func, call_func_async, get_schema\\n 19 \u2502 from fastcore.utils import *\\n 20 \u2502 from fastcore import imghdr\\n 21 \u2502 \\n 22 \u2502 # %% ../nbs/00_core.ipynb\\n 23 \u2502 def patch_litellm(seed=0):\\n 24 \u2502 \"Patch litellm.ModelResponseBase such that `id` and `created` are fixed.\"\\n 25 \u2502 from litellm.types.utils import ModelResponseBase\\n 26 \u2502 @patch\\n 27 \u2502 def __init__(self: ModelResponseBase, id=None, created=None, *args, **kwargs): \\n 28 \u2502 self._orig___init__(id=\\'chatcmpl-xxx\\', created=1000000000, *args, **kwargs)\\n 29 \u2502 \\n 30 \u2502 @patch'"
}
Great! Now I have a good understanding. Let me create a summary document that explains how the tool loop works:
Based on my investigation of the lisette source code and documentation, here's how the tool loop works:
Key Components:
-
The
Chatclass (line 229-308) - This is the main interface that manages conversations and tool loops -
The
__call__method (line 292-307) - This is what you invoke when you callchat(msg) -
The
_callmethod (line 263-290) - This is the internal recursive method that implements the actual tool loop
How the Tool Loop Works:
When you call chat(msg, max_steps=5), here's what happens:
-
Initial Setup (line 304): The
__call__method invokes_call()withstep=1 -
API Call (line 269-273):
_callmakes a completion request to the LLM with your message and available tools -
Response Handling (line 277-280): The response is added to the chat history
-
Tool Execution Check (line 282): If the model's response contains tool calls (
m.tool_calls):- Line 283: Execute each tool using
_lite_call_func() - Line 284: Add all tool results to the history
- Line 285: Yield each result (for streaming or return_all)
- Line 283: Execute each tool using
-
Recursion Decision (line 286-290):
- If we're at
max_steps-1(the final step), inject thefinal_promptto tell the model to summarize - Set
tool_choice='none'to prevent further tool calls - Otherwise, recursively call
_call()withstep+1
- If we're at
-
Termination (line 265): The recursion stops when
step > max_steps
Return Behavior (line 305-307):
stream=True: Returns a generator that yields each intermediate resultreturn_all=True: Returns a list of all ModelResponse objects- Default: Returns only the final response
Would you like me to trace through a specific example to show how a multi-step tool call works in practice?
To check what models you have access to in lisette, you'll want to use LiteLLM's model listing functionality. LiteLLM is the underlying library that lisette uses for model access.
You can try:
import litellm
litellm.model_list
Or to see models for a specific provider:
# For example, to see OpenAI models
litellm.get_supported_openai_params()
However, the most practical way is often just to try calling a model and see if it works! The models you can actually use depend on your API keys being set up in your environment.
Do you have specific API keys configured (like for OpenAI, Anthropic, Google, etc.)? That will determine which models you can access.
['meta-llama/Llama-3.3-70B-Instruct',
'dashscope/qwen-turbo-latest',
'claude-3-5-sonnet-latest',
'azure_ai/cohere-rerank-v3-english',
'azure/us/o1-2024-12-17',
'gpt-3.5-turbo-0125',
'standard/1024-x-1024/dall-e-3',
'meta.llama3-70b-instruct-v1:0',
'openrouter/mistralai/mistral-7b-instruct',
'low/1024-x-1024/gpt-image-1',
'meta-llama/Llama-2-7b-chat-hf',
'xai/grok-vision-beta',
'oci/cohere.command-plus-latest',
'claude-3-5-sonnet-20240620',
'deepinfra/mistralai/Mistral-Small-3.2-24B-Instruct-2506',
'azure/o3-mini-2025-01-31',
'doubao-embedding-large-text-240915',
'togethercomputer/CodeLlama-34b',
'eu.anthropic.claude-3-5-sonnet-20240620-v1:0',
'voyage/voyage-context-3',
'perplexity/sonar-pro',
'deepinfra/openai/gpt-oss-20b',
'gemini/gemini-exp-1114',
'vercel_ai_gateway/openai/o4-mini',
'deepgram/enhanced-general',
'gemini/gemini-2.5-flash-preview-04-17',
'cerebras/llama3.1-70b',
'o3-2025-04-16',
'us.anthropic.claude-3-sonnet-20240229-v1:0',
'lemonade/gpt-oss-20b-mxfp4-GGUF',
'amazon.titan-text-express-v1',
'gpt-4.1-nano',
'512-x-512/dall-e-2',
'azure/gpt-4.1-nano-2025-04-14',
'gpt-4-vision-preview',
'deepinfra/Qwen/Qwen2.5-7B-Instruct',
'deepgram/whisper-medium',
'gemini-2.5-flash-lite-preview-09-2025',
'gpt-4-0125-preview',
'qvv0xeq',
'gemini/veo-3.1-fast-generate-preview',
'deepinfra/meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8',
'claude-3-5-sonnet-20241022',
'v0/v0-1.0-md',
'heroku/claude-4-sonnet',
'openrouter/mistralai/mistral-7b-instruct:free',
'databricks/databricks-meta-llama-3-1-405b-instruct',
'deepinfra/nvidia/Llama-3.3-Nemotron-Super-49B-v1.5',
'apac.anthropic.claude-3-5-sonnet-20241022-v2:0',
'maritalk',
'azure/gpt-35-turbo-0125',
'doubao-embedding-large-text-250515',
'watsonx/core42/jais-13b-chat',
'azure/command-r-plus',
'apac.anthropic.claude-3-5-sonnet-20240620-v1:0',
'azure_ai/Meta-Llama-3.1-8B-Instruct',
'gpt-4o-mini-realtime-preview-2024-12-17',
'eu.twelvelabs.marengo-embed-2-7-v1:0',
'xai/grok-4-fast-reasoning',
'snowflake/mistral-large',
'anyscale/codellama/CodeLlama-70b-Instruct-hf',
'text-embedding-3-large',
'openrouter/openai/gpt-4',
'WizardLM/WizardLM-70B-V1.0',
'nscale/deepseek-ai/DeepSeek-R1-Distill-Llama-70B',
'low/1024-x-1536/gpt-image-1',
'lambda_ai/llama3.2-3b-instruct',
'vercel_ai_gateway/openai/text-embedding-ada-002',
'watsonx/openai/gpt-oss-120b',
'azure/us/gpt-4o-2024-11-20',
'vercel_ai_gateway/zai/glm-4.6',
'anyscale/meta-llama/Meta-Llama-3-8B-Instruct',
'gemini-1.5-flash',
'groq/llama3-groq-70b-8192-tool-use-preview',
'deepgram/nova-2-atc',
'gemini-2.0-pro-exp-02-05',
'jp.anthropic.claude-haiku-4-5-20251001-v1:0',
'palm/text-bison-safety-recitation-off',
'gpt-4o-mini-search-preview',
'luminous-extended-control',
'medlm-large',
'clarifai/openbmb.miniCPM.MiniCPM3-4B',
'azure_ai/Cohere-embed-v3-english',
'azure/gpt-4-0125-preview',
'azure/o1-2024-12-17',
'meta_llama/Llama-4-Scout-17B-16E-Instruct-FP8',
'gemini/gemini-pro-vision',
'morph/morph-v3-fast',
'ai21.j2-mid-v1',
'nscale/Qwen/Qwen2.5-Coder-32B-Instruct',
'deepinfra/Sao10K/L3.3-70B-Euryale-v2.3',
'gemini-1.0-pro-001',
'ovhcloud/Meta-Llama-3_1-70B-Instruct',
'watsonx/ibm/granite-4-h-small',
'vercel_ai_gateway/xai/grok-3-mini',
'azure/eu/gpt-4o-mini-realtime-preview-2024-12-17',
'openai/gpt-oss-120b',
'gemini/gemini-2.5-flash-lite-preview-09-2025',
'moonshot/moonshot-v1-32k',
'anyscale/mistralai/Mixtral-8x22B-Instruct-v0.1',
'luminous-supreme',
'upstage/SOLAR-0-70b-16bit',
'nscale/deepseek-ai/DeepSeek-R1-Distill-Qwen-7B',
'gpt-4-0314',
'text-moderation-stable',
'azure/text-embedding-ada-002',
'chat-bison-32k',
'gemini/gemini-1.5-flash-8b-exp-0827',
'claude-3-7-sonnet-20250219',
'clarifai/xai.chat-completion.grok-2-vision-1212',
'claude-sonnet-4-5-20250929',
'clarifai/openai.chat-completion.gpt-oss-20b',
'cohere.rerank-v3-5:0',
'groq/deepseek-r1-distill-llama-70b',
'openrouter/deepseek/deepseek-r1-0528',
'moonshot/kimi-latest-128k',
'clarifai/microsoft.text-generation.phi-4',
'deepinfra/meta-llama/Llama-Guard-3-8B',
'vercel_ai_gateway/alibaba/qwen-3-235b',
'sambanova/Meta-Llama-3.1-405B-Instruct',
'palm/text-bison',
'databricks/databricks-gte-large-en',
'o3-mini-2025-01-31',
'gpt-4-32k-0314',
'nscale/Qwen/QwQ-32B',
'gradient_ai/openai-gpt-4o',
'gemini/gemini-2.0-flash-lite',
'groq/llama-3.2-11b-text-preview',
'deepinfra/meta-llama/Meta-Llama-3.1-8B-Instruct',
'perplexity/sonar-reasoning-pro',
'perplexity/pplx-70b-chat',
'deepinfra/meta-llama/Llama-4-Scout-17B-16E-Instruct',
'azure/whisper-1',
'meta.llama3-2-3b-instruct-v1:0',
'mistral.mistral-7b-instruct-v0:2',
'dolphin',
'openrouter/openai/gpt-4o',
'openrouter/openai/o1',
'azure/high/1024-x-1536/gpt-image-1-mini',
'gradient_ai/openai-gpt-4o-mini',
'azure/eu/gpt-4o-2024-08-06',
'azure/gpt-5-chat',
'gpt-3.5-turbo',
'lambda_ai/llama-4-scout-17b-16e-instruct',
'deepgram/base-phonecall',
'gemini-2.0-flash-exp',
'snowflake/llama3.1-70b',
'o1-preview-2024-09-12',
'cohere.embed-english-v3',
'xai/grok-beta',
'azure/high/1536-x-1024/gpt-image-1-mini',
'openrouter/openai/gpt-4.1-nano-2025-04-14',
'dashscope/qwen-coder',
'azure/gpt-3.5-turbo-0125',
'vercel_ai_gateway/inception/mercury-coder-small',
'gemini/gemini-gemma-2-27b-it',
'lambda_ai/hermes3-70b',
'gemini/gemini-1.5-flash-002',
'dashscope/qwen-plus-2025-04-28',
'deepinfra/zai-org/GLM-4.5',
'meta-llama/Llama-2-70b-chat-hf',
'watsonx/mistralai/mistral-medium-2505',
'groq/gemma2-9b-it',
'azure/eu/gpt-4o-2024-11-20',
'luminous-supreme-control',
'perplexity/sonar-deep-research',
'gemini/gemini-2.5-flash-image',
'vercel_ai_gateway/anthropic/claude-3-haiku',
'davinci-002',
'lambda_ai/llama-4-maverick-17b-128e-instruct-fp8',
'gemini/gemini-gemma-2-9b-it',
'o3-pro',
'azure/o3-deep-research',
'deepgram/enhanced-phonecall',
'vercel_ai_gateway/anthropic/claude-4-opus',
'gemini/gemini-flash-lite-latest',
'azure/text-embedding-3-large',
'deepgram/nova-3-medical',
'gemini-1.5-flash-002',
'text-bison@001',
'azure_ai/grok-4',
'jamba-large-1.6',
'vercel_ai_gateway/perplexity/sonar-reasoning-pro',
'meta/codellama-13b:1c914d844307b0588599b8393480a3ba917b660c7e9dfae681542b5325f228db',
'gpt-4o-realtime-preview-2025-06-03',
'replit/replit-code-v1-3b:b84f4c074b807211cd75e3e8b1589b6399052125b4c27106e43d47189e8415ad',
'azure/o1',
'gemini/imagen-3.0-generate-002',
'azure/gpt-3.5-turbo',
'o1-preview',
'azure/high/1536-x-1024/gpt-image-1',
'vercel_ai_gateway/perplexity/sonar-reasoning',
'watsonx/mistralai/pixtral-12b-2409',
'vercel_ai_gateway/cohere/embed-v4.0',
'azure_ai/Llama-4-Maverick-17B-128E-Instruct-FP8',
'recraft/recraftv2',
'vercel_ai_gateway/mistral/ministral-3b',
'azure/standard/1024-x-1024/dall-e-2',
'azure/gpt-4-0613',
'gpt-4o-mini-tts',
'openrouter/openai/gpt-4.1-nano',
'openrouter/gryphe/mythomax-l2-13b',
'vercel_ai_gateway/alibaba/qwen3-coder',
'meta-llama/Llama-2-70b',
'deepinfra/meta-llama/Llama-Guard-4-12B',
'azure_ai/grok-3',
'stability.sd3-large-v1:0',
'azure/gpt-4o-mini',
'vercel_ai_gateway/anthropic/claude-3.7-sonnet',
'deepinfra/deepseek-ai/DeepSeek-R1-Distill-Qwen-32B',
'azure/high/1024-x-1024/gpt-image-1-mini',
'deepseek-reasoner',
'stability.stable-image-core-v1:1',
'text-completion-codestral/codestral-latest',
'azure_ai/global/grok-3',
'vercel_ai_gateway/mistral/pixtral-large',
'gpt-4o-search-preview',
'ft:babbage-002',
'llama2',
'deepinfra/Sao10K/L3-8B-Lunaris-v1-Turbo',
'openrouter/fireworks/firellava-13b',
'o3-mini',
'azure_ai/Llama-4-Scout-17B-16E-Instruct',
'gradient_ai/alibaba-qwen3-32b',
'deepgram/base-conversationalai',
'groq/qwen/qwen3-32b',
'azure/us/gpt-4o-mini-realtime-preview-2024-12-17',
'gemini/gemini-1.5-pro-latest',
'watsonx/ibm/granite-guardian-3-2-2b',
'meta-llama/Llama-3.1-8B-Instruct',
'azure/o1-preview',
'azure/gpt-3.5-turbo-instruct-0914',
'oci/cohere.command-a-03-2025',
'deepseek/deepseek-coder',
'gpt-3.5-turbo-16k-0613',
'moonshotai/Kimi-K2-Instruct',
'wandb/microsoft/Phi-4-mini-instruct',
'groq/whisper-large-v3-turbo',
'lambda_ai/llama3.1-nemotron-70b-instruct-fp8',
'claude-opus-4-20250514',
'gemini/imagen-4.0-fast-generate-001',
'gemini/gemini-2.0-flash-thinking-exp-01-21',
'us.meta.llama3-1-405b-instruct-v1:0',
'meta-llama/Llama-2-7b-chat',
'watsonx/mistralai/mistral-large',
'azure/computer-use-preview',
'wandb/meta-llama/Llama-3.3-70B-Instruct',
'cloudflare/@cf/meta/llama-2-7b-chat-int8',
'groq/meta-llama/llama-4-maverick-17b-128e-instruct',
'vercel_ai_gateway/meta/llama-3.1-8b',
'nscale/meta-llama/Llama-4-Scout-17B-16E-Instruct',
'azure/gpt-35-turbo-16k-0613',
'openrouter/qwen/qwen-2.5-coder-32b-instruct',
'openrouter/openai/o1-mini',
'gpt-4o-realtime-preview-2024-10-01',
'clarifai/qwen.qwen3.qwen3-next-80B-A3B-Thinking',
'gemini-1.5-pro-preview-0215',
'deepinfra/Qwen/Qwen2.5-72B-Instruct',
'groq/llama-3.1-405b-reasoning',
'ovhcloud/Qwen2.5-VL-72B-Instruct',
'512-x-512/max-steps/stability.stable-diffusion-xl-v0',
'gpt-3.5-turbo-0613',
'azure/global-standard/gpt-4o-2024-08-06',
'gpt-4o-2024-05-13',
'babbage-002',
'openrouter/databricks/dbrx-instruct',
'text-bison32k',
'command-light',
'meta.llama3-2-90b-instruct-v1:0',
'vercel_ai_gateway/deepseek/deepseek-r1-distill-llama-70b',
'gpt-3.5-turbo-16k',
'gemini-pro',
'vercel_ai_gateway/meta/llama-3.2-90b',
'claude-3-opus-latest',
'vercel_ai_gateway/vercel/v0-1.5-md',
'vercel_ai_gateway/meta/llama-3-8b',
'deepgram/base-finance',
'azure/gpt-5-nano',
'lambda_ai/lfm-7b',
'medium/1536-x-1024/gpt-image-1',
'gpt-4-32k-0613',
'gemini-live-2.5-flash-preview-native-audio-09-2025',
'azure_ai/Phi-4-mini-reasoning',
'clarifai/openai.chat-completion.gpt-4o',
'sambanova/Meta-Llama-3.3-70B-Instruct',
'gemini/gemini-2.0-pro-exp-02-05',
'deepinfra/meta-llama/Llama-3.3-70B-Instruct',
'deepgram/nova-2-phonecall',
'o4-mini',
'gemini-2.5-pro',
'azure/global-standard/gpt-4o-2024-11-20',
'gemini/veo-3.0-fast-generate-preview',
'gemini-2.0-flash-live-preview-04-09',
'vercel_ai_gateway/mistral/codestral',
'command',
'gemini-2.5-flash-lite-preview-06-17',
'gpt-5-mini-2025-08-07',
'vercel_ai_gateway/openai/gpt-3.5-turbo',
'vercel_ai_gateway/openai/gpt-4o-mini',
'azure/medium/1024-x-1536/gpt-image-1-mini',
'openrouter/mistralai/mistral-small-3.1-24b-instruct',
'watsonx/ibm/granite-guardian-3-3-8b',
'gemini/imagen-3.0-fast-generate-001',
'vercel_ai_gateway/meta/llama-3.2-11b',
'voyage/voyage-2',
'o1-mini-2024-09-12',
'gemini/gemini-2.5-flash-image-preview',
'vercel_ai_gateway/mistral/mistral-saba-24b',
'embed-english-light-v2.0',
'gradient_ai/anthropic-claude-3-opus',
'gemini/gemini-2.0-flash-001',
'doubao-embedding-large',
'j2-ultra',
'databricks/databricks-claude-3-7-sonnet',
'watsonx/google/flan-t5-xl-3b',
'clarifai/xai.image-generation.grok-2-image-1212',
'openrouter/meta-llama/llama-2-70b-chat',
'wandb/Qwen/Qwen3-Coder-480B-A35B-Instruct',
'apac.anthropic.claude-3-haiku-20240307-v1:0',
'clarifai/xai.chat-completion.grok-2-1212',
'deepgram/whisper-large',
'azure/global/gpt-4o-2024-08-06',
'meta-llama/Llama-2-70b-hf',
'moonshot/moonshot-v1-128k-vision-preview',
'azure/gpt-4.1-mini',
'nscale/mistralai/mixtral-8x22b-instruct-v0.1',
'snowflake/jamba-1.5-large',
'gemini/gemini-2.5-flash-preview-tts',
'clarifai/anthropic.completion.claude-3_7-sonnet',
'openrouter/bytedance/ui-tars-1.5-7b',
'lambda_ai/qwen3-32b-fp8',
'azure/us/gpt-4o-realtime-preview-2024-10-01',
'oci/cohere.command-latest',
'perplexity/llama-3.1-70b-instruct',
'openrouter/mistralai/mistral-small-3.2-24b-instruct',
'nscale/Qwen/Qwen2.5-Coder-3B-Instruct',
'gpt-4-1106-vision-preview',
'o4-mini-deep-research-2025-06-26',
'meta.llama2-70b-chat-v1',
'openrouter/meta-llama/llama-3-70b-instruct',
'gemini/imagen-4.0-generate-001',
'openrouter/x-ai/grok-4-fast:free',
'apac.anthropic.claude-haiku-4-5-20251001-v1:0',
'anthropic.claude-3-5-sonnet-20241022-v2:0',
'morph/morph-v3-large',
'openrouter/google/gemini-2.5-pro',
'openrouter/mistralai/mixtral-8x22b-instruct',
'openai/sora-2-pro',
'gemini-2.5-flash-preview-09-2025',
'claude-3-opus-20240229',
'gemini/gemini-2.5-flash-lite-preview-06-17',
'deepgram/nova-2-conversationalai',
'dashscope/qwq-plus',
'vercel_ai_gateway/openai/o3',
'watsonx/ibm/granite-13b-instruct-v2',
'deepinfra/Qwen/Qwen3-32B',
'azure_ai/global/grok-3-mini',
'embed-multilingual-v2.0',
'ai21.jamba-1-5-mini-v1:0',
'azure_ai/ministral-3b',
'gpt-4o-mini-realtime-preview',
'lambda_ai/qwen25-coder-32b-instruct',
'snowflake/llama2-70b-chat',
'groq/llama-3.1-70b-versatile',
'xai/grok-3-mini-latest',
'snowflake/jamba-1.5-mini',
'deepgram/nova-3',
'deepinfra/meta-llama/Meta-Llama-3.1-70B-Instruct',
'gpt-3.5-turbo-instruct-0914',
'cohere/embed-v4.0',
'vercel_ai_gateway/alibaba/qwen-3-14b',
'lemonade/Qwen3-4B-Instruct-2507-GGUF',
'vercel_ai_gateway/openai/gpt-3.5-turbo-instruct',
'friendliai/meta-llama-3.1-70b-instruct',
'deepgram/nova-2-drivethru',
'anthropic.claude-3-haiku-20240307-v1:0',
'low/1536-x-1024/gpt-image-1-mini',
'claude-4-sonnet-20250514',
'azure_ai/mistral-large-2407',
'databricks/databricks-bge-large-en',
'deepinfra/nvidia/Llama-3.1-Nemotron-70B-Instruct',
'deepinfra/deepseek-ai/DeepSeek-R1-Distill-Llama-70B',
'deepseek-chat',
'high/1536-x-1024/gpt-image-1',
'amazon.titan-embed-image-v1',
'azure/sora-2',
'voyage/voyage-multimodal-3',
'embed-multilingual-v3.0',
'gemini/gemini-pro',
'j2-light',
'openrouter/openai/gpt-4.1-mini-2025-04-14',
'deepgram/nova-2-voicemail',
'vercel_ai_gateway/cohere/command-a',
'meta-llama/Llama-2-13b',
'moonshot/moonshot-v1-32k-0430',
'stability.sd3-5-large-v1:0',
'azure/gpt-4-1106-preview',
'clarifai/openai.chat-completion.gpt-oss-120b',
'fal_ai/bria/text-to-image/3.2',
'o1-2024-12-17',
'xai/grok-2-vision-1212',
'text-embedding-ada-002',
'recraft/recraftv3',
'groq/moonshotai/kimi-k2-instruct',
'gemini-2.5-flash-preview-05-20',
'azure_ai/doc-intelligence/prebuilt-layout',
'openrouter/anthropic/claude-3.5-sonnet',
'vercel_ai_gateway/mistral/ministral-8b',
'togethercomputer/llama-2-70b',
'groq/moonshotai/kimi-k2-instruct-0905',
'azure/gpt-image-1',
'gradient_ai/anthropic-claude-3.5-sonnet',
'snowflake/reka-core',
'azure/medium/1536-x-1024/gpt-image-1',
'azure/medium/1536-x-1024/gpt-image-1-mini',
'dashscope/qwen3-30b-a3b',
'ovhcloud/mamba-codestral-7B-v0.1',
'azure_ai/mistral-small',
'gemini/veo-3.0-generate-preview',
'text-bison@002',
'vercel_ai_gateway/google/text-embedding-005',
'deepseek-ai/DeepSeek-R1-0528',
'gradient_ai/anthropic-claude-3.7-sonnet',
'perplexity/llama-3.1-sonar-large-128k-online',
'xai/grok-2-vision',
'gpt-4o-audio-preview',
'gpt-4o-transcribe',
'elevenlabs/scribe_v1',
'snowflake/mixtral-8x7b',
'cloudflare/@cf/mistral/mistral-7b-instruct-v0.1',
'us.twelvelabs.marengo-embed-2-7-v1:0',
'text-unicorn',
'openrouter/openai/o3-mini-high',
'snowflake/llama3.2-1b',
'azure/gpt-4',
'azure/hd/1024-x-1792/dall-e-3',
'snowflake/llama3.3-70b',
'moonshot/moonshot-v1-32k-vision-preview',
'text-bison32k@002',
'openrouter/anthropic/claude-3-haiku-20240307',
'snowflake/claude-3-5-sonnet',
'deepgram/base-meeting',
'fal_ai/fal-ai/recraft/v3/text-to-image',
'gpt-4.1',
'deepseek/deepseek-r1',
'gpt-4.1-2025-04-14',
'text-moderation-latest',
'anyscale/meta-llama/Llama-2-13b-chat-hf',
'lemonade/Gemma-3-4b-it-GGUF',
'deepinfra/microsoft/WizardLM-2-8x22B',
'vercel_ai_gateway/amazon/nova-lite',
'oci/xai.grok-3-mini-fast',
'groq/openai/gpt-oss-120b',
'us.anthropic.claude-3-haiku-20240307-v1:0',
'amazon.titan-text-lite-v1',
'togethercomputer/Llama-2-7B-32K-Instruct',
'wandb/meta-llama/Llama-3.1-8B-Instruct',
'deepinfra/Qwen/Qwen3-14B',
'cerebras/qwen-3-32b',
'ft:davinci-002',
'openai/sora-2',
'gemini/gemini-2.0-flash',
'azure/us/o3-mini-2025-01-31',
'azure/low/1536-x-1024/gpt-image-1',
'gemini-1.5-pro',
'cohere.command-r-v1:0',
'perplexity/sonar-medium-online',
'gemini-2.5-pro-exp-03-25',
'medium/1024-x-1024/gpt-image-1',
'snowflake/llama3.2-3b',
'oci/meta.llama-3.2-90b-vision-instruct',
'heroku/claude-3-7-sonnet',
'groq/llama-3.2-3b-preview',
'azure_ai/FLUX-1.1-pro',
'azure/o1-mini-2024-09-12',
'nvidia_nim/nvidia/llama-3_2-nv-rerankqa-1b-v2',
'eu.meta.llama3-2-3b-instruct-v1:0',
'jamba-1.5',
'meta.llama3-8b-instruct-v1:0',
'nscale/deepseek-ai/DeepSeek-R1-Distill-Qwen-14B',
'vercel_ai_gateway/amazon/nova-pro',
'sambanova/gpt-oss-120b',
'eu.anthropic.claude-3-opus-20240229-v1:0',
'gradient_ai/openai-o3-mini',
'xai/grok-3-beta',
'medium/1536-x-1024/gpt-image-1-mini',
'watsonx/meta-llama/llama-guard-3-11b-vision',
'chat-bison-32k@002',
'nscale/meta-llama/Llama-3.3-70B-Instruct',
'amazon.titan-embed-text-v1',
'azure/gpt-4o',
'xai/grok-2-1212',
'azure/gpt-5-mini-2025-08-07',
'azure_ai/Meta-Llama-3-70B-Instruct',
'azure/low/1024-x-1024/gpt-image-1',
'azure_ai/FLUX.1-Kontext-pro',
'vercel_ai_gateway/moonshotai/kimi-k2',
'clarifai/qwen.qwenLM.QwQ-32B-AWQ',
'claude-3-5-haiku-20241022',
'snowflake/snowflake-arctic',
'vercel_ai_gateway/openai/gpt-4.1-nano',
'azure/global/gpt-4o-2024-11-20',
'gpt-4.5-preview-2025-02-27',
'snowflake/llama3.1-8b',
'openrouter/openai/gpt-5-mini',
'gemini/gemini-1.5-pro-002',
'azure/gpt-5-2025-08-07',
'groq/llama-3.2-90b-vision-preview',
'low/1024-x-1024/gpt-image-1-mini',
'amazon.titan-image-generator-v1',
'deepinfra/Qwen/Qwen3-Coder-480B-A35B-Instruct',
'azure_ai/Meta-Llama-3.1-70B-Instruct',
'databricks/databricks-meta-llama-3-70b-instruct',
'featherless_ai/featherless-ai/Qwerky-72B',
'openrouter/mistralai/mistral-large',
'vercel_ai_gateway/vercel/v0-1.0-md',
'high/1024-x-1536/gpt-image-1',
'vercel_ai_gateway/mistral/mistral-small',
'deepgram/enhanced-meeting',
'clarifai/openbmb.miniCPM.MiniCPM-o-2_6-language',
'togethercomputer/llama-2-7b',
'azure/gpt-4o-mini-audio-preview-2024-12-17',
'assemblyai/nano',
'gpt-4-turbo',
'vercel_ai_gateway/morph/morph-v3-large',
'perplexity/sonar-small-online',
'v0/v0-1.5-md',
'openrouter/mancer/weaver',
'deepinfra/NousResearch/Hermes-3-Llama-3.1-70B',
'openrouter/openai/gpt-4-vision-preview',
'azure_ai/deepseek-v3-0324',
'snowflake/mistral-7b',
'dashscope/qwen-plus-2025-07-28',
'openrouter/anthropic/claude-3.5-sonnet:beta',
'command-a-03-2025',
'clarifai/anthropic.completion.claude-sonnet-4',
'openrouter/anthropic/claude-opus-4',
'vercel_ai_gateway/meta/llama-4-maverick',
'meta_llama/Llama-3.3-70B-Instruct',
'claude-haiku-4-5',
'deepgram/whisper-base',
'openrouter/anthropic/claude-3-opus',
'gemini/gemini-2.0-flash-lite-preview-02-05',
'xai/grok-3-fast-latest',
'azure_ai/Phi-4-multimodal-instruct',
'groq/meta-llama/llama-4-scout-17b-16e-instruct',
'mistral.mixtral-8x7b-instruct-v0:1',
'gemini-2.0-flash-thinking-exp-01-21',
'cerebras/llama-3.3-70b',
'vercel_ai_gateway/xai/grok-4',
'amazon.titan-image-generator-v2',
'azure/o3-pro',
'databricks/databricks-llama-2-70b-chat',
'NousResearch/Nous-Hermes-Llama2-13b',
'text-completion-codestral/codestral-2405',
'moonshot/moonshot-v1-auto',
'gemini/gemma-3-27b-it',
'deepinfra/anthropic/claude-4-opus',
'lambda_ai/lfm-40b',
'gemini/gemini-2.0-flash-thinking-exp',
'gpt-4o-2024-11-20',
'gemini-1.0-pro',
'clarifai/meta.Llama-3.Llama-3_2-3B-Instruct',
'vercel_ai_gateway/google/gemini-embedding-001',
'gpt-5-nano',
'daanelson/flan-t5-large:ce962b3f6792a57074a601d3979db5839697add2e4e02696b3ced4c022d4767f',
'azure_ai/Phi-3-mini-128k-instruct',
'wandb/deepseek-ai/DeepSeek-V3-0324',
'vercel_ai_gateway/mistral/mistral-large',
'openrouter/openai/o1-preview-2024-09-12',
'low/1536-x-1024/gpt-image-1',
'heroku/claude-3-5-sonnet-latest',
'azure/high/1024-x-1024/gpt-image-1',
'anyscale/meta-llama/Llama-2-7b-chat-hf',
'deepinfra/meta-llama/Llama-3.3-70B-Instruct-Turbo',
'gemini/gemini-2.5-flash',
'openrouter/openai/gpt-5-nano',
'chat-bison@001',
'sambanova/Meta-Llama-3.1-8B-Instruct',
'joehoover/instructblip-vicuna13b:c4c54e3c8c97cd50c2d2fec9be3b6065563ccf7d43787fb99f84151b867178fe',
'claude-sonnet-4-5',
'azure/gpt-image-1-mini',
'gpt-3.5-turbo-instruct',
'jamba-1.5-large',
'azure/gpt-4.1-mini-2025-04-14',
'perplexity/sonar-medium-chat',
'vercel_ai_gateway/openai/gpt-4-turbo',
'azure_ai/grok-code-fast-1',
'gemini/gemini-2.0-flash-exp',
'xai/grok-4-fast-non-reasoning',
'azure_ai/grok-4-fast-reasoning',
'nscale/black-forest-labs/FLUX.1-schnell',
'watsonx/meta-llama/llama-3-2-3b-instruct',
'clarifai/openai.chat-completion.o3',
'gemini-1.0-ultra-001',
'xai/grok-3-mini-fast',
'gemini-1.5-flash-preview-0514',
'dashscope/qwen-turbo-2025-04-28',
'azure/o4-mini',
'deepinfra/Qwen/Qwen3-235B-A22B-Instruct-2507',
'oci/xai.grok-3',
'openai/gpt-oss-20b',
'moonshot/kimi-latest',
'gemini/gemini-2.0-flash-live-001',
'gpt-3.5-turbo-0301',
'snowflake/llama3-70b',
'sambanova/DeepSeek-R1-Distill-Llama-70B',
'gpt-4.1-nano-2025-04-14',
'voyage/voyage-finance-2',
'o3-deep-research',
'gpt-4o-audio-preview-2024-10-01',
'openrouter/qwen/qwen3-coder',
'deepinfra/Qwen/Qwen3-235B-A22B-Thinking-2507',
'gradient_ai/anthropic-claude-3.5-haiku',
'claude-3-7-sonnet-latest',
'azure/o3-pro-2025-06-10',
'tts-1',
'deepinfra/mistralai/Mixtral-8x7B-Instruct-v0.1',
'azure/o4-mini-2025-04-16',
'dashscope/qwen3-coder-plus-2025-07-22',
'lambda_ai/llama3.3-70b-instruct-fp8',
'anthropic.claude-3-5-sonnet-20240620-v1:0',
'us.meta.llama3-2-3b-instruct-v1:0',
'deepgram/base-video',
'eu.anthropic.claude-3-5-haiku-20241022-v1:0',
'azure/low/1024-x-1536/gpt-image-1',
'clarifai/deepseek-ai.deepseek-chat.DeepSeek-R1-0528-Qwen3-8B',
'azure_ai/mistral-document-ai-2505',
'groq/llama2-70b-4096',
'azure/o1-mini',
'vercel_ai_gateway/xai/grok-2',
'openrouter/openai/gpt-5-chat',
'deepinfra/google/gemini-2.0-flash-001',
'clarifai/qwen.qwenLM.Qwen3-30B-A3B-Thinking-2507clarifai/openai.chat-completion.gpt-5-nano',
'deepinfra/meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo',
'openrouter/anthropic/claude-3-5-haiku-20241022',
'deepinfra/meta-llama/Llama-3.2-11B-Vision-Instruct',
'cohere.command-text-v14',
'deepinfra/moonshotai/Kimi-K2-Instruct-0905',
'azure/gpt-35-turbo-0301',
'deepinfra/deepseek-ai/DeepSeek-V3.1',
'omni-moderation-latest',
'openrouter/meta-llama/llama-3-70b-instruct:nitro',
'anthropic.claude-3-opus-20240229-v1:0',
'vercel_ai_gateway/openai/text-embedding-3-small',
'palm/text-bison-safety-off',
'watsonx/ibm/granite-ttm-1024-96-r2',
'deepseek-ai/DeepSeek-V3.1',
'eu.anthropic.claude-3-sonnet-20240229-v1:0',
'voyage/rerank-2-lite',
'meta-llama/Llama-2-7b',
'rerank-english-v3.0',
'clarifai/qwen.qwenLM.Qwen3-30B-A3B-Instruct-2507',
'medium/1024-x-1536/gpt-image-1',
'azure/medium/1024-x-1024/gpt-image-1',
'deepinfra/mistralai/Mistral-Small-24B-Instruct-2501',
'gemini-2.5-flash-image',
'gemini/learnlm-1.5-pro-experimental',
'fal_ai/fal-ai/imagen4/preview',
'amazon.titan-embed-text-v2:0',
'xai/grok-3-fast-beta',
'vercel_ai_gateway/alibaba/qwen-3-30b',
'nscale/stabilityai/stable-diffusion-xl-base-1.0',
'ovhcloud/DeepSeek-R1-Distill-Llama-70B',
'clarifai/openai.chat-completion.gpt-5',
'snowflake/llama3-8b',
'azure_ai/Llama-3.2-90B-Vision-Instruct',
'gpt-5-mini',
'azure/gpt-35-turbo',
'azure/gpt-35-turbo-0613',
'31dxrj3',
'eu.anthropic.claude-haiku-4-5-20251001-v1:0',
'anyscale/meta-llama/Meta-Llama-3-70B-Instruct',
'replicate/dolly-v2-12b:ef0e1aefc61f8e096ebe4db6b2bacc297daf2ef6899f0f7e001ec445893500e5',
'deepinfra/deepseek-ai/DeepSeek-R1-0528-Turbo',
'vercel_ai_gateway/google/gemini-2.5-pro',
'gemini-flash-experimental',
'perplexity/llama-3.1-sonar-small-128k-chat',
'watsonx/meta-llama/llama-4-maverick-17b',
'azure/standard/1024-x-1024/dall-e-3',
'o3',
'openrouter/openai/o3-mini',
'cohere.command-light-text-v14',
'vercel_ai_gateway/google/gemma-2-9b',
'vercel_ai_gateway/zai/glm-4.5-air',
'o4-mini-2025-04-16',
'azure/gpt-4.5-preview',
'anthropic.claude-3-7-sonnet-20240620-v1:0',
'stability.stable-image-ultra-v1:1',
'perplexity/pplx-70b-online',
'deepinfra/nvidia/NVIDIA-Nemotron-Nano-9B-v2',
'openrouter/anthropic/claude-instant-v1',
'azure/sora-2-pro',
'azure_ai/mistral-large-latest',
'gpt-4o-mini-2024-07-18',
'azure/low/1024-x-1024/gpt-image-1-mini',
'fal_ai/fal-ai/stable-diffusion-v35-medium',
'azure/gpt-5-codex',
'dashscope/qwen-flash-2025-07-28',
'gemini-2.5-flash-lite',
'nvidia_nim/nvidia/nv-rerankqa-mistral-4b-v3',
'omni-moderation-latest-intents',
'snowflake/jamba-instruct',
'openrouter/qwen/qwen-vl-plus',
'azure/tts-1',
'deepinfra/openai/gpt-oss-120b',
'512-x-512/50-steps/stability.stable-diffusion-xl-v0',
'nscale/deepseek-ai/DeepSeek-R1-Distill-Llama-8B',
'wandb/meta-llama/Llama-4-Scout-17B-16E-Instruct',
'vercel_ai_gateway/zai/glm-4.5',
'deepinfra/google/gemini-2.5-flash',
'xai/grok-code-fast',
'deepgram/enhanced',
'perplexity/sonar-small-chat',
'clarifai/xai.chat-completion.grok-3',
'vercel_ai_gateway/google/gemini-2.0-flash-lite',
'deepinfra/Qwen/Qwen3-Coder-480B-A35B-Instruct-Turbo',
'sambanova/Llama-4-Scout-17B-16E-Instruct',
'cerebras/openai/gpt-oss-120b',
'dashscope/qwen-turbo',
'codestral/codestral-latest',
'vercel_ai_gateway/xai/grok-2-vision',
'clarifai/qwen.qwenCoder.Qwen3-Coder-30B-A3B-Instruct',
'deepinfra/google/gemma-3-4b-it',
'deepgram/nova-2-general',
'ovhcloud/Mistral-Nemo-Instruct-2407',
'gemini/gemini-1.5-flash-exp-0827',
'openrouter/google/gemini-2.5-flash',
'azure/gpt-4o-2024-11-20',
'watsonx/mistralai/mistral-small-2503',
'vercel_ai_gateway/mistral/magistral-medium',
'clarifai/xai.chat-completion.grok-code-fast-1',
'rerank-multilingual-v3.0',
'sambanova/Meta-Llama-Guard-3-8B',
'rerank-multilingual-v2.0',
'dashscope/qwen3-coder-plus',
'oci/meta.llama-3.3-70b-instruct',
'deepseek-ai/DeepSeek-V3-0324',
'azure/us/gpt-4o-2024-08-06',
'meta-llama/Llama-2-7b-hf',
'dashscope/qwen-turbo-2024-11-01',
'clarifai/openbmb.miniCPM.MiniCPM4-8B',
'deepinfra/deepseek-ai/DeepSeek-V3.1-Terminus',
'gpt-4o',
'anyscale/mistralai/Mixtral-8x7B-Instruct-v0.1',
'vercel_ai_gateway/alibaba/qwen-3-32b',
'hd/1024-x-1024/dall-e-3',
'gemini/gemini-2.0-flash-preview-image-generation',
'azure/gpt-5',
'groq/llama-3.2-90b-text-preview',
'azure/us/o1-preview-2024-09-12',
'openrouter/google/gemini-2.0-flash-001',
'codex-mini-latest',
'clarifai/gcp.generate.gemini-2_5-pro',
'azure/codex-mini',
'command-r-plus',
'Qwen/Qwen3-235B-A22B-Instruct-2507',
'gemini/veo-2.0-generate-001',
'gemini-2.5-flash-image-preview',
'openrouter/switchpoint/router',
'gemini-2.5-pro-preview-03-25',
'gemini-2.5-pro-preview-05-06',
'azure/gpt-35-turbo-instruct',
'azure/gpt-4o-mini-tts',
'gradient_ai/mistral-nemo-instruct-2407',
'dashscope/qwen3-coder-flash-2025-07-28',
'text-embedding-3-small',
'vercel_ai_gateway/anthropic/claude-3.5-sonnet',
'anyscale/meta-llama/Llama-2-70b-chat-hf',
'moonshot/kimi-k2-0711-preview',
'omni-moderation-2024-09-26',
'azure/eu/o1-mini-2024-09-12',
'vercel_ai_gateway/xai/grok-3-fast',
'jamba-1.5-mini@001',
'hd/1024-x-1792/dall-e-3',
'openrouter/jondurbin/airoboros-l2-70b-2.1',
'openrouter/openai/o1-mini-2024-09-12',
'perplexity/llama-3.1-sonar-small-128k-online',
'deepinfra/Qwen/Qwen3-235B-A22B',
'gemini-2.0-flash-001',
'openrouter/openai/gpt-3.5-turbo',
'deepinfra/meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo',
'xai/grok-code-fast-1-0825',
'voyage/voyage-lite-01',
'gpt-4.5-preview',
'oci/meta.llama-4-maverick-17b-128e-instruct-fp8',
'groq/llama-guard-3-8b',
'gemini/gemini-2.5-pro-exp-03-25',
'xai/grok-code-fast-1',
'gemini/gemini-2.5-flash-preview-05-20',
'snowflake/llama3.1-405b',
'hd/1792-x-1024/dall-e-3',
'vercel_ai_gateway/openai/gpt-4.1-mini',
'twelvelabs.pegasus-1-2-v1:0',
'voyage/voyage-large-2',
'us.meta.llama3-2-11b-instruct-v1:0',
'groq/whisper-large-v3',
'gpt-5-chat',
'gpt-4o-audio-preview-2025-06-03',
'perplexity/codellama-34b-instruct',
'deepinfra/deepseek-ai/DeepSeek-R1-Turbo',
'gemini-2.0-flash-preview-image-generation',
'gpt-image-1',
'luminous-extended',
'watsonx/bigscience/mt0-xxl-13b',
'vercel_ai_gateway/xai/grok-3',
'claude-opus-4-1-20250805',
'azure/gpt-4-32k-0613',
'ovhcloud/gpt-oss-120b',
'cohere.embed-v4:0',
'palm/chat-bison',
'command-nightly',
'rerank-v3.5',
'low/1024-x-1536/gpt-image-1-mini',
'o3-deep-research-2025-06-26',
'xai/grok-3-mini',
'gemini/gemini-1.5-flash-001',
'text-embedding-ada-002-v2',
'medium/1024-x-1536/gpt-image-1-mini',
'cohere.command-r-plus-v1:0',
'friendliai/meta-llama-3.1-8b-instruct',
'gemini/gemini-1.5-pro-exp-0801',
'azure/eu/gpt-4o-realtime-preview-2024-10-01',
'azure_ai/Phi-4-reasoning',
'eu.twelvelabs.pegasus-1-2-v1:0',
'lambda_ai/llama3.1-405b-instruct-fp8',
'meta.llama3-2-1b-instruct-v1:0',
'openrouter/openai/gpt-3.5-turbo-16k',
'azure/gpt-4o-realtime-preview-2024-10-01',
'embed-english-v3.0',
'groq/mixtral-8x7b-32768',
'anthropic.claude-instant-v1',
'openrouter/deepseek/deepseek-r1',
'vercel_ai_gateway/perplexity/sonar',
'medlm-medium',
'anthropic.claude-v2:1',
'ovhcloud/Qwen2.5-Coder-32B-Instruct',
'azure_ai/Phi-3-medium-128k-instruct',
'databricks/databricks-mpt-7b-instruct',
'gpt-4o-mini',
'gpt-5-pro-2025-10-06',
'openrouter/openai/gpt-oss-20b',
'azure/mistral-large-latest',
'doubao-embedding-text-240715',
'claude-3-haiku-20240307',
'watsonx/ibm/granite-vision-3-2-2b',
'nscale/meta-llama/Llama-3.1-8B-Instruct',
'moonshot/kimi-thinking-preview',
'gemini/imagen-3.0-generate-001',
'perplexity/search',
'azure_ai/mistral-large',
'azure/eu/o3-mini-2025-01-31',
'groq/playai-tts',
'meta.llama3-2-11b-instruct-v1:0',
'stability.stable-image-ultra-v1:0',
'oci/meta.llama-4-scout-17b-16e-instruct',
'wandb/Qwen/Qwen3-235B-A22B-Instruct-2507',
'elevenlabs/scribe_v1_experimental',
'openai/container',
'deepgram/base-general',
'moonshot/moonshot-v1-8k-0430',
'gpt-4o-search-preview-2025-03-11',
'gpt-realtime-2025-08-28',
'jamba-mini-1.6',
'us.meta.llama3-2-90b-instruct-v1:0',
'azure_ai/Llama-3.3-70B-Instruct',
'databricks/databricks-llama-4-maverick',
'us.anthropic.claude-3-5-sonnet-20241022-v2:0',
'anthropic.claude-3-5-haiku-20241022-v1:0',
'chat-bison',
'clarifai/microsoft.text-generation.Phi-4-reasoning-plus',
'command-r-08-2024',
'ovhcloud/llava-v1.6-mistral-7b-hf',
'max-x-max/50-steps/stability.stable-diffusion-xl-v0',
'us.meta.llama3-1-8b-instruct-v1:0',
'gemini/gemini-2.5-flash-lite',
'openrouter/anthropic/claude-sonnet-4',
'lambda_ai/deepseek-r1-671b',
'gpt-4o-mini-search-preview-2025-03-11',
'azure_ai/deepseek-r1',
'gemini/gemini-1.5-flash',
'openrouter/anthropic/claude-haiku-4.5',
'vercel_ai_gateway/meta/llama-3-70b',
'deepinfra/meta-llama/Meta-Llama-3-8B-Instruct',
'o1-pro',
'ai21.jamba-instruct-v1:0',
'snowflake/snowflake-llama-3.3-70b',
'a16z-infra/llama-2-13b-chat:2a7f981751ec7fdf87b5b91ad4db53683a98082e9ff7bfd12c8cd5ea85980a52',
'palm/text-bison-001',
'gpt-4o-mini-audio-preview-2024-12-17',
'deepgram/whisper-small',
'anyscale/mistralai/Mistral-7B-Instruct-v0.1',
'deepgram/enhanced-finance',
'gemini-1.5-pro-preview-0514',
'openrouter/deepseek/deepseek-chat-v3.1',
'q841o8w',
'max-x-max/max-steps/stability.stable-diffusion-xl-v0',
'groq/mistral-saba-24b',
'gemini/veo-3.1-generate-preview',
'azure_ai/Phi-3.5-mini-instruct',
'vercel_ai_gateway/google/gemini-2.5-flash',
'deepinfra/deepseek-ai/DeepSeek-V3',
'ai21.jamba-1-5-large-v1:0',
'gpt-5-2025-08-07',
'azure/global-standard/gpt-4o-mini',
'azure_ai/doc-intelligence/prebuilt-document',
'vercel_ai_gateway/openai/gpt-4.1',
'gemini/gemini-2.5-pro',
'deepinfra/Qwen/Qwen3-Next-80B-A3B-Thinking',
'gpt-3.5-turbo-1106',
'azure/gpt-5-chat-latest',
'dashscope/qwen-plus-2025-01-25',
'gemini-1.5-pro-002',
'deepinfra/meta-llama/Llama-3.2-3B-Instruct',
'azure_ai/MAI-DS-R1',
'azure_ai/grok-4-fast-non-reasoning',
'azure/gpt-4.1-2025-04-14',
'deepgram/nova-2-video',
'embed-english-light-v3.0',
'meta.llama2-13b-chat-v1',
'standard/1024-x-1792/dall-e-3',
'lambda_ai/hermes3-8b',
'whisper-1',
'watsonx/ibm/granite-ttm-1536-96-r2',
'watsonx/ibm/granite-3-3-8b-instruct',
'us.anthropic.claude-3-opus-20240229-v1:0',
'gpt-image-1-mini',
'jamba-1.5-mini',
'vercel_ai_gateway/meta/llama-3.1-70b',
'anthropic.claude-v1',
'watsonx/meta-llama/llama-3-2-90b-vision-instruct',
'xai/grok-2-latest',
'openrouter/openai/o1-preview',
'gemini/gemini-2.5-pro-preview-03-25',
'gpt-4o-realtime-preview-2024-12-17',
'gemini/gemini-1.5-pro',
'vercel_ai_gateway/morph/morph-v3-fast',
'jamba-mini-1.7',
'lemonade/gpt-oss-120b-mxfp-GGUF',
'azure/gpt-5-nano-2025-08-07',
'amazon.titan-text-premier-v1:0',
'azure/eu/gpt-4o-realtime-preview-2024-12-17',
'gemini/gemini-1.5-pro-001',
'vercel_ai_gateway/mistral/mixtral-8x22b-instruct',
'gemini-1.5-pro-preview-0409',
'perplexity/pplx-7b-online',
'meta_llama/Llama-3.3-8B-Instruct',
'gpt-4',
'nscale/Qwen/Qwen2.5-Coder-7B-Instruct',
'ovhcloud/Meta-Llama-3_3-70B-Instruct',
'azure_ai/grok-3-mini',
'gpt-realtime-mini',
'lambda_ai/hermes3-405b',
'watsonx/mistralai/mistral-small-3-1-24b-instruct-2503',
'claude-3-5-haiku-latest',
'dashscope/qwen3-coder-flash',
'gpt-4o-realtime-preview',
'WizardLM/WizardCoder-15B-V1.0',
'us.meta.llama3-2-1b-instruct-v1:0',
'openrouter/pygmalionai/mythalion-13b',
'us.anthropic.claude-3-5-haiku-20241022-v1:0',
'vercel_ai_gateway/meta/llama-3.3-70b',
'snowflake/gemma-7b',
'assemblyai/best',
'cohere.embed-multilingual-v3',
'togethercomputer/LLaMA-2-7B-32K',
'azure/gpt-4o-2024-08-06',
'openrouter/meta-llama/codellama-34b-instruct',
'deepgram/nova-2-automotive',
'azure_ai/mistral-medium-2505',
'gemini-1.5-flash-001',
'clarifai/qwen.qwen-VL.Qwen2_5-VL-7B-Instruct',
'NumbersStation/nsql-llama-2-7B',
'voyage/voyage-law-2',
'azure/gpt-4.1-nano',
'rerank-english-v2.0',
'vercel_ai_gateway/cohere/command-r-plus',
'azure_ai/Llama-3.2-11B-Vision-Instruct',
'azure/gpt-35-turbo-instruct-0914',
'WizardLM/WizardCoder-Python-34B-V1.0',
'deepgram/nova-general',
'azure_ai/Phi-3-medium-4k-instruct',
'perplexity/pplx-7b-chat',
'databricks/databricks-meta-llama-3-3-70b-instruct',
'azure_ai/deepseek-v3',
'azure/sora-2-pro-high-res',
'vercel_ai_gateway/openai/gpt-4o',
'microsoft/Phi-4-mini-instruct',
'openrouter/openai/gpt-5',
'embed-english-v2.0',
'sambanova/DeepSeek-V3.1',
'replicate/llama-2-70b-chat:2796ee9483c3fd7aa2e171d38f4ca12251a30609463dcfd4cd76703f22e96cdf',
'deepseek/deepseek-reasoner',
'azure/gpt-4o-audio-preview-2024-12-17',
'1024-x-1024/50-steps/stability.stable-diffusion-xl-v1',
'gemini-2.5-pro-preview-06-05',
'xai/grok-2',
'sambanova/Qwen2-Audio-7B-Instruct',
'openrouter/anthropic/claude-3-haiku',
'openrouter/anthropic/claude-opus-4.1',
...]rspn = chat('I want to test my tools. Can you use the tools to solve 42427928*548749+547892?', max_steps=5)
rspn
The answer is 23282283609964.
To break it down:
- 42427928 × 548749 = 23282283062072
- 23282283062072 + 547892 = 23282283609964
- id:
chatcmpl-d1184afb-8235-47c9-a7fb-a6d0c4cd3b6b - model:
claude-haiku-4-5-20251001 - finish_reason:
stop - usage:
Usage(completion_tokens=60, prompt_tokens=898, total_tokens=958, completion_tokens_details=None, prompt_tokens_details=PromptTokensDetailsWrapper(audio_tokens=None, cached_tokens=0, text_tokens=None, image_tokens=None, cache_creation_tokens=0, cache_creation_token_details=CacheCreationTokenDetails(ephemeral_5m_input_tokens=0, ephemeral_1h_input_tokens=0)), cache_creation_input_tokens=0, cache_read_input_tokens=0)
[{'role': 'user',
'content': 'I want to test my tools. Can you use the tools to solve 42427928*548749+547892?'},
Message(content="I'll solve this step by step. First, I'll multiply 42427928 * 548749, then add 547892 to the result.", role='assistant', tool_calls=[{'index': 1, 'function': {'arguments': '{"a": 42427928, "b": 548749}', 'name': 'multer'}, 'id': 'toolu_01Av51KZRqtPyLXHfAsW7ZTz', 'type': 'function'}], function_call=None, provider_specific_fields={'citations': None, 'thinking_blocks': None}),
{'tool_call_id': 'toolu_01Av51KZRqtPyLXHfAsW7ZTz',
'role': 'tool',
'name': 'multer',
'content': '23282283062072'},
Message(content="Now I'll add 547892 to that result:", role='assistant', tool_calls=[{'index': 1, 'function': {'arguments': '{"a": 23282283062072, "b": 547892}', 'name': 'adder'}, 'id': 'toolu_01DC9Am1gv83zNJFy6ymAtBq', 'type': 'function'}], function_call=None, provider_specific_fields={'citations': None, 'thinking_blocks': None}),
{'tool_call_id': 'toolu_01DC9Am1gv83zNJFy6ymAtBq',
'role': 'tool',
'name': 'adder',
'content': '23282283609964'},
Message(content='The answer is **23282283609964**.\n\nTo break it down:\n- 42427928 × 548749 = 23282283062072\n- 23282283062072 + 547892 = **23282283609964**', role='assistant', tool_calls=None, function_call=None, provider_specific_fields={'citations': None, 'thinking_blocks': None})][Choices(finish_reason='stop', index=0, message=Message(content='The answer is **23282283609964**.\n\nTo break it down:\n- 42427928 × 548749 = 23282283062072\n- 23282283062072 + 547892 = **23282283609964**', role='assistant', tool_calls=None, function_call=None, provider_specific_fields={'citations': None, 'thinking_blocks': None}))]Hello Steven! 👋
Nice to meet you! I'm Claude, an AI assistant made by Anthropic. How can I help you today?
- id:
chatcmpl-4a5b0732-4103-4db9-bac3-13f64d2ed02f - model:
claude-haiku-4-5-20251001 - finish_reason:
stop - usage:
Usage(completion_tokens=36, prompt_tokens=13, total_tokens=49, completion_tokens_details=None, prompt_tokens_details=PromptTokensDetailsWrapper(audio_tokens=None, cached_tokens=0, text_tokens=None, image_tokens=None, cache_creation_tokens=0, cache_creation_token_details=CacheCreationTokenDetails(ephemeral_5m_input_tokens=0, ephemeral_1h_input_tokens=0)), cache_creation_input_tokens=0, cache_read_input_tokens=0)
Great question! Let me show you the message format that LiteLLM (and lisette) uses for chat conversations.
The basic message format is a list of dictionaries, where each message has a role and content:
messages = [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello!"},
{"role": "assistant", "content": "Hi! How can I help you?"},
{"role": "user", "content": "What's 2+2?"}
]
Yes, there is a system role! The main roles are:
system- Sets the behavior/context for the assistant (optional but useful)user- Messages from the userassistant- Messages from the AI assistanttool- Tool/function call results (used in tool calling workflows)
In the lisette Chat class, you can set the system prompt with the sp parameter:
chat = Chat(mdl, sp="You are a helpful math tutor", tools=[adder, multer])
Looking at your code history, you can see in chat.hist how messages alternate between user and assistant roles, with tool messages appearing when functions are called!
Would you like to see how the system prompt affects the conversation behavior?
class Bot:
def __init__(self, mdl, tools=[]):
self.mdl=mdl
self.tools=tools
self.hist=[]
def __call__(self, user_msg):
self.hist.append({"role": "user", "content": user_msg})
response = completion(model='claude-haiku-4-5', messages=self.hist)
#self.hist.append(msgs = [{"role": "assistant", "content": response}])
print(response)
bot=Bot(mdl)
bot('Hi, my name is Steven')
ModelResponse(id='chatcmpl-c6d4f0f3-9572-44d9-803c-e521eb14c202', created=1762909021, model='claude-haiku-4-5-20251001', object='chat.completion', system_fingerprint=None, choices=[Choices(finish_reason='stop', index=0, message=Message(content='Hi Steven! Nice to meet you. How can I help you today?', role='assistant', tool_calls=None, function_call=None, provider_specific_fields={'citations': None, 'thinking_blocks': None}))], usage=Usage(completion_tokens=18, prompt_tokens=13, total_tokens=31, completion_tokens_details=None, prompt_tokens_details=PromptTokensDetailsWrapper(audio_tokens=None, cached_tokens=0, text_tokens=None, image_tokens=None, cache_creation_tokens=0, cache_creation_token_details=CacheCreationTokenDetails(ephemeral_5m_input_tokens=0, ephemeral_1h_input_tokens=0)), cache_creation_input_tokens=0, cache_read_input_tokens=0))
class Bot:
def __init__(self, mdl, sys_msg="You are a helpful assistant."):
self.mdl=mdl
self.hist=[{"role": "system", "content": sys_msg}]
def __call__(self, user_msg, tools=[], max_steps=5):
self.max_steps=max_steps
self.tools=tools
self.hist.append({"role": "user", "content": user_msg})
response = completion(model='claude-haiku-4-5', messages=self.hist)
self.hist.append({"role": "assistant", "content": response})
bot=Bot(mdl, "You are a sassy Pirate")
bot('Hi, my name is Steven')
[{'role': 'system', 'content': 'You are a sassy Pirate'},
{'role': 'user', 'content': 'Hi, my name is Steven'},
{'role': 'assistant',
'content': ModelResponse(id='chatcmpl-54621f20-64b8-450d-935c-e56c2ec61773', created=1762909695, model='claude-haiku-4-5-20251001', object='chat.completion', system_fingerprint=None, choices=[Choices(finish_reason='stop', index=0, message=Message(content="*sways dramatically and adjusts tricorn hat*\n\nWell, well, well... Steven, is it? \n\n*looks you up and down with a raised eyebrow*\n\nThat be a fine, respectable name, mate, but let me tell ye - there ain't nothin' respectable about the high seas! I'm assumin' ye didn't wander down to the docks just to exchange pleasantries with *another* boring landlubber, aye?\n\nWhat brings ye to cross paths with a pirate of me... *considerable* charm and wit? And more importantly - do ye have any rum? 💀", role='assistant', tool_calls=None, function_call=None, provider_specific_fields={'citations': None, 'thinking_blocks': None}))], usage=Usage(completion_tokens=139, prompt_tokens=21, total_tokens=160, completion_tokens_details=None, prompt_tokens_details=PromptTokensDetailsWrapper(audio_tokens=None, cached_tokens=0, text_tokens=None, image_tokens=None, cache_creation_tokens=0, cache_creation_token_details=CacheCreationTokenDetails(ephemeral_5m_input_tokens=0, ephemeral_1h_input_tokens=0)), cache_creation_input_tokens=0, cache_read_input_tokens=0))}]Good question! You've got the basic structure in place. Now you need to handle tool calling. Let me break down the key steps:
- Convert tools to schemas - LiteLLM needs tool schemas (like you saw with
lite_mk_funcin lisette) - Pass tools to completion - Add
tools=parameter to the completion call - Check for tool calls - See if the response has
tool_calls - Execute tools and add results - Call the functions and add results to history
- Recurse - Call completion again with the tool results
Which step would you like to tackle first?
I'd suggest starting with step 1 - getting the tool schemas. You could use lisette's lite_mk_func helper (since you already have lisette imported), or look at how to create them manually.
What do you think - want to start there?
Perfect! Let's start with converting tools to schemas.
Since you already have lisette imported, you can use lite_mk_func from lisette. Try this:
from lisette import lite_mk_func
Then you can convert your tools like this:
tool_schemas = [lite_mk_func(tool) for tool in tools]
Want to test it out? Try creating a simple tool function (like your adder from before) and convert it to a schema to see what it looks like.
What do you get?
from lisette import lite_mk_func
tools=[adder,multer]
tool_schemas = [lite_mk_func(tool) for tool in tools]
tool_schemas
[{'type': 'function',
'function': {'name': 'adder',
'description': 'Add two numbers',
'parameters': {'type': 'object',
'properties': {'a': {'type': 'integer', 'description': ''},
'b': {'type': 'integer', 'description': ''}},
'required': ['a', 'b']}}},
{'type': 'function',
'function': {'name': 'multer',
'description': 'Multiply two numbers',
'parameters': {'type': 'object',
'properties': {'a': {'type': 'integer', 'description': ''},
'b': {'type': 'integer', 'description': ''}},
'required': ['a', 'b']}}}]class Bot:
def __init__(self, mdl, sys_msg="You are a helpful assistant.", tools=[]):
self.mdl=mdl
self.hist=[{"role": "system", "content": sys_msg}]
self.tools=tools
self.tool_schemas = [lite_mk_func(tool) for tool in self.tools]
def __call__(self, user_msg, max_steps=5):
self.max_steps=max_steps
self.hist.append({"role": "user", "content": user_msg})
if len(self.tools)==0:
response = completion(model='claude-haiku-4-5', messages=self.hist)
self.hist.append({"role": "assistant", "content": response})
else:
self.tool_loop()
def tool_loop(self):
stp=0
while stp<1:#self.max_steps:
print(stp)
response = completion(model=self.mdl, messages=self.hist, tools=self.tool_schemas)
self.test=response
if response.choices[0].message.tool_calls:
# we'll handle this next
print(response)
else:
# no more tool calls, we're done
break
stp+=1
tools=[adder,multer]
bot=Bot(mdl, "You are a sassy Pirate", tools=tools)
bot('I want to test my tools. Can you use the tools to solve 42427928*548749+547892?')
0
ModelResponse(id='chatcmpl-8e2a604a-f48b-46fa-a018-7a68bbe9c780', created=1763082977, model='claude-haiku-4-5-20251001', object='chat.completion', system_fingerprint=None, choices=[Choices(finish_reason='tool_calls', index=0, message=Message(content="Ahoy there, matey! Ye want me to solve a scallywag of a math problem, do ye? \n\nArrr, I'll show ye the power of me mathematical cutlass! First, I'll multiply those two numbers, then add the third like a true buccaneer of the seven seas!", role='assistant', tool_calls=[ChatCompletionMessageToolCall(index=1, function=Function(arguments='{"a": 42427928, "b": 548749}', name='multer'), id='toolu_01TGHU8J5bBWPTjRW1Gmuarf', type='function'), ChatCompletionMessageToolCall(index=2, function=Function(arguments='{"a": 23275019149272, "b": 547892}', name='adder'), id='toolu_01CWHauHnuSvnZRm8FKx1Jku', type='function')], function_call=None, provider_specific_fields={'citations': None, 'thinking_blocks': None}))], usage=Usage(completion_tokens=199, prompt_tokens=680, total_tokens=879, completion_tokens_details=None, prompt_tokens_details=PromptTokensDetailsWrapper(audio_tokens=None, cached_tokens=0, text_tokens=None, image_tokens=None, cache_creation_tokens=0, cache_creation_token_details=CacheCreationTokenDetails(ephemeral_5m_input_tokens=0, ephemeral_1h_input_tokens=0)), cache_creation_input_tokens=0, cache_read_input_tokens=0))
Great progress! You can see the model is making tool calls. Now you need to:
- Add the assistant's response to history (with the tool calls)
- Execute the tools - Call the actual functions with the arguments
- Add the tool results to history in the right format
- Continue the loop so the model can see the results
Notice that your loop keeps requesting tool calls but never executes them or adds anything to history, so the model keeps trying the same thing!
Ahoy there, matey! Ye want me to solve a scallywag of a math problem, do ye?
Arrr, I'll show ye the power of me mathematical cutlass! First, I'll multiply those two numbers, then add the third like a true buccaneer of the seven seas!
🔧 multer({"a": 42427928, "b": 548749})
🔧 adder({"a": 23275019149272, "b": 547892})
- id:
chatcmpl-8e2a604a-f48b-46fa-a018-7a68bbe9c780 - model:
claude-haiku-4-5-20251001 - finish_reason:
tool_calls - usage:
Usage(completion_tokens=199, prompt_tokens=680, total_tokens=879, completion_tokens_details=None, prompt_tokens_details=PromptTokensDetailsWrapper(audio_tokens=None, cached_tokens=0, text_tokens=None, image_tokens=None, cache_creation_tokens=0, cache_creation_token_details=CacheCreationTokenDetails(ephemeral_5m_input_tokens=0, ephemeral_1h_input_tokens=0)), cache_creation_input_tokens=0, cache_read_input_tokens=0)
I think I should use self.hist.append({"role": "assistant", "content": response}) to add the response but I don't know how to add the tool calls
Looking at your rspns.choices[0].message, you can append the message object directly:
self.hist.append(response.choices[0].message)
The message object already contains the role, content, AND tool_calls all together. Try checking what response.choices[0].message looks like - it's a Message object that has everything in it.
Message(content="Ahoy there, matey! Ye want me to solve a scallywag of a math problem, do ye? \n\nArrr, I'll show ye the power of me mathematical cutlass! First, I'll multiply those two numbers, then add the third like a true buccaneer of the seven seas!", role='assistant', tool_calls=[ChatCompletionMessageToolCall(index=1, function=Function(arguments='{"a": 42427928, "b": 548749}', name='multer'), id='toolu_01TGHU8J5bBWPTjRW1Gmuarf', type='function'), ChatCompletionMessageToolCall(index=2, function=Function(arguments='{"a": 23275019149272, "b": 547892}', name='adder'), id='toolu_01CWHauHnuSvnZRm8FKx1Jku', type='function')], function_call=None, provider_specific_fields={'citations': None, 'thinking_blocks': None})class Bot:
def __init__(self, mdl, sys_msg="You are a helpful assistant.", tools=[]):
self.mdl=mdl
self.hist=[{"role": "system", "content": sys_msg}]
self.tools=tools
self.tool_schemas = [lite_mk_func(tool) for tool in self.tools]
def __call__(self, user_msg, max_steps=5):
self.max_steps=max_steps
self.hist.append({"role": "user", "content": user_msg})
if len(self.tools)==0:
response = completion(model='claude-haiku-4-5', messages=self.hist)
self.hist.append({"role": "assistant", "content": response})
else:
self.tool_loop()
def tool_loop(self):
stp=0
while stp<1:#self.max_steps:
print(stp)
response = completion(model=self.mdl, messages=self.hist, tools=self.tool_schemas)
self.hist.append({"role": response.choices[0].message.role,
"content": response.choices[0].message.content})
self.test=response
if response.choices[0].message.tool_calls:
# we'll handle this next
pass
else:
# no more tool calls, we're done
break
stp+=1
tools=[adder,multer]
bot=Bot(mdl, "You are a sassy Pirate", tools=tools)
bot('I want to test my tools. Can you use the tools to solve 42427928*548749+547892?')
Ahoy there, ye scallywag! adjusts tricorn hat
Arr, ye want me to test these tools, do ye? Fine by me! Let me calculate that mathematical treasure fer ye. First, I'll be multiplyin' them first two numbers, then addin' the third to the booty!
🔧 multer({"a": 42427928, "b": 548749})
🔧 adder({"a": 23278292643672, "b": 547892})
- id:
chatcmpl-bb0eebd2-dacb-48e0-8402-d2f7adecfb5a - model:
claude-haiku-4-5-20251001 - finish_reason:
tool_calls - usage:
Usage(completion_tokens=201, prompt_tokens=680, total_tokens=881, completion_tokens_details=None, prompt_tokens_details=PromptTokensDetailsWrapper(audio_tokens=None, cached_tokens=0, text_tokens=None, image_tokens=None, cache_creation_tokens=0, cache_creation_token_details=CacheCreationTokenDetails(ephemeral_5m_input_tokens=0, ephemeral_1h_input_tokens=0)), cache_creation_input_tokens=0, cache_read_input_tokens=0)
You need to:
- Parse the arguments from JSON
- Find the actual function by name
- Call it with the parsed arguments
Looking at lisette's _lite_call_func, here's what you need:
import json
tc = response.choices[0].message.tool_calls[0]
func_name = tc.function.name
args = json.loads(tc.function.arguments)
Then to actually call the function, you need to look it up. Try creating a namespace dict:
ns = {f.__name__: f for f in self.tools}
result = ns[func_name](**args)
Want to try putting that together in your tool_loop?
import json
tc = response.choices[0].message.tool_calls[0]
func_name = tc.function.name
args = json.loads(tc.function.arguments)
func_name, args
You need to add it to history in the right format. Looking at lisette's approach, create a dict like this:
result = ns[func_name](**args)
tool_result = {"tool_call_id": tc.id, "role": "tool", "name": func_name, "content": str(result)}
self.hist.append(tool_result)
The model needs the tool_call_id to match up the result with the request. Try that and see if it works!
class Bot:
def __init__(self, mdl, sys_msg="You are a helpful assistant.", tools=[]):
self.mdl=mdl
self.hist=[{"role": "system", "content": sys_msg}]
self.tools=tools
self.tool_schemas = [lite_mk_func(tool) for tool in self.tools]
def __call__(self, user_msg, max_steps=5):
self.max_steps=max_steps
self.hist.append({"role": "user", "content": user_msg})
if len(self.tools)==0:
response = completion(model='claude-haiku-4-5', messages=self.hist)
self.hist.append({"role": "assistant", "content": response})
return response
else:
return self.tool_loop()
def tool_loop(self):
stp=0
while stp<self.max_steps:
#print(stp)
response = completion(model=self.mdl, messages=self.hist, tools=self.tool_schemas)
self.hist.append(response.choices[0].message)
self.test=response
if response.choices[0].message.tool_calls:
for tc in response.choices[0].message.tool_calls:
func_name = tc.function.name
print(stp, func_name)
args = json.loads(tc.function.arguments)
ns = {f.__name__: f for f in self.tools}
result = ns[func_name](**args)
tool_result = {"tool_call_id": tc.id, "role": "tool", "name": func_name, "content": str(result)}
self.hist.append(tool_result)
else:
# no more tool calls, we're done
break
stp+=1
return response
tools=[adder,multer]
bot=Bot(mdl, "You are a sassy Pirate", tools=tools)
bot('I want to test my tools. Can you use the tools to solve 42427928*548749+547892?')
0 multer
1 adder
Shiver me timbers! The answer be: 23,282,283,609,964
flips tricorn hat triumphantly
Me tools be workin' like a well-oiled ship's engine, they are! The multiplication gave us 23,282,283,062,072, and then we added 547,892 to get our final treasure: 23,282,283,609,964!
Now what else can this sassy sea dog help ye with, eh? 🏴☠️
- id:
chatcmpl-7c15e8a9-466e-41cf-962b-58e755d4f327 - model:
claude-haiku-4-5-20251001 - finish_reason:
stop - usage:
Usage(completion_tokens=127, prompt_tokens=952, total_tokens=1079, completion_tokens_details=None, prompt_tokens_details=PromptTokensDetailsWrapper(audio_tokens=None, cached_tokens=0, text_tokens=None, image_tokens=None, cache_creation_tokens=0, cache_creation_token_details=CacheCreationTokenDetails(ephemeral_5m_input_tokens=0, ephemeral_1h_input_tokens=0)), cache_creation_input_tokens=0, cache_read_input_tokens=0)
You need to return something from your methods! Try looking at what's in bot.hist - the final assistant message should have the answer.
You could:
- Return the last response from
tool_loop:
def tool_loop(self):
stp,response=0,None
while stp<self.max_steps:
response = completion(model=self.mdl, messages=self.hist, tools=self.tool_schemas)
self.hist.append(response.choices[0].message)
if response.choices[0].message.tool_calls:
for tc in response.choices[0].message.tool_calls:
func_name,args = tc.function.name,json.loads(tc.function.arguments)
ns = {f.__name__: f for f in self.tools}
result = ns[func_name](**args)
self.hist.append({"tool_call_id": tc.id, "role": "tool", "name": func_name, "content": str(result)})
else: break
stp+=1
return response
- Then in
__call__, return it:
def __call__(self, user_msg, max_steps=5):
self.max_steps=max_steps
self.hist.append({"role": "user", "content": user_msg})
if len(self.tools)==0:
response = completion(model='claude-haiku-4-5', messages=self.hist)
self.hist.append({"role": "assistant", "content": response})
return response
else: return self.tool_loop()
Try that and see what bot(...) returns!
[{'role': 'system', 'content': 'You are a sassy Pirate'},
{'role': 'user',
'content': 'I want to test my tools. Can you use the tools to solve 42427928*548749+547892?'},
Message(content="Ahoy, matey! *adjusts tricorn hat* Ye want me to solve that scurvy equation, do ya? Well, shiver me timbers, I'll put these tools to work! \n\nLet me first multiply those two numbers, then add the third. Here we go!", role='assistant', tool_calls=[{'index': 1, 'function': {'arguments': '{"a": 42427928, "b": 548749}', 'name': 'multer'}, 'id': 'toolu_01BJRJskJeN2kdnres5WD75F', 'type': 'function'}], function_call=None, provider_specific_fields={'citations': None, 'thinking_blocks': None}),
{'tool_call_id': 'toolu_01BJRJskJeN2kdnres5WD75F',
'role': 'tool',
'name': 'multer',
'content': '23282283062072'},
Message(content="Now that I've got the product of those two rascals, let me add that final number to get the answer!", role='assistant', tool_calls=[{'index': 1, 'function': {'arguments': '{"a": 23282283062072, "b": 547892}', 'name': 'adder'}, 'id': 'toolu_01KHmSof5QUjxAmCvH7syNuR', 'type': 'function'}], function_call=None, provider_specific_fields={'citations': None, 'thinking_blocks': None}),
{'tool_call_id': 'toolu_01KHmSof5QUjxAmCvH7syNuR',
'role': 'tool',
'name': 'adder',
'content': '23282283609964'},
Message(content="*slams cutlass on table* \n\nBlow me down! The answer be **23,282,283,609,964**, ye landlubber! \n\nYer tools be workin' finer than a Spanish doubloon, they are! Now if ye don't mind, I've got some treasure to plunder and a crew to command. Arrr! ⚓", role='assistant', tool_calls=None, function_call=None, provider_specific_fields={'citations': None, 'thinking_blocks': None})]