Skip to content

Tools

seed_data.tools

Strands @tool wrappers used by agents in the graph.

make_ask_user_tool(on_question)

Build an ask_user tool that routes questions to a host callback.

This is our host-pluggable take on Strands' handoff_to_user (which is stdio-only, as the Strands docs note): the agent calls ask_user when it is genuinely uncertain, and the on_question(question) -> answer callback decides how to collect the answer (terminal stdin, a notebook widget, a Slack DM, a web modal). on_question is bound in the closure — like make_render_tool — so concurrent agents never share question state.

The callback may return None (user declined / skipped) or raise; both are turned into a benign "no answer — use your best judgment" tool result so a dialogue hiccup never aborts inference.

Source code in seed_data/tools.py
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
def make_ask_user_tool(on_question):
    """Build an ``ask_user`` tool that routes questions to a host callback.

    This is our host-pluggable take on Strands' ``handoff_to_user`` (which is
    stdio-only, as the Strands docs note): the agent calls ``ask_user`` when it
    is genuinely uncertain, and the ``on_question(question) -> answer`` callback
    decides how to collect the answer (terminal stdin, a notebook widget, a Slack
    DM, a web modal). ``on_question`` is bound in the closure — like
    ``make_render_tool`` — so concurrent agents never share question state.

    The callback may return ``None`` (user declined / skipped) or raise; both are
    turned into a benign "no answer — use your best judgment" tool result so a
    dialogue hiccup never aborts inference.
    """
    @tool
    def ask_user(question: str) -> dict:
        """Ask the human a clarifying question about the document and get their answer.

        Use ONLY for genuinely ambiguous details you cannot determine from the
        document itself — e.g. whether a field that appears once is always present,
        the realistic range of a value, or a naming/format convention. Ask concise,
        specific questions. Do not ask about things visible in the document.

        Args:
            question: A single, specific clarifying question for the user.
        """
        try:
            answer = on_question(question)
        except Exception as e:  # a broken UI must not crash inference
            return {"status": "success", "content": [
                {"text": f"(no answer available: {e}; use your best judgment)"}
            ]}
        if answer is None or str(answer).strip() == "":
            return {"status": "success", "content": [
                {"text": "(user did not answer; use your best judgment)"}
            ]}
        return {"status": "success", "content": [{"text": str(answer).strip()}]}

    return ask_user

make_render_tool(backend='xhtml2pdf')

Build a render_html_to_pdf tool with its backend bound in.

The backend is captured in the closure rather than read from a global env var, so each doc-generator gets its own correctly-configured render tool and concurrent workers with different renderers never collide. The tool keeps the name render_html_to_pdf so the generator prompt can reference it.

Source code in seed_data/tools.py
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
def make_render_tool(backend: str = "xhtml2pdf"):
    """Build a ``render_html_to_pdf`` tool with its backend bound in.

    The backend is captured in the closure rather than read from a global env
    var, so each doc-generator gets its own correctly-configured render tool and
    concurrent workers with different renderers never collide. The tool keeps the
    name ``render_html_to_pdf`` so the generator prompt can reference it.
    """
    render = _HTML_RENDERERS.get(backend, _render_with_xhtml2pdf)

    @tool
    def render_html_to_pdf(html_path: str, pdf_path: str) -> dict:
        """Render an HTML file to PDF.

        Args:
            html_path: Path to the HTML file to render.
            pdf_path: Path where the PDF should be saved.
        """
        try:
            os.makedirs(os.path.dirname(pdf_path), exist_ok=True)
            render(html_path, pdf_path)
            if os.path.exists(pdf_path):
                size = os.path.getsize(pdf_path)
                return {"status": "success", "content": [
                    {"text": f"PDF rendered: {pdf_path} ({size:,} bytes)"}
                ]}
            return {"status": "error", "content": [
                {"text": f"{backend} completed but PDF was not created."}
            ]}
        except Exception as e:
            # Surface the real traceback to stdout — otherwise only the LLM sees
            # the (paraphrased) error and the true cause never reaches the user.
            import traceback
            print(f"\n!!! render_html_to_pdf ({backend}) FAILED for {html_path}:")
            traceback.print_exc()
            return {"status": "error", "content": [
                {"text": f"Render failed ({backend}): {type(e).__name__}: {e}"}
            ]}

    return render_html_to_pdf

preview_pdf(pdf_path)

Render a PDF to images so you can visually inspect the output.

Call this after generating a PDF to see what it actually looks like. Returns page images you can examine for layout issues, truncation, font sizing, whitespace, and rendering artifacts.

Parameters:

Name Type Description Default
pdf_path str

Path to the PDF file to preview.

required
Source code in seed_data/tools.py
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
@tool
def preview_pdf(pdf_path: str) -> dict:
    """Render a PDF to images so you can visually inspect the output.

    Call this after generating a PDF to see what it actually looks like.
    Returns page images you can examine for layout issues, truncation,
    font sizing, whitespace, and rendering artifacts.

    Args:
        pdf_path: Path to the PDF file to preview.
    """
    try:
        import io

        import pypdfium2 as pdfium
        doc = pdfium.PdfDocument(pdf_path)
        content = []
        for page in doc:
            bitmap = page.render(scale=150 / 72)
            buf = io.BytesIO()
            bitmap.to_pil().save(buf, format="PNG")
            page.close()
            content.append({
                "image": {
                    "format": "png",
                    "source": {"bytes": buf.getvalue()}
                }
            })
        doc.close()
        content.append({"text": f"Rendered {len(content)} page(s) from {pdf_path}"})
        return {"status": "success", "content": content}
    except Exception as e:
        return {"status": "error", "content": [{"text": f"Preview failed: {e}"}]}

random_roll(percent_chance)

Roll a random decision with a given percent chance of returning true.

Parameters:

Name Type Description Default
percent_chance int

Integer from 1-100 representing the probability of returning true. e.g. 70 means 70% chance of true, 50 means 50% chance of true.

required

Returns:

Type Description
dict

{"result": true} or {"result": false}

Call this once per decision independently — never reuse the same result for multiple decisions.

Source code in seed_data/tools.py
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@tool
def random_roll(percent_chance: int) -> dict:
    """Roll a random decision with a given percent chance of returning true.

    Args:
        percent_chance: Integer from 1-100 representing the probability of returning true.
                        e.g. 70 means 70% chance of true, 50 means 50% chance of true.

    Returns:
        {"result": true} or {"result": false}

    Call this once per decision independently — never reuse the same result for multiple decisions.
    """
    import random
    result = random.randint(1, 100) <= percent_chance
    return {"status": "success", "content": [{"text": str(result).lower()}]}

read_json_file(file_path)

Read a JSON file and return its contents.

Parameters:

Name Type Description Default
file_path str

Path to the JSON file to read.

required
Source code in seed_data/tools.py
26
27
28
29
30
31
32
33
34
35
36
37
38
@tool
def read_json_file(file_path: str) -> dict:
    """Read a JSON file and return its contents.

    Args:
        file_path: Path to the JSON file to read.
    """
    try:
        with open(file_path) as f:
            data = json.load(f)
        return {"status": "success", "content": [{"text": json.dumps(data, indent=2)}]}
    except (FileNotFoundError, json.JSONDecodeError) as e:
        return {"status": "error", "content": [{"text": f"Error reading {file_path}: {e}"}]}

save_json_file(file_path, json_content)

Save JSON content to a file.

Parameters:

Name Type Description Default
file_path str

Path where the JSON file should be saved.

required
json_content dict

The JSON object to save. A JSON-encoded string is also accepted and will be parsed before saving.

required
Source code in seed_data/tools.py
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
@tool
def save_json_file(file_path: str, json_content: dict) -> dict:
    """Save JSON content to a file.

    Args:
        file_path: Path where the JSON file should be saved.
        json_content: The JSON object to save. A JSON-encoded string is also
            accepted and will be parsed before saving.
    """
    try:
        # Callers should pass a JSON object, but tolerate a JSON-encoded string.
        if isinstance(json_content, str):
            json_content = json.loads(json_content)
        os.makedirs(os.path.dirname(file_path), exist_ok=True)
        with open(file_path, "w") as f:
            json.dump(json_content, f, indent=2)
        return {"status": "success", "content": [{"text": f"Saved to: {file_path}"}]}
    except (OSError, json.JSONDecodeError, TypeError) as e:
        return {"status": "error", "content": [{"text": f"Error saving: {e}"}]}