Skip to content

Commit 0e12ea0

Browse files
R1-53, S1-3: single-pass prompt render and sanitize string defaults
1 parent ef7604c commit 0e12ea0

3 files changed

Lines changed: 35 additions & 5 deletions

File tree

‎tests/test_prompt.py‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,32 @@ def test_repair_hygiene(self):
6666
self.assertNotIn("Traceback", repair)
6767
self.assertNotIn('File "', repair)
6868
self.assertTrue(repair.endswith("Reply:"))
69+
70+
def test_description_with_placeholder_not_replaced(self):
71+
spec = ToolSpec("t", "Find {query} fast", (), lambda: None)
72+
prompt = render_prompt("hello", [spec])
73+
self.assertIn("Find {query} fast", prompt)
74+
self.assertEqual(prompt.count("hello"), 1)
75+
76+
def test_string_default_newline_sanitized(self):
77+
p = ParamSpec("x", "str", False, "a\nb")
78+
spec = ToolSpec("t", "desc", (p,), lambda: None)
79+
line = render_tool_line(spec)
80+
self.assertNotIn("\n", line)
81+
self.assertIn('x:str="a b"', line)
82+
83+
def test_string_default_truncated_in_prompt_only(self):
84+
long_str = "a" * 30
85+
p = ParamSpec("x", "str", False, long_str)
86+
spec = ToolSpec("t", "desc", (p,), lambda: None)
87+
line = render_tool_line(spec)
88+
expected_str = ("a" * 20) + "..."
89+
self.assertIn(f'x:str="{expected_str}"', line)
90+
self.assertEqual(spec.params[0].default, long_str)
91+
92+
def test_query_with_braces_is_safe(self):
93+
prompt = render_prompt("what is {tool_lines} and {query}?", [])
94+
lines = prompt.splitlines()
95+
self.assertTrue(lines[-2].startswith("Query: what is {tool_lines} and {query}?"))
96+
self.assertEqual(lines[-1], "Reply:")
97+
self.assertEqual(len([l for l in lines if l.startswith("Query:")]), 1)

‎tinyagent/prompt.py‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import math
22
import re
33
import unicodedata
4+
from typing import Sequence
45
from .types import ToolSpec
56

67
PROMPT_TEMPLATE = """Pick one tool for the query.
@@ -49,9 +50,9 @@ def render_tool_line(spec: ToolSpec) -> str:
4950
if p.default is None:
5051
default_str = "None"
5152
elif isinstance(p.default, str):
52-
default_val = p.default
53+
default_val = _sanitize(p.default)
5354
if len(default_val) > 20:
54-
default_val = default_val[:17] + "..."
55+
default_val = default_val[:20] + "..."
5556
default_str = f'"{default_val}"'
5657
else:
5758
default_str = repr(p.default)
@@ -60,7 +61,7 @@ def render_tool_line(spec: ToolSpec) -> str:
6061
params_str = ", ".join(rendered_params)
6162
return f"{spec.name}({params_str}) - {spec.description}"
6263

63-
def render_prompt(query: str, tools: tuple[ToolSpec, ...], *, max_query_chars: int = 2000) -> str:
64+
def render_prompt(query: str, tools: Sequence[ToolSpec], *, max_query_chars: int = 2000) -> str:
6465
sanitized = _sanitize(query)[:max_query_chars]
6566
if not sanitized:
6667
raise ValueError("empty query")
@@ -70,7 +71,7 @@ def render_prompt(query: str, tools: tuple[ToolSpec, ...], *, max_query_chars: i
7071
else:
7172
tool_lines = "\n".join(render_tool_line(t) for t in tools)
7273

73-
return PROMPT_TEMPLATE.replace("{tool_lines}", tool_lines).replace("{query}", sanitized)
74+
return PROMPT_TEMPLATE.format(tool_lines=tool_lines, query=sanitized)
7475

7576
def render_repair_prompt(prompt: str, previous_output: str, code: str, detail: str) -> str:
7677
"""

‎tinyagent/reflection.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import re
33
import types
44
import typing
5-
from typing import Any, Callable, Optional, Union, get_args, get_origin
5+
from typing import Any, Callable, Union, get_args, get_origin
66

77
from .types import ParamSpec, ToolSpec
88
from .registry import ToolRegistry, default_registry

0 commit comments

Comments
 (0)