Skip to main content

solveit_dmtools

Usage and examples

from dialoghelper.core import *
from solveit_dmtools import * # core, dhb, dhp, fab
import solveit_dmtools # This will be just for the next cell to print the version
print(solveit_dmtools.__version__); del solveit_dmtools
0.0.30

TL;DR:

module what it gives you try
dhb chat with any LiteLLM model you have a key for bc = dhb.c('gemini/gemini-flash-lite-latest'); bc("hi")
dhp Polya’s four stages as one-liners that write prompt cells dhp.help(), dhp.u.summary()
fab 200+ open-source fabric prompts fab.p.summarize()

Full docs for each module are on its own page; await add_msg(modulename.doc, msg_type='note') prints more details in a cell if you’d rather stay here.

dhb - Backup Chat for SolveIt

First calling with no model will prompt you to type in part of a model name to search

dhb.c() # `dhb.c()` with no model prompts you for a substring and lists matching models
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/gem':
gemini/gemini-live-2.5-flash-preview-native-audio-09-2025
gemini/gemini-robotics-er-1.5-preview
gemini/gemini-robotics-er-2-preview
gemini/gemini-robotics-er-1.6-preview
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/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/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/gemini-2.0-flash-exp-image-generation
gemini/gemini-2.0-flash-lite-001
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/gemini-pro-latest
### The following ones are listed by OpenRouter but not LiteLLM (may still work)

<solveit_dmtools.dhb.BackupChat>
bc = dhb.c("openrouter/openai/gpt-5.2-codex")
# bc = dhb.c("openrouter/moonshotai/kimi-k2.5")

The following will be automatically commented out when run, then a prompt cell is added after it with input/output from the other LLM.

bc("Hi, can you use tools?")

Yes—I can use the available tools (e.g., read_url), but I’ll ask for confirmation before accessing any URLs. What would you like to do?

  • id: gen-1786095705-Y8KGvpeB6uBr8yLeAjp2
  • model: openai/gpt-5.2-codex
  • finish_reason: stop
  • usage: Usage(completion_tokens=71, prompt_tokens=1738, total_tokens=1809, completion_tokens_details=CompletionTokensDetailsWrapper(accepted_prediction_tokens=None, audio_tokens=0, reasoning_tokens=97, 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.0040355, is_byok=False, cost_details={'upstream_inference_cost': 0.0040355, 'upstream_inference_prompt_cost': 0.0030415, 'upstream_inference_completions_cost': 0.000994})

Prompt (openrouter/openai/gpt-5.2-codex): Hi, can you use tools?

🤖Reply🤖

Yes—I can use the available tools (e.g., read_url), but I’ll ask for confirmation before accessing any URLs. What would you like to do?

{"model": "openrouter/openai/gpt-5.2-codex", "prompt_tokens": 1738, "completion_tokens": 71, "total_tokens": 1809, "cached_tokens": 0, "cache_creation_tokens": 0, "cost": 0.0043995}
def bad_joke():
    "Tells a bad joke"
    return "Why are engineers bad at telling jokes timing?"
bc.add_tools('bad_joke')
len(bc.tool_schemas)
2
%bc Tell me a bad joke using your tools please

Why are engineers bad at telling jokes timing?

  • id: gen-1786095857-BqlLTP3a2yngmpz7ZTGr
  • model: openai/gpt-5.2-codex
  • finish_reason: stop
  • usage: Usage(completion_tokens=13, prompt_tokens=1922, total_tokens=1935, 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=1792, text_tokens=None, image_tokens=None, video_tokens=0, cache_creation_tokens=0), cost=0.0007231, is_byok=False, cost_details={'upstream_inference_cost': 0.0007231, 'upstream_inference_prompt_cost': 0.0005411, 'upstream_inference_completions_cost': 0.000182})

Prompt (openrouter/openai/gpt-5.2-codex): Tell me a bad joke using your tools please

🤖Reply🤖
{
  "id": "call_tXyLFSyyNoargmQNSStFu1c7",
  "name": "bad_joke",
  "args": {},
  "result": "Why are engineers bad at telling jokes timing?"
}

Why are engineers bad at telling jokes timing?

{"model": "openrouter/openai/gpt-5.2-codex", "prompt_tokens": 3811, "completion_tokens": 44, "total_tokens": 3855, "cached_tokens": 1792, "cache_creation_tokens": 0, "cost": 0.00446285}

Prompt (openrouter/openai/gpt-5.2-codex): Tell me a bad joke using your tools please

🤖Reply🤖

I don’t have access to bad_joke in this session. Can you add it with bc.add_tools('bad_joke') (or bc.add_vars_and_tools) and then share it here?

{"model": "openrouter/openai/gpt-5.2-codex", "prompt_tokens": 1874, "completion_tokens": 114, "total_tokens": 1988, "cached_tokens": 1536, "cache_creation_tokens": 0, "cost": 0.0024563}
bc("Can you please read https://raw.githubusercontent.com/AnswerDotAI/fhdaisy/refs/heads/main/README.md and give the elevator pitch and some sample code? No need to store the raw content.")

Reading that URL is fine if you confirm. Want me to fetch it now with read_url?

  • id: gen-1786095913-XosONBUH1ZYe2DxcSfAS
  • model: openai/gpt-5.2-codex
  • finish_reason: stop
  • usage: Usage(completion_tokens=41, prompt_tokens=2650, total_tokens=2691, 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=1664, text_tokens=None, image_tokens=None, video_tokens=0, cache_creation_tokens=0), cost=0.0025907, is_byok=False, cost_details={'upstream_inference_cost': 0.0025907, 'upstream_inference_prompt_cost': 0.0020167, 'upstream_inference_completions_cost': 0.000574})

Prompt (openrouter/openai/gpt-5.2-codex): Can you please read https://raw.githubusercontent.com/AnswerDotAI/fhdaisy/refs/heads/main/README.md and give the elevator pitch and some sample code? No need to store the raw content.

🤖Reply🤖

Reading that URL is fine if you confirm. Want me to fetch it now with read_url?

{"model": "openrouter/openai/gpt-5.2-codex", "prompt_tokens": 2650, "completion_tokens": 41, "total_tokens": 2691, "cached_tokens": 1664, "cache_creation_tokens": 0, "cost": 0.0025907}

The above is built-in to ask for confirmation… some models will not bother to ask each time, though.

%%bc
Yes that
is fine

Here’s a concise summary based on the README you linked.

Elevator pitch
fhdaisy is a Python wrapper that maps DaisyUI components to FastHTML-friendly Python components. Instead of writing raw HTML with class strings (e.g., class="btn btn-primary"), you use components like Btn('Click me', cls='-primary'). Every DaisyUI class becomes a title‑case component, and modifiers can be shortened (btn-primary-primary). There’s also an xtras module with helper builders (e.g., accordions) to reduce repetitive markup.

Sample code (minimal examples)

from fhdaisy import *
c = Btn("Hey there", cls="-primary")
from fhdaisy import *
c = Alert("Saved!", cls="-success -soft")
from fhdaisy.xtras import *
acc = mk_accordion(("Q1", "A1"), ("Q2", "A2"), itemcls="-arrow")

Want a specific example (e.g., cards, forms, modals) or a tiny FastHTML page stub to see these components in context?

  • id: gen-1786095942-VNg6t7CR0H1QZMAR9Fu4
  • model: openai/gpt-5.2-codex
  • finish_reason: stop
  • usage: Usage(completion_tokens=250, prompt_tokens=16259, total_tokens=16509, 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=2944, text_tokens=None, image_tokens=None, video_tokens=0, cache_creation_tokens=0), cost=0.02731645, is_byok=False, cost_details={'upstream_inference_cost': 0.02731645, 'upstream_inference_prompt_cost': 0.02381645, 'upstream_inference_completions_cost': 0.0035})

Prompt (openrouter/openai/gpt-5.2-codex): Yes that is fine

🤖Reply🤖
{
  "id": "call_8gSnY8pSbnzVTUt3UIPFo84Y",
  "name": "read_url",
  "args": {
    "url": "https://raw.githubusercontent.com/AnswerDotAI/fhdaisy/refs/heads/main/README.md",
    "as_md": true,
    "extract_section": true,
    "selector": "",
    "ai_img": false
  },
  "result": "# fhdaisy\n\n\n<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->\n\n`fhdaisy` is a Python wrapp<TRUNCATED>"
}

Here’s a concise summary based on the README you linked.

Elevator pitch
fhdaisy is a Python wrapper that maps DaisyUI components to FastHTML-friendly Python components. Instead of writing raw HTML with class strings (e.g., class="btn btn-primary"), you use components like Btn('Click me', cls='-primary'). Every DaisyUI class becomes a title‑case component, and modifiers can be shortened (btn-primary-primary). There’s also an xtras module with helper builders (e.g., accordions) to reduce repetitive markup.

Sample code (minimal examples)

from fhdaisy import *
c = Btn("Hey there", cls="-primary")
from fhdaisy import *
c = Alert("Saved!", cls="-success -soft")
from fhdaisy.xtras import *
acc = mk_accordion(("Q1", "A1"), ("Q2", "A2"), itemcls="-arrow")

Want a specific example (e.g., cards, forms, modals) or a tiny FastHTML page stub to see these components in context?

{"model": "openrouter/openai/gpt-5.2-codex", "prompt_tokens": 19266, "completion_tokens": 320, "total_tokens": 19586, "cached_tokens": 5376, "cache_creation_tokens": 0, "cost": 0.0297283}

dhp - Polya helpers for SolveIt

Dialog Helper for Polya’s Problem-Solving Method

The dhp module provides quick access to Polya’s four-stage problem-solving process through interactive prompts.

Each stage has multiple questions/prompts:

  • To preview one you can just print it/type its name in a cell and hit Submit
  • e.g. dhp.act.next shows you “(prompt) What is next?”.
  • To execute one you call it by adding () after the name
  • e.g. dhp.act.next() will replace the current message cell with a prompt cell having “What is next?” in it
  • It will be automatically executed, you can hit Esc to stop it and/or Enter to edit the prompt

TYPICAL FLOW:

  • Start with dhp.u (understand) (even briefly) to clarify your understanding of the problem
  • Move to dhp.p (plan) to develop initial strategies
  • Switch between plan and act (or dhp.a, or dhp.x/ execute) as you develop and test approaches
  • Use dhp.r (review) to gain deeper understanding of your approach and findings
  • You might loop back to other steps after dhp.r (review)

If you feel stuck, run dhp.help() and it will submit the prompt cell it creates - SolveIt will help you pick a next prompt!

The problem we want to solve is demonstrating the dhp module

The following cell is from running dhp.help() then running the prompt cell it creates

Please pick an appropriate next-step/prompt from the below:

Dialog Helper for Polya’s Problem-Solving Method

This module provides quick access to Polya’s four-stage problem-solving process through interactive prompts.

Each stage has multiple questions/prompts:

  • To preview one you can just print it/type its name in a cell and hit Submit
  • e.g. dhp.act.next shows you “(prompt) What is next?”.
  • To execute one you call it by adding () after the name
  • e.g. dhp.act.next() will replace the current message cell with a prompt cell having “What is next?” in it
  • It will be automatically executed, you can hit Esc to stop it and/or Enter to edit the prompt

TYPICAL FLOW:

  • Start with dhp.u (understand) (even briefly) to clarify your understanding of the problem
  • Move to dhp.p (plan) to develop initial strategies
  • Switch between plan and act (or dhp.a, or dhp.x/execute) as you develop and test approaches
  • Use dhp.r (review) to gain deeper understanding of your approach and findings
  • You might loop back to other steps after r (review)

If you feel stuck, run dhp.help() and it will submit the prompt cell it creates - SolveIt will help you pick a next prompt!

UNDERSTAND STAGE - Clarify the problem before solving

  • dhp.u.summary() - Creates a prompt cell asking SolveIt to give a concise summary of the problem
  • dhp.u.info() - Creates a prompt cell asking SolveIt to inventory known/unknown information
  • dhp.u.similar() - Creates a prompt cell asking SolveIt if it has seen a similar problem before
  • dhp.u.lateral() - Creates a prompt cell to explore problem relationships and scope
  • dhp.u.related() - Creates a prompt cell to identify similar or simpler problems
  • dhp.u.viz() - Creates a prompt cell asking SolveIt to create a figure or diagram to represent the problem
  • dhp.u.notation() - Creates a prompt cell asking SolveIt to pick suitable notation (symbols for quantities/data, states, transitions)
  • dhp.u.simplest() - Creates a prompt cell asking SolveIt for the simplest way to look at the problem
  • dhp.u.simplify() - Creates a prompt cell asking SolveIt to separate problem parts (break down complex conditions into simpler ones)
  • dhp.u.objective() - Creates a note cell asking you to state the objective in one sentence, without reference to algorithms, models, or data
  • dhp.u.levers() - Creates a note cell asking you to separate what you can control from what you cannot
  • dhp.u.ideal_data() - Creates a prompt cell asking SolveIt what data you would collect if you could measure anything

PLAN STAGE - Develop strategies and approaches

  • dhp.p.chunks() - Creates a prompt cell asking SolveIt to break down the problem into smaller sub-problems
  • dhp.p.partial() - Creates a prompt cell asking SolveIt if there’s a smaller part or representation of the problem to solve
  • dhp.p.known_approach() - Creates a prompt cell asking SolveIt to use a known algorithm or library to solve the problem
  • dhp.p.verifiable() - Creates a prompt cell asking SolveIt how to verify if the solution is consistent and correct
  • dhp.p.backward() - Creates a prompt cell asking SolveIt to work backward from the desired result
  • dhp.p.aux() - Creates a prompt cell asking SolveIt to use an auxiliary element (variable, diagram, or example) to clarify the path
  • dhp.p.analogy() - Creates a prompt cell asking SolveIt to use analogy or similarity to relate the problem to a known solution
  • dhp.p.review() - Creates a prompt cell asking SolveIt to critique the plan of attack (be frank and critical)
  • dhp.p.completeness() - Creates a prompt cell asking SolveIt if you’ve used all the data and taken into account all essential notions

ACT STAGE - Execute your plan while monitoring progress

  • dhp.a.all() - Creates a prompt cell asking SolveIt if we covered all of the data or examples for this step
  • dhp.a.check() - Creates a prompt cell asking SolveIt if this step seems correct
  • dhp.a.doubt() - Creates a prompt cell asking SolveIt if we’re using the right approach
  • dhp.a.next() - Creates a prompt cell asking SolveIt what is next
  • dhp.a.other() - Creates a prompt cell asking SolveIt if there’s another way to look at this
  • dhp.a.partial() - Creates a prompt cell asking SolveIt about intermediate results or milestones to aim for
  • dhp.a.simpler() - Creates a prompt cell asking SolveIt if there was a simpler way to do this step
  • dhp.a.symmetry() - Creates a prompt cell asking SolveIt about symmetries or patterns in the problem to exploit
  • dhp.a.valid() - Creates a prompt cell asking SolveIt if this step was a valid step

REVIEW STAGE - Verify results, reflect on process, and extract lessons

  • dhp.r.all() - Creates a prompt cell asking SolveIt if we covered all of the data or examples for this problem
  • dhp.r.alter() - Creates a prompt cell asking SolveIt for alternative solutions or approaches that might be more efficient or effective
  • dhp.r.general() - Creates a prompt cell asking SolveIt if we can generalize the solution to other similar problems
  • dhp.r.grok() - Creates a note cell with the text “To consider: Can I understand the solution without having to perform all the steps?”
  • dhp.r.learned() - Creates a prompt cell asking SolveIt what lessons have been learned from this
  • dhp.r.mistakes() - Creates a prompt cell asking SolveIt about common mistakes made
  • dhp.r.other() - Creates a prompt cell asking SolveIt if we can derive the result differently
  • dhp.r.principles() - Creates a prompt cell asking SolveIt to identify underlying principles or patterns that emerged during the solution process
  • dhp.r.sanity() - Creates a prompt cell asking SolveIt if the result makes sense and can be verified by substitution or another method
  • dhp.r.simpler() - Creates a prompt cell asking SolveIt if we can derive the result in a simpler way
  • dhp.r.test() - Creates a prompt cell asking SolveIt for different ways to test this
  • dhp.r.framing() - Creates a note cell asking you to reflect on whether you started with the objective or with a tool
  • dhp.r.levers_overlooked() - Creates a prompt cell asking SolveIt what levers you might have overlooked
  • dhp.r.future_data() - Creates a prompt cell asking SolveIt what data you would collect differently next time
🤖Reply🤖

Since the stated problem is simply “demonstrating the dhp module,” the natural starting point is the understand stage. A good first step is dhp.u.summary() — it creates a prompt cell asking for a concise summary of the problem, which will also show what running one of these helpers actually looks like end-to-end.

dhp.u.summary()
{"model": "claude-sonnet-5", "prompt_tokens": 60981, "completion_tokens": 219, "total_tokens": 61200, "cached_tokens": 8701, "cache_creation_tokens": 52278, "cost": 0.009311400000000001}

fab - leverage Daniel Miessler’s fabric module of shared prompts

# Uncomment and submit the line below if you do not already have a copy of fabric in your /app/data/fabric folder
#!cd /app/data; git clone --depth 1 https://github.com/danielmiessler/fabric.git
await add_msg(content=fab.doc, msg_type="note")
'_3719b4eb'

fab - Open Source ‘fabric’ prompts made quickly available in SolveIt

This module leverages over 200 open source LLM prompts that are available in Daniel Miesller’s ‘fabric’ project.

If you import as fab, Submit the following to see an overview of all the prompts: fab.p

HOW TO USE IT

Most Common Syntax: prompt="Your Prompt" in one cell then fab.p.pattern_name() in another, where pattern_name is any of the 200+ available fabric patterns.

MOST IMPORTANT AND USED OPTIONS AND FEATURES

  • Variable Targeting: Use fab.p.pattern_name('variable_name') to process content from a specific variable instead of the default ‘prompt’ variable.

  • Pattern Discovery: Use fab.p.help() (an alias for suggest_pattern()) to get suggestions of which pattern to pick for your prompt.

  • Compression Feature: Use fab.compress() after running a pattern to save tokens by marking the previous cell as skipped and compressing the output to a new note.

  • Default Variable: Most patterns work with a variable called ‘prompt’ by default, making it easy to process your main content.

COMMON PATTERNS

  • For Summarizing Content: fab.p.summarize()
  • For Explaining Code: fab.p.explain_code()
  • For Analyzing Claims: fab.p.analyze_claims()
  • For Extracting Wisdom from Text: fab.p.extract_wisdom()
  • For Creating Quizzes: fab.p.create_quiz()
fab.p.explain_code
Fabric pattern: explain_code - Analyze/explain code, security tool outputs, and configs.
prompt = read_url("https://raw.githubusercontent.com/shuane/blogtopod/refs/heads/main/blogtopod.py")

The next cell is running fab.p.explain_code(), which we then run to get a result.

The call to any fab.p function also adds another cell below that has fab.compress(), which helps to reduce the tokens for later use.

(From fab.p.explain_code folded below) # IDENTITY and PURPOSE

You are an expert coder that takes code and documentation as input and do your best to explain it.

Take a deep breath and think step by step about how to best accomplish this goal using the following steps. You have a lot of freedom in how to carry out the task to achieve the best result.

OUTPUT SECTIONS

  • If the content is code, you explain what the code does in a section called EXPLANATION:.

  • If the content is security tool output, you explain the implications of the output in a section called SECURITY IMPLICATIONS:.

  • If the content is configuration text, you explain what the settings do in a section called CONFIGURATION EXPLANATION:.

  • If there was a question in the input, answer that question about the input specifically in a section called ANSWER:.

OUTPUT

  • Do not output warnings or notes—just the requested sections.

INPUT:

INPUT:

$prompt

🤖Reply🤖

EXPLANATION:

This is a marimo notebook script (a reactive, cell-based Python UI, run with uv run given the inline PEP 723 metadata block at the top) that turns a web article into a two-host podcast, complete with synthesized audio.

Setup cells

  • Declares dependencies (marimo, gaspare, openai, pydub, contextkit) via the # ///script header, so uv run can provision them automatically.
  • Imports OpenAI for text-to-speech, gaspare (aliased gp) for calling an LLM (Gemini) to write the script, contextkit.read_url to fetch and clean web content, and pydub.AudioSegment for stitching audio clips together.
  • Asserts that OPENAI_API_KEY and GEMINI_API_KEY are set in the environment, then creates an OpenAI client.

UI form

  • Builds a marimo form with: source URL, output filename, two host names, two OpenAI TTS voice names, a slider for approximate episode length (3–60 min), and a “Run!” button. Nothing downstream executes until the button is clicked (mo.stop(not run_button.value) gates every later cell).

Helper functions

  • clean_text_for_tts: repairs common mis-encoded punctuation (broken ellipses, curly quotes), strips non-ASCII characters, collapses whitespace, and NFKD-normalizes the text so it’s safe to feed to a TTS engine.
  • parse_podcast_script: parses a raw script into a list of {"speaker": ..., "text": ...} segments, splitting on lines that start with HOSTNAME: (case-insensitive match against the two configured host names) and cleaning each segment’s text.

Pipeline (only runs once “Run!” is clicked)

  1. read_url fetches and converts the source page into text/markdown.
  2. A large prompt is built asking Gemini to convert that source material into a two-host podcast script, with detailed instructions on hosts’ personalities, structure, tone, and a strict HOSTNAME: text output format, targeting the requested approximate spoken length.
  3. gp.Chat(model="gemini-2.5-pro-preview-03-25") sends that prompt and the response is parsed into segments via parse_podcast_script.
  4. For each segment, client.audio.speech.with_streaming_response.create (OpenAI’s gpt-4o-mini-tts model) synthesizes audio using the voice and persona instructions matching that speaker, streaming bytes into a BytesIO buffer and loading it as a pydub.AudioSegment.
  5. All segments are concatenated (sum(parts, AudioSegment.empty())) into a single preview audio object, previewed inline, then exported to the configured output MP3 path.
  6. The generated script text is also cleaned and written to a companion _script.md file, and rendered inline as markdown for review.

Overall purpose: given nothing but a URL and some UI settings, the script scrapes an article, has an LLM turn it into a natural two-host dialogue script of a target length, and synthesizes it into a playable/downloadable podcast MP3 with two distinct AI voices — all inside an interactive marimo app.

{"model": "claude-sonnet-5", "prompt_tokens": 68864, "completion_tokens": 1259, "total_tokens": 70123, "cached_tokens": 8701, "cache_creation_tokens": 60161, "cost": 0.011273850000000002}

Prompt (From fab.p.explain_code) for $prompt

EXPLANATION:

This is a marimo notebook script (a reactive, cell-based Python UI, run with uv run given the inline PEP 723 metadata block at the top) that turns a web article into a two-host podcast, complete with synthesized audio.

Setup cells

  • Declares dependencies (marimo, gaspare, openai, pydub, contextkit) via the # ///script header, so uv run can provision them automatically.
  • Imports OpenAI for text-to-speech, gaspare (aliased gp) for calling an LLM (Gemini) to write the script, contextkit.read_url to fetch and clean web content, and pydub.AudioSegment for stitching audio clips together.
  • Asserts that OPENAI_API_KEY and GEMINI_API_KEY are set in the environment, then creates an OpenAI client.

UI form

  • Builds a marimo form with: source URL, output filename, two host names, two OpenAI TTS voice names, a slider for approximate episode length (3–60 min), and a “Run!” button. Nothing downstream executes until the button is clicked (mo.stop(not run_button.value) gates every later cell).

Helper functions

  • clean_text_for_tts: repairs common mis-encoded punctuation (broken ellipses, curly quotes), strips non-ASCII characters, collapses whitespace, and NFKD-normalizes the text so it’s safe to feed to a TTS engine.
  • parse_podcast_script: parses a raw script into a list of {"speaker": ..., "text": ...} segments, splitting on lines that start with HOSTNAME: (case-insensitive match against the two configured host names) and cleaning each segment’s text.

Pipeline (only runs once “Run!” is clicked)

  1. read_url fetches and converts the source page into text/markdown.
  2. A large prompt is built asking Gemini to convert that source material into a two-host podcast script, with detailed instructions on hosts’ personalities, structure, tone, and a strict HOSTNAME: text output format, targeting the requested approximate spoken length.
  3. gp.Chat(model="gemini-2.5-pro-preview-03-25") sends that prompt and the response is parsed into segments via parse_podcast_script.
  4. For each segment, client.audio.speech.with_streaming_response.create (OpenAI’s gpt-4o-mini-tts model) synthesizes audio using the voice and persona instructions matching that speaker, streaming bytes into a BytesIO buffer and loading it as a pydub.AudioSegment.
  5. All segments are concatenated (sum(parts, AudioSegment.empty())) into a single preview audio object, previewed inline, then exported to the configured output MP3 path.
  6. The generated script text is also cleaned and written to a companion _script.md file, and rendered inline as markdown for review.

Overall purpose: given nothing but a URL and some UI settings, the script scrapes an article, has an LLM turn it into a natural two-host dialogue script of a target length, and synthesizes it into a playable/downloadable podcast MP3 with two distinct AI voices — all inside an interactive marimo app.

{"model": "claude-sonnet-5", "prompt_tokens": 68864, "completion_tokens": 1259, "total_tokens": 70123, "cached_tokens": 8701, "cache_creation_tokens": 60161, "cost": 0.011273850000000002}

To go back full-circle, we can have a different LLM validate the results… Note that the %%bc cell magic allows you to pick a different BackupChat instance (%bc line magic does not)

gemini = dhb.c("gemini/gemini-3.1-pro-preview")
%%bc gemini
Please compare the answer above to https://raw.githubusercontent.com/shuane/blogtopod/refs/heads/main/README.md and comment on any issues? It's OK, it's not a big download, so go ahead and read it!

The previous code explanation was remarkably accurate and aligns perfectly with what the README.md describes! The explain_code prompt did a great job dissecting the pipeline (Contextkit -> Gemini 2.5 Pro -> OpenAI TTS -> Pydub) exactly as the author intended.

However, cross-referencing with the README.md reveals a few practical, user-facing details that the raw code analysis missed:

  1. Python Version Constraint: The README explicitly notes that you need a Python version before 3.13 (specifically recommending 3.12). This is due to a limitation with the pydub audio processing library.
  2. Execution Method: While the code explanation noted it can be run via uv run due to the PEP 723 block, the README specifically recommends running it headlessly/sandboxed with: uvx marimo run --sandbox https://raw.githubusercontent.com/shuane/blogtopod/refs/heads/main/blogtopod.py
  3. Patience Required: The README gives the user a heads-up that clicking “Run!” might take 6 to 10 minutes to process.

Are there any specific parts of the pipeline (like how it chunks the script or handles the TTS streams) you want to dive deeper into?

  • id: OW14apiNOcyB-8YPgcW2mAI
  • model: gemini-3.1-pro-preview
  • finish_reason: stop
  • usage: Usage(completion_tokens=1235, prompt_tokens=10316, total_tokens=11551, completion_tokens_details=CompletionTokensDetailsWrapper(accepted_prediction_tokens=None, audio_tokens=None, reasoning_tokens=948, rejected_prediction_tokens=None, text_tokens=287, image_tokens=None, video_tokens=None), prompt_tokens_details=PromptTokensDetailsWrapper(audio_tokens=None, cached_tokens=8059, text_tokens=2257, image_tokens=None, video_tokens=None), cache_read_input_tokens=8059)

Prompt (gemini/gemini-3.1-pro-preview): Please the answer above to https://raw.githubusercontent.com/shuane/blogtopod/refs/heads/main/README.md and comment on any issues? It’s OK, it’s not a big download, so go ahead and read it!

🤖Reply🤖
{
  "id": "g4hepim7__thought__EswLCskLARFNMg8h/YeY1L8Iw6DSZ4ywSjFnmvqeqxTw5rQAWun4NunuJMsRwA4dSZ7dHOJ1lbKO6uZQG3z9Hp2y7/UGagi2YVINseLt7LSetvJBKvIJjobShhDo2Zz3N2B5SSMdlQ71bB7GbtICA8TVjaiU+IsH2nHJLElbBY6Mzi3UuHkniGQcEtay0BqY0D/sICEfa43WaEm698v/Y6CmGrGqZwDP441vxCTErekJBBd43A9p89ydbZTMawTm1KrnTMgnxEZcaz3PQFQq7AcbBekPe2bj6KMaPapRGa81SMMAZLjfZLqRp8fRerTR2e1PpC8as06L/r4yKSiyOxiirsLmHshJimIhwVItbUo6ToXpqBkIs4BZ1pZJ3/+Qa/lziyNvP9Ig6KCvroDFWeMSw1TNWGi9uepS9TAkwF/h8R9YtJVAJcV/BNcbdyKdH+8rc1lDPb6R+TettXIIYjnMRijYmCWjNgppx2+1QLOVoe/rNYN74ar0FfRFuWBlZ1g675W1V3vY6erVlN1CNiAiKrHvYZHPdx19phAQlJX+5yYxavQTmMpynfVOuLqUgOsSySxV52ZzdxH+ve5BGsrOMT4wk/aCgfOHhDJYMFOxnGWWmFUNOT5ncQbVPEi4QUwxRV3LBi6ubZhkiDM0NS7eC/OZc/6X8iLmdEuZlGWvMwb7NvXNBKnhfhpVz8q4GCXFy+Wbw4N5TqbP1mqKTVi7OP7dbR55zZ8+aCzJMsq/Zi4pCxRSApMt/kXd7yxawNTq+smzhnhWkCPHO8KntWJT1Eunv3b6xOqlVOqc7RW//tRli22skuUKjlyAqr7xcIGBNUsyfwNArQX79WSXT43VxJ8m+L/6+xjAvfgEHX9+MAEBbwctmwSAGyJyiR6DIOxp+iYpqdW2pp4rdvFcYyTkAOK9dITjGJFnEmriA02RD/zrPQdsV5lihcK4FsbJBqsi9Xl8cqkP4buAsAdshXeqO+URDfsyMngeRJo+4R5u424sEZRc5DI893/HTokU7XzaXmjil3NHyu+TbK3gD+aROxKu6Lz7TmS2C7d77RjaA4JxGPWDCY7CTpx01AmL23wZBxdarX9f/3/nnkV0A0O9mkvC+aSg4mriQbgsszRi9a945Eg4xGViTffeMWRs8m6VDssVEN80Hucj4K7vQ+alZ0nP67DpjaPD/EMKcrLXQmE/ymR+ZBzJHguGP6OYjLp91UByLUoCB7/B4kII9i3/OzVwu3XP0rNVZapNsURorvmOLlUTH2q9Zd5b6e2yHEOMkuPi3vL8iafm9FQ/Hptc3UspQXW6oB2WX6PCgeQD7u8TI84mvunKmh+BJJVqG/7BoaOJyIi54rOs+PzzirJuTGJtSJRqPOH5bGUk/eiZgunzPs79mShJKACzK0+I0+ZMBrhzWxs4+NX4KTCrTE8lbhC1yWyrMVSx/Oi5J8I9xhmHwfRr8oe9YE4Ykng371jZdHi8XaIN/gw++/Kgpg6IfPf1sTUPtg3gofGC3+l7T9k5CoOciPSjkUCzS54AAbVd6j9qPqOL3/mlRR4FTCKKR3yOrDZ+lVIUYNR4NC9QKFr1ThLkGvZRnZk/NXaM6QRauNTqAUbWe+ejL+BtDPhDt8V/8hPEqQLG8Q2u6I91SCQN2HOhwU/VsCmQHE50NJ56tmHxIfdrC1vYuJx3Z1cS+/+4hQ7jnKuIJcJcDX9F4PUfJZ6M7/3lB1KqjWy8UYAXzVxiNyuSrQ1wdZzuJ/iYjBwp42Lo58MRe2SRxSM4XZrAdm4eHyRM+gm0CO9/vYIhsTexPC+/XiQjRR1Xwrj2DH9m6ESD8zmeRasthKJfdsgzkA+SMDe5JjxI2Mu7cWaA060ib9V9ARSyqoYYzib6V7k47SAcyZF0Si2qZDQXPojv9i8bErRaupsLWCUSVjiELxVJVTigw+zvWSla/hkf+dFQFldKjf1qRkO0k/M4FKYCzrRnIQRJfIAbsEg=",
  "name": "read_url",
  "args": {
    "url": "https://raw.githubusercontent.com/shuane/blogtopod/refs/heads/main/README.md"
  },
  "result": "# blogtopod: Turn blog posts into podcast episodes\n\n*Convert blog posts into podcast episodes using <TRUNCATED>"
}

The previous code explanation was remarkably accurate and aligns perfectly with what the README.md describes! The explain_code prompt did a great job dissecting the pipeline (Contextkit -> Gemini 2.5 Pro -> OpenAI TTS -> Pydub) exactly as the author intended.

However, cross-referencing with the README.md reveals a few practical, user-facing details that the raw code analysis missed:

  1. Python Version Constraint: The README explicitly notes that you need a Python version before 3.13 (specifically recommending 3.12). This is due to a limitation with the pydub audio processing library.
  2. Execution Method: While the code explanation noted it can be run via uv run due to the PEP 723 block, the README specifically recommends running it headlessly/sandboxed with: uvx marimo run --sandbox https://raw.githubusercontent.com/shuane/blogtopod/refs/heads/main/blogtopod.py
  3. Patience Required: The README gives the user a heads-up that clicking “Run!” might take 6 to 10 minutes to process.

Are there any specific parts of the pipeline (like how it chunks the script or handles the TTS streams) you want to dive deeper into?

{"model": "gemini/gemini-3.1-pro-preview", "prompt_tokens": 19260, "completion_tokens": 1657, "total_tokens": 20917, "cached_tokens": 8059, "cache_creation_tokens": 0, "cost": 0.0438978}

Developer Guide

If you are new to using nbdev here are some useful pointers to get you started.

Installing solveit_dmtools in Development mode

  1. local dev install:

$ pip install -e .

  1. make changes under nbs/ directory: …
  2. prepare for commit:

$ alias nbprep='nbdev-clean ; nbdev-export'

$ nbprep

#!pip install -e ..
# OR
#!pip install -Uqq solveit_dmtools

Installation

Install latest from the GitHub repository:

$ pip install git+https://github.com/shuane/solveit_dmtools.git

or from pypi

$ pip install solveit_dmtools

Documentation

Documentation can be found hosted on this GitHub repository’s pages. Additionally you can find package manager specific guidelines on the pypi site.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

solveit_dmtools-0.0.30.tar.gz (51.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

solveit_dmtools-0.0.30-py3-none-any.whl (32.7 kB view details)

Uploaded Python 3

File details

Details for the file solveit_dmtools-0.0.30.tar.gz.

File metadata

  • Download URL: solveit_dmtools-0.0.30.tar.gz
  • Upload date:
  • Size: 51.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.1

File hashes

Hashes for solveit_dmtools-0.0.30.tar.gz
Algorithm Hash digest
SHA256 f7f3c51c6237913fb54877602a696fc213ad7ce21b076f3f58fd4c1e443667ba
MD5 ce860c3fe882c7693b302a74c58bcc68
BLAKE2b-256 0dbd169e9853f261fe2569f9022724a4256bf95abbd362460a43e6b7ea583e66

See more details on using hashes here.

File details

Details for the file solveit_dmtools-0.0.30-py3-none-any.whl.

File metadata

File hashes

Hashes for solveit_dmtools-0.0.30-py3-none-any.whl
Algorithm Hash digest
SHA256 e914ff60e1114c5c376a01b0adf6cc3a872f16671ae907a1e7e97c42c907abb0
MD5 fc233dcd4a8983c3e8dc1454b571413a
BLAKE2b-256 f31321918ddfb9330bfd6e3014761f9e5c2698dfbe182cd633f4fd5f3399e121

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.0.30 This release

2 files

0.0.29

2 files

0.0.28

2 files

0.0.27

2 files

0.0.26

2 files

0.0.24

2 files

0.0.23

2 files

0.0.22

2 files

0.0.21

2 files

0.0.20

2 files

0.0.19

2 files

0.0.18

2 files

0.0.17

2 files

0.0.16

2 files

0.0.15

2 files

0.0.14

2 files

0.0.13

2 files

0.0.12

2 files

0.0.11

2 files

0.0.10

2 files

0.0.9

2 files

0.0.8

2 files

0.0.7

2 files

0.0.6

2 files

0.0.5

2 files

0.0.4

2 files

0.0.3

2 files

0.0.2

2 files

0.0.1

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page