# dhb


<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->

``` python
c()
```

    Please try again by using e.g. `bc = dhb.c('model_name')` with a model name e.g. pick from these found by searching for 'gemini':
    databricks/databricks-gemini-2-5-flash
    databricks/databricks-gemini-2-5-pro
    deepinfra/google/gemini-2.0-flash-001
    deepinfra/google/gemini-2.5-flash
    deepinfra/google/gemini-2.5-pro
    fal_ai/fal-ai/gemini-25-flash-image
    gemini-2.0-flash
    gemini-2.0-flash-001
    gemini-2.0-flash-lite
    gemini-2.0-flash-lite-001
    gemini-2.5-flash
    gemini-2.5-flash-image
    gemini-3-pro-image
    gemini-3-pro-image-preview
    gemini-3.1-flash-image
    gemini-3.1-flash-image-preview
    gemini-3.1-flash-lite-preview
    gemini-3.1-flash-lite
    gemini-3.5-flash-lite
    gemini-2.5-flash-lite
    gemini-2.5-flash-lite-preview-09-2025
    gemini-2.5-flash-preview-09-2025
    gemini-live-2.5-flash-preview-native-audio-09-2025
    gemini/gemini-live-2.5-flash-preview-native-audio-09-2025
    gemini-2.5-flash-lite-preview-06-17
    gemini-2.5-pro
    gemini-3-pro-preview
    gemini-3.1-pro-preview
    gemini-3.1-pro-preview-customtools
    vertex_ai/gemini-3-pro-preview
    vertex_ai/gemini-3-flash-preview
    vertex_ai/gemini-3.5-flash
    vertex_ai/gemini-3.6-flash
    vertex_ai/gemini-3.1-pro-preview
    vertex_ai/gemini-3.1-pro-preview-customtools
    gemini-2.5-pro-preview-tts
    gemini-robotics-er-1.5-preview
    gemini/gemini-robotics-er-1.5-preview
    gemini/gemini-robotics-er-2-preview
    gemini/gemini-robotics-er-1.6-preview
    gemini-2.5-computer-use-preview-10-2025
    gemini-embedding-001
    gemini-embedding-2-preview
    gemini-embedding-2
    vertex_ai/gemini-embedding-2-preview
    vertex_ai/gemini-embedding-2
    gemini-flash-experimental
    gemini/gemini-embedding-001
    gemini/gemini-embedding-2-preview
    gemini/gemini-embedding-2
    gemini/gemini-1.5-flash
    gemini/gemini-2.0-flash
    gemini/gemini-2.0-flash-001
    gemini/gemini-2.0-flash-lite
    gemini/gemini-2.5-flash
    gemini/gemini-2.5-flash-image
    gemini/gemini-3-pro-image
    gemini/gemini-3-pro-image-preview
    gemini/gemini-3.1-flash-image
    gemini/gemini-3.1-flash-image-preview
    gemini/deep-research-pro-preview-12-2025
    gemini/gemini-2.5-flash-lite
    gemini/gemini-2.5-flash-lite-preview-09-2025
    gemini/gemini-2.5-flash-preview-09-2025
    gemini/gemini-flash-latest
    gemini/gemini-flash-lite-latest
    gemini/gemini-2.5-flash-lite-preview-06-17
    gemini/gemini-2.5-flash-preview-tts
    gemini/gemini-2.5-pro
    gemini/gemini-2.5-computer-use-preview-10-2025
    gemini/gemini-3-pro-preview
    gemini/gemini-3.1-flash-lite-preview
    gemini/gemini-3.1-flash-lite
    gemini/gemini-3.5-flash-lite
    gemini/gemini-3-flash-preview
    gemini/gemini-3.5-flash
    gemini/gemini-3.6-flash
    gemini/gemini-omni-flash-preview
    gemini/gemini-3.1-pro-preview
    gemini/gemini-3.1-pro-preview-customtools
    gemini-3-flash-preview
    gemini-omni-flash-preview
    gemini-3.5-flash
    gemini-3.6-flash
    gemini/gemini-2.5-pro-preview-tts
    gemini/gemini-exp-1114
    gemini/gemini-exp-1206
    gemini/gemini-gemma-2-27b-it
    gemini/gemini-gemma-2-9b-it
    gemini/gemma-3-27b-it
    gemini/imagen-3.0-fast-generate-001
    gemini/imagen-3.0-generate-001
    gemini/imagen-3.0-generate-002
    gemini/imagen-4.0-fast-generate-001
    gemini/imagen-4.0-generate-001
    gemini/imagen-4.0-ultra-generate-001
    gemini/learnlm-1.5-pro-experimental
    gemini/lyria-3-clip-preview
    gemini/lyria-3-pro-preview
    gemini/veo-2.0-generate-001
    gemini/veo-3.1-fast-generate-preview
    gemini/veo-3.1-generate-preview
    gemini/veo-3.1-lite-generate-preview
    gemini/veo-3.1-fast-generate-001
    gemini/veo-3.1-generate-001
    github_copilot/gemini-2.5-pro
    github_copilot/gemini-3-pro-preview
    gmi/google/gemini-3-pro-preview
    gmi/google/gemini-3-flash-preview
    oci/google.gemini-2.5-flash
    oci/google.gemini-2.5-pro
    oci/google.gemini-2.5-flash-lite
    openrouter/google/gemini-2.0-flash-001
    openrouter/google/gemini-2.5-flash
    openrouter/google/gemini-2.5-pro
    openrouter/google/gemini-3-pro-preview
    openrouter/google/gemini-3-flash-preview
    openrouter/google/gemini-3.1-flash-lite-preview
    openrouter/google/gemini-3.1-flash-lite
    openrouter/google/gemini-3.1-pro-preview
    perplexity/google/gemini-3-pro-preview
    perplexity/google/gemini-3-flash-preview
    perplexity/google/gemini-2.5-pro
    perplexity/google/gemini-2.5-flash
    replicate/google/gemini-3-pro
    replicate/google/gemini-2.5-flash
    vercel_ai_gateway/google/gemini-2.0-flash
    vercel_ai_gateway/google/gemini-2.0-flash-lite
    vercel_ai_gateway/google/gemini-2.5-flash
    vercel_ai_gateway/google/gemini-2.5-pro
    vercel_ai_gateway/google/gemini-embedding-001
    vertex_ai/gemini-2.5-flash-image
    vertex_ai/gemini-3-pro-image
    vertex_ai/gemini-3-pro-image-preview
    vertex_ai/gemini-3.1-flash-image
    vertex_ai/gemini-3.1-flash-image-preview
    vertex_ai/gemini-3.1-flash-lite-preview
    vertex_ai/gemini-3.1-flash-lite
    vertex_ai/gemini-3.5-flash-lite
    gemini-2.0-flash-exp-image-generation
    gemini/gemini-2.0-flash-exp-image-generation
    gemini/gemini-2.0-flash-lite-001
    gemini-2.5-flash-native-audio-latest
    gemini-2.5-flash-native-audio-preview-09-2025
    gemini-2.5-flash-native-audio-preview-12-2025
    gemini-3.1-flash-live-preview
    gemini/gemini-2.5-flash-native-audio-latest
    gemini/gemini-2.5-flash-native-audio-preview-09-2025
    gemini/gemini-2.5-flash-native-audio-preview-12-2025
    gemini/gemini-3.1-flash-live-preview
    gemini-2.5-flash-preview-tts
    gemini-flash-latest
    gemini-flash-lite-latest
    gemini-pro-latest
    gemini/gemini-pro-latest
    gemini-exp-1206
    ### The following ones are listed by OpenRouter but not LiteLLM (may still work)

    <__main__.BackupChat>

``` python
%time
bc = c("gemini/gemini-flash-lite-latest")
bc("hi")
```

    CPU times: user 3 us, sys: 0 ns, total: 3 us
    Wall time: 5.96 us

Hello! How can I help you with your `solveit_dmtools` module today?

<details>

- id: `Um54avDDEvyc-8YPx5OY4Qg`
- model: `gemini-flash-lite-latest`
- finish_reason: `stop`
- usage:
  `Usage(completion_tokens=19, prompt_tokens=7869, total_tokens=7888, completion_tokens_details=CompletionTokensDetailsWrapper(accepted_prediction_tokens=None, audio_tokens=None, reasoning_tokens=None, rejected_prediction_tokens=None, text_tokens=19, image_tokens=None, video_tokens=None), prompt_tokens_details=PromptTokensDetailsWrapper(audio_tokens=None, cached_tokens=None, text_tokens=7869, image_tokens=None, video_tokens=None), cache_read_input_tokens=None)`

</details>

**Prompt (gemini/gemini-flash-lite-latest):** hi

##### 🤖Reply🤖<!-- SOLVEIT_SEPARATOR_7f3a9b2c -->

Hello! How can I help you with your `solveit_dmtools` module today?

``` json
{"model": "gemini/gemini-flash-lite-latest", "prompt_tokens": 7869, "completion_tokens": 19, "total_tokens": 7888, "cached_tokens": 0, "cache_creation_tokens": 0, "cost": 0.0007945}
```

``` python
%%bc
Testing if cell magic works
```

``` python
%%bc
Testing cell magic execution
```

<details>

- id: `Zm54au7wBN-UjrEPmKzPgAw`
- model: `gemini-flash-lite-latest`
- finish_reason: `stop`
- usage:
  `Usage(completion_tokens=12, prompt_tokens=8192, total_tokens=8204, completion_tokens_details=CompletionTokensDetailsWrapper(accepted_prediction_tokens=None, audio_tokens=None, reasoning_tokens=None, rejected_prediction_tokens=None, text_tokens=12, image_tokens=None, video_tokens=None), prompt_tokens_details=PromptTokensDetailsWrapper(audio_tokens=None, cached_tokens=None, text_tokens=8192, image_tokens=None, video_tokens=None), cache_read_input_tokens=None)`

</details>

**Prompt (gemini/gemini-flash-lite-latest):** Testing if cell magic
works

##### 🤖Reply🤖<!-- SOLVEIT_SEPARATOR_7f3a9b2c -->

``` python
%%bc
Testing cell magic execution
```

``` json
{"model": "gemini/gemini-flash-lite-latest", "prompt_tokens": 8192, "completion_tokens": 12, "total_tokens": 8204, "cached_tokens": 0, "cache_creation_tokens": 0, "cost": 0.000824}
```

``` python
[(h['role'], h['content'][0:200]) for h in bc.hist[-6:]]
```

    [('user',
      '```python\n%time\nbc = c("gemini/gemini-flash-lite-latest")\nbc("hi")\n```\nOutput: CPU times: user 3 us, sys: 0 ns, total: 3 us\nWall time: 5.96 us\n\nHello! How can I help you with your `solveit_dmtools` mo'),
     ('user', '**Prompt (gemini/gemini-flash-lite-latest):** hi'),
     ('assistant',
      'Hello! How can I help you with your `solveit_dmtools` module today?\n'),
     ('assistant', '.'),
     ('user', 'Testing if cell magic works\n'),
     ('assistant', '```python\n%%bc\nTesting cell magic execution\n```')]

``` python
%bc Testing line magic!
```

``` python
%bc Testing line magic execution
```

<details>

- id: `f254atqkD_PI-8YP88ry8QE`
- model: `gemini-flash-lite-latest`
- finish_reason: `stop`
- usage:
  `Usage(completion_tokens=11, prompt_tokens=8439, total_tokens=8450, completion_tokens_details=CompletionTokensDetailsWrapper(accepted_prediction_tokens=None, audio_tokens=None, reasoning_tokens=None, rejected_prediction_tokens=None, text_tokens=11, image_tokens=None, video_tokens=None), prompt_tokens_details=PromptTokensDetailsWrapper(audio_tokens=None, cached_tokens=None, text_tokens=8439, image_tokens=None, video_tokens=None), cache_read_input_tokens=None)`

</details>

**Prompt (gemini/gemini-flash-lite-latest):** Testing line magic!

##### 🤖Reply🤖<!-- SOLVEIT_SEPARATOR_7f3a9b2c -->

``` python
%bc Testing line magic execution
```

``` json
{"model": "gemini/gemini-flash-lite-latest", "prompt_tokens": 8439, "completion_tokens": 11, "total_tokens": 8450, "cached_tokens": 0, "cache_creation_tokens": 0, "cost": 0.0008483}
```

``` python
[(h['role'], h['content'][0:200]) for h in bc.hist[-6:]]
```

    [('user',
      '**Prompt (gemini/gemini-flash-lite-latest):** Testing if cell magic works\n'),
     ('assistant', '```python\n%%bc\nTesting cell magic execution\n```\n'),
     ('user',
      '```python\n[(h[\'role\'], h[\'content\'][0:200]) for h in bc.hist[-6:]]\n```\nOutput: [(\'user\', \'```python\\n%time\\nbc = c("gemini/gemini-flash-lite-latest")\\nbc("hi")\\n```\\nOutput: CPU times: user 3 us, sys:'),
     ('assistant', '.'),
     ('user', 'Testing line magic!'),
     ('assistant', '```python\n%bc Testing line magic execution\n```')]

``` python
bc.print_hist()
```

    {'role': 'user', 'content': '```python\n#|default_exp dhb\n```\nOutput: '}

    {'role': 'user', 'content': '```python\n#| hide\nfrom nbdev.showdoc import *\n```\nOutput: '}

    {'role': 'user', 'content': '```python\n#| export\ndoc = """**Backup Chat for SolveIt using dialoghelper and lisette**\n\nSometimes we may have a problem in SolveIt while Sonnet is down (E300), or maybe we want a different perspective.\n\nThis module helps us to leverage any other LLM that is available to LiteLLM by providing our own keys and the model name.\n\nUsage: \n```python\nfrom solveit_dmtools import dhb\n\n# then in another cell\n# dhb.c() to search model names\nbc = dhb.c("model-name")\n# then in another cell\nbc("Hi")\n# you can also use line magic\n%bc some prompt\n# or cell magic\n%%bc\nsome\nprompt\n# cell magic allows you to pick a different BackupChat instance than default \'bc\' - e.g. one called \'judge\'\n%%bc judge\nwas the response above good?\nor was it bad?\n```\n"""\n```\nOutput: '}

    {'role': 'user', 'content': '```python\n#| export\nimport json\nimport re\nfrom dialoghelper.core import *\nfrom lisette import *\nfrom solveit_dmtools.core import run_async\nfrom typing import Optional, Union\nfrom ipykernel_helper import read_url\nimport inspect\nfrom fastcore.all import patch\nimport os\n\ndef _default_sanitize(text: str) -> str:\n    "Strip [sanitized tool: tool] and [sanitized var: var] references from untrusted input, replacing with a labeled placeholder."\n    def replace(m):\n        kind = \'var\' if m.group(0)[0] == \'$\' else \'tool\'\n        name = re.search(r\'`([^`]*)`\', m.group(0)).group(1).strip()\n        return f\'[sanitized {kind}: {name}]\'\n    return re.sub(r\'[&$]\\s*`[^`]*`\', replace, text)\n\n_usage_re = re.compile(r\'\\n*```json\\s*\\{\\.usage\\}.*?```\\n*\', re.DOTALL)\n\n_DEFAULT_SP = """You\'re continuing a conversation from another session. Variables are marked as [sanitized var: varname] and tools as [sanitized tool: toolname] in the context.\n\n**Available Resources**\n\nIf you see references to variables or tools that might be relevant but aren\'t fully available, ask the user which ones they want to include by calling their `bc.add_vars`, `bc.add_tools`, or `bc.add_vars_and_tools` methods (if they called their chat instance `bc`). These methods accept either a list of names or a space-delimited string.\n\n**Tool Usage Notes**\n\n- Tool results from earlier conversations may be truncated to ~100 characters. If you need complete information, ask the user to run the tool and store results in a variable, then make that variable available using `bc.add_vars`.\n- You have access to the `read_url` tool, but confirm before reading URLs as access may be expensive.\n\n**Code Execution**\n\nYou cannot run code yourself or store variables. Instead, provide Python code in fenced markdown blocks. The user can execute these in their environment.\n\n**Teaching Approach**\n\nUse a Socratic method - guide through questions rather than providing direct answers - unless the user explicitly requests otherwise. When providing code examples:\n\n- Keep code snippets brief (1-3 lines maximum) unless the user explicitly asks you to write more\n- Encourage the user to implement solutions themselves\n- Ask clarifying questions about their expertise and goals to customize your responses\n"""\n\nclass BackupChat(Chat):\n    models = None\n    vars_for_hist = None\n    model = None\n\n    def __init__(self,\n                model: str = None,\n                sp=None,\n                temp=0,\n                search=False,\n                tools: list = None,\n                hist: list = None,\n                ns: Optional[dict] = None,\n                cache=False,\n                cache_idxs: list = [-1],\n                ttl=None,\n                var_names: Union[list,str] = None,\n                hide_msg:bool=True, # whether to hide the cell that includes a BackupChat.__call__\n                sanitize_fn=_default_sanitize, # applied to all messages; pass None to disable\n    ):\n        if sp is None or sp == \'\': sp = _DEFAULT_SP\n        if self.models is None:\n            self.models = self.get_litellm_models()\n        if model is None:\n            _m1 = input("Please enter part of a model name to pick your model. Remember you also need to have secret for their API key already defined in your secrets:")\n            print(f"Please try again by using e.g. `bc = dhb.c(\'model_name\')` with a model name e.g. pick from these found by searching for \'{_m1}\':")\n            # search case-insensitively and return models that match\n            print(\'\\n\'.join([m for m in self.models if _m1.lower() in m.lower() or \'###\' in m]))\n            return None\n        if model not in self.models:\n            raise ValueError(f"Model {model} not found in LiteLLM models. Please check the model name or use a different model.")\n        self.model = model\n        self.hide_msg = hide_msg\n        self.sanitize_fn = sanitize_fn\n        self.vars_for_hist = dict()\n        if var_names is not None:\n            self.add_vars(var_names)\n        if tools is None:\n            tools = [read_url]\n        if ns is None:\n            ns = inspect.currentframe().f_back.f_globals\n        try: self._dname = ns.get(\'__dialog_name\') or find_var(\'__dialog_name\')\n        except ValueError: self._dname = \'\'\n        super().__init__(model=model, sp=sp, temp=temp, search=search, tools=tools, hist=hist, ns=ns, cache=cache, cache_idxs=cache_idxs, ttl=ttl)\n\n    def get_openrouter_ignored(self):\n        url = "https://raw.githubusercontent.com/cheahjs/free-llm-api-resources/refs/heads/main/src/data.py"\n        code = read_url(url, as_md=False)\n        \n        # Find the OPENROUTER_IGNORED_MODELS set definition\n        pattern = r\'OPENROUTER_IGNORED_MODELS\\s*=\\s*\\{([^}]+)\\}\'\n        match = re.search(pattern, code, re.DOTALL)\n        models = []\n        \n        if match:\n            # Extract the content and parse the strings\n            content = match.group(1)\n            models = re.findall(r\'"([^"]+)"\', content)\n        return list(models)\n    \n    def fetch_openrouter_models(self, already_listed:list=None):\n        r = read_url("https://openrouter.ai/api/v1/models", as_md=False)\n        models = json.loads(r)[\'data\']\n        ignored_models = self.get_openrouter_ignored()\n        ret_models = []\n        for model in models:\n            pricing = float(model.get("pricing", {}).get("completion", "1")) + float(\n                model.get("pricing", {}).get("prompt", "1")\n            )\n            if pricing != 0 or ":free" not in model["id"] or model["id"].lower() in [im.lower() for im in ignored_models]:\n                continue\n            if not (already_listed and model["id"].lower() in [al.replace(\'openrouter/\', \'\').lower() for al in already_listed]):\n                ret_models.append(\n                    {\n                        "id": f"openrouter/{model[\'id\']}",\n                        "limits": {\n                            "requests/minute": 20,\n                            "requests/day": 50,\n                        },\n                    }\n                )\n        return ret_models\n    \n    def get_litellm_models(self):\n        url = "https://raw.githubusercontent.com/BerriAI/litellm/refs/heads/main/model_prices_and_context_window.json"\n        data = read_url(url, as_md=False)\n        models = json.loads(data)\n        already_listed = [k for k in models.keys() if k != \'sample_spec\']\n        return already_listed + [f"### The following ones are listed by OpenRouter but not LiteLLM (may still work)"] + sorted([orm[\'id\'] for orm in self.fetch_openrouter_models(already_listed)])\n   \n    def add_vars(self, var_names:Union[list,str]=None):\n        "Add variables to conversation as user message"\n        if isinstance(var_names, str):\n            var_names = var_names.split()\n        if not isinstance(var_names, list):\n            raise ValueError(f"var_names must be a string or list of strings, not {type(var_names)}")\n        \n        # Add each var to the self.vars_for_hist dictionary\n        for v in var_names:\n            self.vars_for_hist[v.strip()] = self.ns.get(v.strip(), \'NOT AVAILABLE\')\n```\nOutput: '}

    {'role': 'user', 'content': '```python\n#| export\n@patch\nasync def _async_call(self:BackupChat,\n            msg=None,\n            prefill=None,\n            temp=None,\n            think=None,\n            search=None,\n            stream=False,\n            max_steps=2,\n            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            return_all=False,\n            var_names=None, # list of variable names to add to the chat\n            last_msg=None,\n            curr_msg=None,\n            is_launcher=None, # True if calling cell is a disposable launcher (e.g. %%bc); None infers\n            **kwargs,\n            ):\n    dname = \'/\' + self._dname.lstrip(\'/\') if self._dname else \'\'\n    msgs = [{k: m[k] for k in [\'id\', \'msg_type\', \'content\', \'output\', \'pinned\', \'skipped\']} for m in await find_msgs(dname=dname, include_output=True, include_skipped=True)]\n    if var_names: self.add_vars(var_names)\n    if msg and self.sanitize_fn: msg = self.sanitize_fn(msg)\n    self.hist = self._build_hist(msgs, last_msg=last_msg)\n    start = len(self.hist)\n    if is_launcher is None:\n        instance_name = next((k for k, v in self.ns.items() if v is self), None)\n        is_launcher = bool(instance_name and f"{instance_name}(" in curr_msg[\'content\'])\n    if is_launcher:\n        await update_msg(id=curr_msg[\'id\'], content="# " + curr_msg[\'content\'].replace(\'\\n\', \'\\n# \'), skipped=self.hide_msg, dname=dname)\n    response = Chat.__call__(self, msg=msg, prefill=prefill, temp=temp, think=think, search=search, stream=stream, max_steps=max_steps, final_prompt=final_prompt, return_all=return_all, **kwargs)\n    output = self._new_msgs_to_output(start) + self._usage_block()\n    if is_launcher:\n        await update_msg(id=curr_msg[\'id\'], o_collapsed=True, dname=dname)\n    await add_msg(content=f"**Prompt ({self.model}):** {msg}", output=output, msg_type=\'prompt\', id=curr_msg[\'id\'], dname=dname)\n    return response\n\n@patch\ndef __call__(self:BackupChat,\n            msg=None,\n            prefill=None,\n            temp=None,\n            think=None,\n            search=None,\n            stream=False,\n            max_steps=2,\n            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            return_all=False,\n            var_names=None, # list of variable names to add to the chat\n            msg_id=None, # if provided, use this message id as the anchor instead of the current message\n            is_launcher=None, # True if calling cell is a disposable launcher (e.g. %%bc); None infers\n            **kwargs,\n            ):\n    dname =  \'/\' + self._dname.lstrip(\'/\') if self._dname else \'\'\n    if msg_id is not None:\n        last_msg = call_endp(\'read_msg_\', dname, json=True, id=msg_id, n=-1, relative=True)\n        curr_msg = call_endp(\'read_msg_\', dname, json=True, id=msg_id, n=0, relative=True)\n    else:\n        last_msg = call_endp(\'read_msg_\', dname, json=True, n=-1, relative=True)\n        curr_msg = call_endp(\'read_msg_\', dname, json=True, n=0, relative=True)\n    return run_async(self._async_call(msg=msg, prefill=prefill, temp=temp, think=think, search=search, stream=stream, max_steps=max_steps, final_prompt=final_prompt, return_all=return_all, var_names=var_names, is_launcher=is_launcher, last_msg=last_msg, curr_msg=curr_msg, **kwargs))\n\n@patch\ndef _build_hist(self:BackupChat, msgs:list, last_msg=None):\n    if last_msg is None: curr = len(msgs)-1\n    else:\n        try: curr = next(i for i,m in enumerate(msgs) if m[\'id\'] == last_msg[\'id\'])\n        except StopIteration: curr = len(msgs)-1\n    hist = []\n    for m in msgs[:curr+1]:\n        if m[\'pinned\'] or not m[\'skipped\']:\n            eol = \'\\n\'\n            base = self.sanitize_fn or (lambda x: x)\n            san = lambda x: base(_usage_re.sub(\'\\n\', x or \'\'))\n            if m[\'msg_type\'] == \'code\': hist.append({\'role\': \'user\', \'content\': f"```python{eol}{san(m[\'content\'])}{eol}```{eol}Output: {san(m.get(\'output\', \'[]\'))}"})\n            elif m[\'msg_type\'] == \'note\' or m[\'msg_type\'] == \'raw\': hist.append({\'role\': \'user\', \'content\': san(m[\'content\'])})\n            elif m[\'msg_type\'] == \'prompt\':\n                hist.append({\'role\': \'user\', \'content\': san(m[\'content\'])})\n                if m.get(\'output\'): hist.append({\'role\': \'assistant\', \'content\': san(m[\'output\'])})\n    \n    hist = hist + self._vars_as_msg() + [{\'role\': \'assistant\', \'content\': \'.\'}] # empty assistant msg to prevent flipping chat msg to look like prefill\n    return hist\n\n@patch\ndef _vars_as_msg(self:BackupChat):\n    if self.vars_for_hist and len(self.vars_for_hist.keys()):\n        content = "Here are the requested variables:\\n" + json.dumps(self.vars_for_hist)\n        return [{\'role\': \'user\', \'content\': content}]\n    else:\n        return []\n\n@patch\ndef _new_msgs_to_output(self:BackupChat, start):\n    new_msgs = self.hist[start+1:]\n    parts = []\n    for i, m in enumerate(new_msgs):\n        if m.get(\'role\') == \'assistant\' and m.get(\'tool_calls\'):\n            for tc in m[\'tool_calls\']:\n                result_msg = next((r for r in new_msgs if r.get(\'tool_call_id\') == tc[\'id\']), None)\n                if result_msg: parts.append(self._format_tool_details(tc[\'id\'], tc[\'function\'][\'name\'], json.loads(tc[\'function\'][\'arguments\']), result_msg[\'content\'], is_last_msg=(i == len(new_msgs)-1)))\n        elif m.get(\'role\') == \'assistant\' and m.get(\'content\'):\n            content = m[\'content\']\n            if \'You have no more tool uses\' not in content: parts.append(content)\n    return \'\\n\\n\'.join(parts)\n\n@patch\ndef _trunc_tool_result(self:BackupChat, result, max_len=100, is_last_msg=False):\n    if len(str(result)) <= max_len or is_last_msg: return result\n    return str(result)[:max_len] + \'<TRUNCATED>\'\n\n@patch\ndef _format_tool_details(self:BackupChat, tool_id, func_name, args, result, is_last_msg=False):\n    result_str = self._trunc_tool_result(result, is_last_msg=is_last_msg)\n    tool_json = json.dumps({"id": tool_id, "name": func_name, "args": args, "result": result_str}, indent=2)\n    return "```json {.tool}\\n" + tool_json + "\\n```"\n\n@patch\ndef _usage_block(self:BackupChat):\n    "Solveit-style \njson {.usage}\\n" + json.dumps(d) + "\\n```"\n```\nOutput: '}

    {'role': 'user', 'content': '```python\n#| export\n@patch\ndef add_tools(self:BackupChat, tool_names:Union[list,str]=None):\n    "Add tools to the chat\'s tool list"\n    if isinstance(tool_names, str):\n        tool_names = tool_names.split()\n    tools = [self.ns.get(t) for t in tool_names if self.ns.get(t)]\n    self.tools = list(self.tools or []) + tools\n    self.tool_schemas = [lite_mk_func(t) for t in self.tools] if self.tools else None\n    \n@patch\ndef add_vars_and_tools(self:BackupChat, var_names:Union[list,str]=None, tool_names:Union[list,str]=None):\n    "Add both variables and tools to the chat\'s lists"\n    self.add_tools(tool_names)\n    self.add_vars(var_names)\n```\nOutput: '}

    {'role': 'user', 'content': '```python\n#| export\nc = BackupChat\n```\nOutput: '}

    {'role': 'user', 'content': '```python\n#| export\nimport inspect\nimport shlex\n\ndef _get_magic():\n    skip = {\'self\',\'msg\',\'msg_id\',\'is_launcher\',\'kwargs\'}\n    over = {\'prefill\':str, \'temp\':float, \'think\':bool, \'search\':bool, \'var_names\':str}\n    sig = inspect.signature(BackupChat.__call__).parameters\n    casts = {k: over.get(k, type(p.default)) for k,p in sig.items() if k not in skip}\n    truthy = {\'true\',\'1\',\'t\',\'y\'}\n    def _cast(k,v): return v.lower() in truthy if casts[k] is bool else casts[k](v)\n\n    def bc_magic(line, cell=None):\n        "Send cell (or line) as a prompt to a BackupChat: `%bc prompt` or `%%bc [name] [--flag val ...]`"\n        ns = get_ipython().user_ns\n        toks = shlex.split(line) if cell is not None else []\n        name = toks.pop(0) if toks and not toks[0].startswith(\'--\') else \'bc\'\n        kw = {}\n        while toks:\n            k = toks.pop(0)\n            if not k.startswith(\'--\'): raise ValueError(f"Expected a --flag, got {k!r}")\n            k = k[2:]\n            if k not in casts: raise ValueError(f"Unknown flag --{k}. Valid: {\', \'.join(casts)}")\n            if not toks: raise ValueError(f"--{k} requires a value")\n            kw[k] = _cast(k, toks.pop(0))\n        chat = ns.get(name)\n        if chat is None: raise NameError(f"No chat named {name!r} - create one with `{name} = dhb.c(\'model-name\')`")\n        dname = getattr(chat, \'_dname\', \'\')\n        dname = \'/\' + dname.lstrip(\'/\') if dname else \'\'\n        curr_msg = call_endp(\'read_msg_\', dname, json=True, n=0, relative=True)\n        return chat(cell if cell is not None else line, msg_id=curr_msg[\'id\'], is_launcher=True, **kw)\n    return bc_magic\n\nbc_magic = _get_magic()\nget_ipython().register_magic_function(bc_magic, \'line_cell\', \'bc\')\n```\nOutput: '}

    {'role': 'user', 'content': "```python\n#|eval: false\nc()\n```\nOutput: Please try again by using e.g. `bc = dhb.c('model_name')` with a model name e.g. pick from these found by searching for 'gemini':\ndatabricks/databricks-gemini-2-5-flash\ndatabricks/databricks-gemini-2-5-pro\ndeepinfra/google/gemini-2.0-flash-001\ndeepinfra/google/gemini-2.5-flash\ndeepinfra/google/gemini-2.5-pro\nfal_ai/fal-ai/gemini-25-flash-image\ngemini-2.0-flash\ngemini-2.0-flash-001\ngemini-2.0-flash-lite\ngemini-2.0-flash-lite-001\ngemini-2.5-flash\ngemini-2.5-flash-image\ngemini-3-pro-image\ngemini-3-pro-image-preview\ngemini-3.1-flash-image\ngemini-3.1-flash-image-preview\ngemini-3.1-flash-lite-preview\ngemini-3.1-flash-lite\ngemini-3.5-flash-lite\ngemini-2.5-flash-lite\ngemini-2.5-flash-lite-preview-09-2025\ngemini-2.5-flash-preview-09-2025\ngemini-live-2.5-flash-preview-native-audio-09-2025\ngemini/gemini-live-2.5-flash-preview-native-audio-09-2025\ngemini-2.5-flash-lite-preview-06-17\ngemini-2.5-pro\ngemini-3-pro-preview\ngemini-3.1-pro-preview\ngemini-3.1-pro-preview-customtools\nvertex_ai/gemini-3-pro-preview\nvertex_ai/gemini-3-flash-preview\nvertex_ai/gemini-3.5-flash\nvertex_ai/gemini-3.6-flash\nvertex_ai/gemini-3.1-pro-preview\nvertex_ai/gemini-3.1-pro-preview-customtools\ngemini-2.5-pro-preview-tts\ngemini-robotics-er-1.5-preview\ngemini/gemini-robotics-er-1.5-preview\ngemini/gemini-robotics-er-2-preview\ngemini/gemini-robotics-er-1.6-preview\ngemini-2.5-computer-use-preview-10-2025\ngemini-embedding-001\ngemini-embedding-2-preview\ngemini-embedding-2\nvertex_ai/gemini-embedding-2-preview\nvertex_ai/gemini-embedding-2\ngemini-flash-experimental\ngemini/gemini-embedding-001\ngemini/gemini-embedding-2-preview\ngemini/gemini-embedding-2\ngemini/gemini-1.5-flash\ngemini/gemini-2.0-flash\ngemini/gemini-2.0-flash-001\ngemini/gemini-2.0-flash-lite\ngemini/gemini-2.5-flash\ngemini/gemini-2.5-flash-image\ngemini/gemini-3-pro-image\ngemini/gemini-3-pro-image-preview\ngemini/gemini-3.1-flash-image\ngemini/gemini-3.1-flash-image-preview\ngemini/deep-research-pro-preview-12-2025\ngemini/gemini-2.5-flash-lite\ngemini/gemini-2.5-flash-lite-preview-09-2025\ngemini/gemini-2.5-flash-preview-09-2025\ngemini/gemini-flash-latest\ngemini/gemini-flash-lite-latest\ngemini/gemini-2.5-flash-lite-preview-06-17\ngemini/gemini-2.5-flash-preview-tts\ngemini/gemini-2.5-pro\ngemini/gemini-2.5-computer-use-preview-10-2025\ngemini/gemini-3-pro-preview\ngemini/gemini-3.1-flash-lite-preview\ngemini/gemini-3.1-flash-lite\ngemini/gemini-3.5-flash-lite\ngemini/gemini-3-flash-preview\ngemini/gemini-3.5-flash\ngemini/gemini-3.6-flash\ngemini/gemini-omni-flash-preview\ngemini/gemini-3.1-pro-preview\ngemini/gemini-3.1-pro-preview-customtools\ngemini-3-flash-preview\ngemini-omni-flash-preview\ngemini-3.5-flash\ngemini-3.6-flash\ngemini/gemini-2.5-pro-preview-tts\ngemini/gemini-exp-1114\ngemini/gemini-exp-1206\ngemini/gemini-gemma-2-27b-it\ngemini/gemini-gemma-2-9b-it\ngemini/gemma-3-27b-it\ngemini/imagen-3.0-fast-generate-001\ngemini/imagen-3.0-generate-001\ngemini/imagen-3.0-generate-002\ngemini/imagen-4.0-fast-generate-001\ngemini/imagen-4.0-generate-001\ngemini/imagen-4.0-ultra-generate-001\ngemini/learnlm-1.5-pro-experimental\ngemini/lyria-3-clip-preview\ngemini/lyria-3-pro-preview\ngemini/veo-2.0-generate-001\ngemini/veo-3.1-fast-generate-preview\ngemini/veo-3.1-generate-preview\ngemini/veo-3.1-lite-generate-preview\ngemini/veo-3.1-fast-generate-001\ngemini/veo-3.1-generate-001\ngithub_copilot/gemini-2.5-pro\ngithub_copilot/gemini-3-pro-preview\ngmi/google/gemini-3-pro-preview\ngmi/google/gemini-3-flash-preview\noci/google.gemini-2.5-flash\noci/google.gemini-2.5-pro\noci/google.gemini-2.5-flash-lite\nopenrouter/google/gemini-2.0-flash-001\nopenrouter/google/gemini-2.5-flash\nopenrouter/google/gemini-2.5-pro\nopenrouter/google/gemini-3-pro-preview\nopenrouter/google/gemini-3-flash-preview\nopenrouter/google/gemini-3.1-flash-lite-preview\nopenrouter/google/gemini-3.1-flash-lite\nopenrouter/google/gemini-3.1-pro-preview\nperplexity/google/gemini-3-pro-preview\nperplexity/google/gemini-3-flash-preview\nperplexity/google/gemini-2.5-pro\nperplexity/google/gemini-2.5-flash\nreplicate/google/gemini-3-pro\nreplicate/google/gemini-2.5-flash\nvercel_ai_gateway/google/gemini-2.0-flash\nvercel_ai_gateway/google/gemini-2.0-flash-lite\nvercel_ai_gateway/google/gemini-2.5-flash\nvercel_ai_gateway/google/gemini-2.5-pro\nvercel_ai_gateway/google/gemini-embedding-001\nvertex_ai/gemini-2.5-flash-image\nvertex_ai/gemini-3-pro-image\nvertex_ai/gemini-3-pro-image-preview\nvertex_ai/gemini-3.1-flash-image\nvertex_ai/gemini-3.1-flash-image-preview\nvertex_ai/gemini-3.1-flash-lite-preview\nvertex_ai/gemini-3.1-flash-lite\nvertex_ai/gemini-3.5-flash-lite\ngemini-2.0-flash-exp-image-generation\ngemini/gemini-2.0-flash-exp-image-generation\ngemini/gemini-2.0-flash-lite-001\ngemini-2.5-flash-native-audio-latest\ngemini-2.5-flash-native-audio-preview-09-2025\ngemini-2.5-flash-native-audio-preview-12-2025\ngemini-3.1-flash-live-preview\ngemini/gemini-2.5-flash-native-audio-latest\ngemini/gemini-2.5-flash-native-audio-preview-09-2025\ngemini/gemini-2.5-flash-native-audio-preview-12-2025\ngemini/gemini-3.1-flash-live-preview\ngemini-2.5-flash-preview-tts\ngemini-flash-latest\ngemini-flash-lite-latest\ngemini-pro-latest\ngemini/gemini-pro-latest\ngemini-exp-1206\n### The following ones are listed by OpenRouter but not LiteLLM (may still work)\n\n<__main__.BackupChat>"}

    {'role': 'user', 'content': '```python\n%time\nbc = c("gemini/gemini-flash-lite-latest")\nbc("hi")\n```\nOutput: CPU times: user 3 us, sys: 0 ns, total: 3 us\nWall time: 5.96 us\n\nHello! How can I help you with your `solveit_dmtools` module today?\n\n<details markdown="1">\n\n- id: `Um54avDDEvyc-8YPx5OY4Qg`\n- model: `gemini-flash-lite-latest`\n- finish_reason: `stop`\n- usage: `Usage(completion_tokens=19, prompt_tokens=7869, total_tokens=7888, completion_tokens_details=CompletionTokensDetailsWrapper(accepted_prediction_tokens=None, audio_tokens=None, reasoning_tokens=None, rejected_prediction_tokens=None, text_tokens=19, image_tokens=None, video_tokens=None), prompt_tokens_details=PromptTokensDetailsWrapper(audio_tokens=None, cached_tokens=None, text_tokens=7869, image_tokens=None, video_tokens=None), cache_read_input_tokens=None)`\n\n</details>'}

    {'role': 'user', 'content': '**Prompt (gemini/gemini-flash-lite-latest):** hi'}

    {'role': 'assistant', 'content': 'Hello! How can I help you with your `solveit_dmtools` module today?\n'}

    {'role': 'user', 'content': '**Prompt (gemini/gemini-flash-lite-latest):** Testing if cell magic works\n'}

    {'role': 'assistant', 'content': '```python\n%%bc\nTesting cell magic execution\n```\n'}

    {'role': 'user', 'content': '```python\n[(h[\'role\'], h[\'content\'][0:200]) for h in bc.hist[-6:]]\n```\nOutput: [(\'user\', \'```python\\n%time\\nbc = c("gemini/gemini-flash-lite-latest")\\nbc("hi")\\n```\\nOutput: CPU times: user 3 us, sys: 0 ns, total: 3 us\\nWall time: 5.96 us\\n\\nHello! How can I help you with your `solveit_dmtools` mo\'), (\'user\', \'**Prompt (gemini/gemini-flash-lite-latest):** hi\'), (\'assistant\', \'Hello! How can I help you with your `solveit_dmtools` module today?\\n\'), (\'assistant\', \'.\'), (\'user\', \'Testing if cell magic works\\n\'), (\'assistant\', \'```python\\n%%bc\\nTesting cell magic execution\\n```\')]'}

    {'role': 'assistant', 'content': '.'}

    {'role': 'user', 'content': 'Testing line magic!'}

    Message(content='```python\n%bc Testing line magic execution\n```', role='assistant', tool_calls=None, function_call=None, images=[], thinking_blocks=[], provider_specific_fields={'thought_signatures': ['EjQKMgERTTIP07OqTlQFoODgy3VYUcDZ9iMq6I6fcPeK9qu+CFUTeVyjw3ORLx7LLvChvfEK']})

``` python
lisette_md = read_url("https://lisette.answer.ai/")
lisette_md[0:10]
```

    '[ lisette '

``` python
# bc = c("gemini/gemini-flash-lite-latest")
# bc = c("claude-haiku-4-5")
bc = c("openrouter/openai/gpt-5.2-codex")
# bc = c("openrouter/openai/gpt-5-mini")
# bc = c("openrouter/mistralai/mistral-7b-instruct:free")
```

The following gets commented out when run (uncommented now so you can
run in a test)

``` python
bc("Can you please teach me about Lisette? Only use the info in $`lisette_md`.")
```

I don’t have access to `lisette_md` yet. Could you add it with
`bc.add_vars("lisette_md")` (or `bc.add_vars(["lisette_md"])`) and then
ask again?

<details>

- id: `gen-1786277542-GqL7d4kVz4pNh2hm7kaZ`
- model: `openai/gpt-5.2-codex`
- finish_reason: `stop`
- usage:
  `Usage(completion_tokens=61, prompt_tokens=15437, total_tokens=15498, completion_tokens_details=CompletionTokensDetailsWrapper(accepted_prediction_tokens=None, audio_tokens=0, reasoning_tokens=0, rejected_prediction_tokens=None, text_tokens=None, image_tokens=0, video_tokens=None), prompt_tokens_details=PromptTokensDetailsWrapper(audio_tokens=0, cache_write_tokens=0, cached_tokens=0, text_tokens=None, image_tokens=None, video_tokens=0, cache_creation_tokens=0), cost=0.02786875, is_byok=False, cost_details={'upstream_inference_cost': 0.02786875, 'upstream_inference_prompt_cost': 0.02701475, 'upstream_inference_completions_cost': 0.000854})`

</details>

**Prompt (openrouter/openai/gpt-5.2-codex):** Can you please teach me
about Lisette? Only use the info in \[sanitized var: lisette_md\].

##### 🤖Reply🤖<!-- SOLVEIT_SEPARATOR_7f3a9b2c -->

I don’t have access to `lisette_md` yet. Could you add it with
`bc.add_vars("lisette_md")` (or `bc.add_vars(["lisette_md"])`) and then
ask again?

``` json
{"model": "openrouter/openai/gpt-5.2-codex", "prompt_tokens": 15437, "completion_tokens": 61, "total_tokens": 15498, "cached_tokens": 0, "cache_creation_tokens": 0, "cost": 0.02786875}
```

``` python
# bc.add_vars('lisette_md')
# bc("Can you tell me about the library now, based only on the variable, elevator pitch plus example code from the source. I know you are being Socratic but please give answers and not questions on this one.")
```

Here’s a concise elevator pitch and an example drawn only from the
provided `lisette_md`:

**Elevator pitch**  
Lisette is a wrapper around the LiteLLM Python SDK that makes it easy to
use 100+ LLM providers through a single, OpenAI‑style interface. It
automates the boilerplate (stateful chat, tools, search, streaming,
async, caching) while keeping full control in your hands.

**Example code (from the source)**

``` python
from lisette import *
models = ["gemini/gemini-3-flash-preview", "claude-opus-4-6", "openai/gpt-4.1"]
chat = Chat(models[0])
res = chat("Please tell me about yourself in one brief sentence.")
display(res)
```

<details>

- id: `gen-1786277569-XhNe2WFCpznTZVuylysc`
- model: `openai/gpt-5.2-codex`
- finish_reason: `stop`
- usage:
  `Usage(completion_tokens=169, prompt_tokens=21231, total_tokens=21400, completion_tokens_details=CompletionTokensDetailsWrapper(accepted_prediction_tokens=None, audio_tokens=0, reasoning_tokens=0, rejected_prediction_tokens=None, text_tokens=None, image_tokens=0, video_tokens=None), prompt_tokens_details=PromptTokensDetailsWrapper(audio_tokens=0, cache_write_tokens=0, cached_tokens=15232, text_tokens=None, image_tokens=None, video_tokens=0, cache_creation_tokens=0), cost=0.01552985, is_byok=False, cost_details={'upstream_inference_cost': 0.01552985, 'upstream_inference_prompt_cost': 0.01316385, 'upstream_inference_completions_cost': 0.002366})`

</details>

**Prompt (openrouter/openai/gpt-5.2-codex):** Can you tell me about the
library now, based only on the variable, elevator pitch plus example
code from the source. I know you are being Socratic but please give
answers and not questions on this one.

##### 🤖Reply🤖<!-- SOLVEIT_SEPARATOR_7f3a9b2c -->

Here’s a concise elevator pitch and an example drawn only from the
provided `lisette_md`:

**Elevator pitch**  
Lisette is a wrapper around the LiteLLM Python SDK that makes it easy to
use 100+ LLM providers through a single, OpenAI‑style interface. It
automates the boilerplate (stateful chat, tools, search, streaming,
async, caching) while keeping full control in your hands.

**Example code (from the source)**

``` python
from lisette import *
models = ["gemini/gemini-3-flash-preview", "claude-opus-4-6", "openai/gpt-4.1"]
chat = Chat(models[0])
res = chat("Please tell me about yourself in one brief sentence.")
display(res)
```

``` json
{"model": "openrouter/openai/gpt-5.2-codex", "prompt_tokens": 21231, "completion_tokens": 169, "total_tokens": 21400, "cached_tokens": 15232, "cache_creation_tokens": 0, "cost": 0.015529850000000001}
```

``` python
bc = c("gemini/gemini-3.5-flash")
bc("Can you use tools? For example can you read https://llmstxt.org/index.md and tell me about it? Fetch it, don't store it, give the elevator pitch please.")
```

Yes, I do have access to the `read_url` tool! I can use it to fetch and
read contents directly from the web.

Before I go ahead and fetch `https://llmstxt.org/index.md` to give you
that elevator pitch, **could you please confirm if you’d like me to
proceed with reading this URL?** (We ask for confirmation first since
external web requests can be expensive or resource-intensive).

Once you give the green light, I will fetch it and summarize it for you.
In the meantime, what got you interested in the `llms.txt` proposal?

<details>

- id: `1254as_XDMed-8YPwL2n6AE`
- model: `gemini-3.5-flash`
- finish_reason: `stop`
- usage:
  `Usage(completion_tokens=986, prompt_tokens=19089, total_tokens=20075, completion_tokens_details=CompletionTokensDetailsWrapper(accepted_prediction_tokens=None, audio_tokens=None, reasoning_tokens=857, rejected_prediction_tokens=None, text_tokens=129, image_tokens=None, video_tokens=None), prompt_tokens_details=PromptTokensDetailsWrapper(audio_tokens=None, cached_tokens=None, text_tokens=19089, image_tokens=None, video_tokens=None), cache_read_input_tokens=None)`

</details>

**Prompt (gemini/gemini-3.5-flash):** Can you use tools? For example can
you read https://llmstxt.org/index.md and tell me about it? Fetch it,
don’t store it, give the elevator pitch please.

##### 🤖Reply🤖<!-- SOLVEIT_SEPARATOR_7f3a9b2c -->

Yes, I do have access to the `read_url` tool! I can use it to fetch and
read contents directly from the web.

Before I go ahead and fetch `https://llmstxt.org/index.md` to give you
that elevator pitch, **could you please confirm if you’d like me to
proceed with reading this URL?** (We ask for confirmation first since
external web requests can be expensive or resource-intensive).

Once you give the green light, I will fetch it and summarize it for you.
In the meantime, what got you interested in the `llms.txt` proposal?

``` json
{"model": "gemini/gemini-3.5-flash", "prompt_tokens": 19089, "completion_tokens": 986, "total_tokens": 20075, "cached_tokens": 0, "cache_creation_tokens": 0, "cost": 0.0375075}
```

``` python
%bc go ahead
```

Here is the elevator pitch for the `/llms.txt` proposal:

### **The `/llms.txt` Elevator Pitch**

**`/llms.txt`** is a proposed standard for website owners to place a
curated, LLM-friendly Markdown file at the root of their domain (similar
to `/robots.txt`).

- **The Problem:** Modern LLMs often need real-time access to online
  documentation (especially APIs and coding libraries), but scraping
  complex HTML structures (with ads, menus, and JS) is imprecise, and
  websites in their entirety are often too large for small context
  windows.
- **The Solution:** A standard `/llms.txt` file provides a concise,
  structured markdown summary of the site, paired with a curated
  directory of links pointing directly to clean, raw `.md` versions of
  the documentation pages.

It is designed to be highly readable by both humans and LLMs, easily
parseable by regex or classic programming tools, and optimized for
real-time *inference* (on-demand context retrieval) rather than model
training.

------------------------------------------------------------------------

Would you like to explore how you can automatically generate an
`llms.txt` file for your own projects using tools like `nbdev` or any of
the existing integrations?

<details>

- id: `-m54aoXqCuKM-8YPwdWVyAg`
- model: `gemini-3.5-flash`
- finish_reason: `stop`
- usage:
  `Usage(completion_tokens=796, prompt_tokens=22666, total_tokens=23462, completion_tokens_details=CompletionTokensDetailsWrapper(accepted_prediction_tokens=None, audio_tokens=None, reasoning_tokens=537, rejected_prediction_tokens=None, text_tokens=259, image_tokens=None, video_tokens=None), prompt_tokens_details=PromptTokensDetailsWrapper(audio_tokens=None, cached_tokens=16322, text_tokens=6344, image_tokens=None, video_tokens=None), cache_read_input_tokens=16322)`

</details>

**Prompt (gemini/gemini-3.5-flash):** go ahead

##### 🤖Reply🤖<!-- SOLVEIT_SEPARATOR_7f3a9b2c -->

``` json
{
  "id": "cnveqljf__thought__EvIDCu8DARFNMg9dkmRsckyIBEhC/kBdM9B871DE9F259zzAlfODYO98/GwC8350xb7T1AetTASn4h2X93vAvPbchhS+nPGG9lot+Xwsd3AR1dEVPecXnMh5uqmBMVacWPYOg0XrTU1fmEcMb6szngdJecshTXoe4bN/vNwFjGTvakEqwLjGacwKCgt242qQ0DDNERwnYgn8AjDzfDF3Nfuy8buSJ4ES13JsZhe5HLT4yM8ap/o/wuEE/I7aI0K3I1gnAN26k1IBhASd/z/1VRzfdwosElGDg12CR2EZ6slidTbFgwApj0iGyKnvp28MSIK/28tgupfxgOHmp/GJjEsUc3Jxwn5A3sltJdfP9DRPNJJzK9Ls/MecWPB6zwfR8JRFGoBKU52LZ6Q39kubdzWqELQrheuhpwT/LH/q673Yoj+JLJs/Of0ISt23PlV/IA1PeLq73g4FClAGxL3gipeCt6R81rOJZWez/nTAdNOVdr78XELgSsN41XuiuRavvPhS0qKnkapXRp22JLfKCEytga3+kEUvm+v+jfE5Yk9Jgnnf92p9+ISRlXaRSJAUsuEQJE7rET1Hyq0c9s0toGkNberB1mFjzkBUO3dIJfLu3M7/WEYtSdcgLcuJ1PnI+8X7ozrldyL0aimyF2oiny4nIRfZ",
  "name": "read_url",
  "args": {
    "url": "https://llmstxt.org/index.md"
  },
  "result": "# The /llms.txt file\nJeremy Howard\n2024-09-03\n\n## Background\n\nLarge language models increasingly rel<TRUNCATED>"
}
```

Here is the elevator pitch for the `/llms.txt` proposal:

### **The `/llms.txt` Elevator Pitch**

**`/llms.txt`** is a proposed standard for website owners to place a
curated, LLM-friendly Markdown file at the root of their domain (similar
to `/robots.txt`).

- **The Problem:** Modern LLMs often need real-time access to online
  documentation (especially APIs and coding libraries), but scraping
  complex HTML structures (with ads, menus, and JS) is imprecise, and
  websites in their entirety are often too large for small context
  windows.
- **The Solution:** A standard `/llms.txt` file provides a concise,
  structured markdown summary of the site, paired with a curated
  directory of links pointing directly to clean, raw `.md` versions of
  the documentation pages.

It is designed to be highly readable by both humans and LLMs, easily
parseable by regex or classic programming tools, and optimized for
real-time *inference* (on-demand context retrieval) rather than model
training.

------------------------------------------------------------------------

Would you like to explore how you can automatically generate an
`llms.txt` file for your own projects using tools like `nbdev` or any of
the existing integrations?

``` json
{"model": "gemini/gemini-3.5-flash", "prompt_tokens": 42311, "completion_tokens": 965, "total_tokens": 43276, "cached_tokens": 32643, "cache_creation_tokens": 0, "cost": 0.028083450000000003}
```

``` python
mdh_md = read_url("https://raw.githubusercontent.com/AnswerDotAI/toolslm/refs/heads/main/nbs/04_md_hier.ipynb")
bc("How can I use toolslm.md_hier to parse $`lisette_md`? Please give code", var_names='lisette_md mdh_md')
```

You can parse the markdown string directly using `create_heading_dict`
like this:

``` python
from toolslm.md_hier import create_heading_dict

parsed_doc = create_heading_dict(lisette_md)
```

Now that you have the parsed document, what are you looking to do with
it? For example, would you like to view its outline or search for a
specific term?

<details>

- id: `tG94aoPfGqjSjMcPlY3I6Q4`
- model: `gemini-3.5-flash`
- finish_reason: `stop`
- usage:
  `Usage(completion_tokens=776, prompt_tokens=39124, total_tokens=39900, completion_tokens_details=CompletionTokensDetailsWrapper(accepted_prediction_tokens=None, audio_tokens=None, reasoning_tokens=687, rejected_prediction_tokens=None, text_tokens=89, image_tokens=None, video_tokens=None), prompt_tokens_details=PromptTokensDetailsWrapper(audio_tokens=None, cached_tokens=32692, text_tokens=6432, image_tokens=None, video_tokens=None), cache_read_input_tokens=32692)`

</details>

**Prompt (gemini/gemini-3.5-flash):** How can I use toolslm.md_hier to
parse \[sanitized var: lisette_md\]? Please give code

##### 🤖Reply🤖<!-- SOLVEIT_SEPARATOR_7f3a9b2c -->

You can parse the markdown string directly using `create_heading_dict`
like this:

``` python
from toolslm.md_hier import create_heading_dict

parsed_doc = create_heading_dict(lisette_md)
```

Now that you have the parsed document, what are you looking to do with
it? For example, would you like to view its outline or search for a
specific term?

``` json
{"model": "gemini/gemini-3.5-flash", "prompt_tokens": 39124, "completion_tokens": 776, "total_tokens": 39900, "cached_tokens": 32692, "cache_creation_tokens": 0, "cost": 0.0215358}
```

``` python
bc.tools
```

    [<function ipykernel_helper.core.read_url(url: str, as_md: bool = True, extract_section: bool = True, selector: str = None, ai_img: bool = False)>]

``` python
def bad_joke() -> str:
    "Returns a bad joke"
    return "Why are engineers bad at telling jokes timing?"
```

``` python
bc.add_tools('bad_joke')
bc("Can you tell me a bad joke, using your tools?")
```

I used the `bad_joke` tool to retrieve this one for you:

“Why are engineers bad at telling jokes timing?”

How’s that for a classic, slightly awkward bad joke?

<details>

- id: `G3B4aoruKLef-8YPjIfN2A0`
- model: `gemini-3.5-flash`
- finish_reason: `stop`
- usage:
  `Usage(completion_tokens=102, prompt_tokens=39922, total_tokens=40024, completion_tokens_details=CompletionTokensDetailsWrapper(accepted_prediction_tokens=None, audio_tokens=None, reasoning_tokens=61, rejected_prediction_tokens=None, text_tokens=41, image_tokens=None, video_tokens=None), prompt_tokens_details=PromptTokensDetailsWrapper(audio_tokens=None, cached_tokens=32677, text_tokens=7245, image_tokens=None, video_tokens=None), cache_read_input_tokens=32677)`

</details>

**Prompt (gemini/gemini-3.5-flash):** Can you tell me a bad joke, using
your tools?

##### 🤖Reply🤖<!-- SOLVEIT_SEPARATOR_7f3a9b2c -->

``` json
{
  "id": "atg33et5__thought__ErcDCrQDARFNMg94gfQh2qJIG3G9SWwuFCQQYCZP1L+lEmf8WPQ2z3C9G12YhN684QBI7QkOMZI4NoAk/6j58UFl5tOVd02sOPWrnCbULZ6NCbovSPGYBp8lvcLcCHSme+u9BTbAjgTHKolTv/wbDo/s7qXnVmHVRAI1dLP5T814oD9QLko9NdGOAJC728qhmWSHWkih1RBqaYTztf946hBhZTG2qtzT1HTcFuWbBsGaZ0TsdT+2fxUDn0tOAdeeEMuQK5giwJGR5RY0inHjM0jzk/V9APJTyxQxcL/daRIewcpMhkghkOWJZCB9M0d85yrR88fumdl4fRYau3FO6FW6Fi4CPRbfBF2eA6P4T5NmMH8Mo9Td/31H4vgYhlc6jM877HirMM8ee5jscl7fFfpeZeDbj+R1Aw5je4urWh5ULpZ1x50DoGYs7oW4n+TihprwKIy+6S8O4RYVl7qj0hxV/DwYYd+xPUVOhgYH0WMiNJYHAmCpJxtHUYFUttQY8UbCgPbKNf32hy5lTnjEHcsHr+D3D+D26kB6gKr/hTiJ3ZNUobM/tLo4UgkWL0ux4v9oT/ewu+ysJw==",
  "name": "bad_joke",
  "args": {},
  "result": "Why are engineers bad at telling jokes timing?"
}
```

I used the `bad_joke` tool to retrieve this one for you:

“Why are engineers bad at telling jokes timing?”

How’s that for a classic, slightly awkward bad joke?

``` json
{"model": "gemini/gemini-3.5-flash", "prompt_tokens": 79696, "completion_tokens": 228, "total_tokens": 79924, "cached_tokens": 32677, "cache_creation_tokens": 0, "cost": 0.07748205}
```
