dhb

Backup Chat for SolveIt using dialoghelper and lisette
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>
%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?

  • 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)

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

🤖Reply🤖

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

{"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}
%%bc
Testing if cell magic works
%%bc
Testing cell magic execution
  • 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)

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

🤖Reply🤖
%%bc
Testing cell magic execution
{"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}
[(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```')]
%bc Testing line magic!
%bc Testing line magic execution
  • 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)

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

🤖Reply🤖
%bc Testing line magic execution
{"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}
[(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```')]
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']})
lisette_md = read_url("https://lisette.answer.ai/")
lisette_md[0:10]
'[ lisette '
# 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)

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?

  • 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})

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

🤖Reply🤖

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?

{"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}
# 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)

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)
  • 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})

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🤖

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)

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)
{"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}
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?

  • 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)

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🤖

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?

{"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}
%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?

  • 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)

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

🤖Reply🤖
{
  "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?

{"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}
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:

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?

  • 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)

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

🤖Reply🤖

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

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?

{"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}
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)>]
def bad_joke() -> str:
    "Returns a bad joke"
    return "Why are engineers bad at telling jokes timing?"
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?

  • 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)

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

🤖Reply🤖
{
  "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?

{"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}