Submission checklist
Package (Required)
Related Issues / PRs
No response
Reproduction Steps / Example Code (Python)
from langchain_core.prompts import ChatPromptTemplate, DictPromptTemplate
# 1. Direct use of DictPromptTemplate
template = {
"type": "tool_use",
"id": "call_1",
"name": "search",
"input": {"query": "{q}", "top_k_scores": [1, 2, 3], "flags": [True, None]},
}
prompt = DictPromptTemplate(template=template, template_format="f-string")
print("1 formatted :", prompt.format(q="cats"))
# 2. Same loss through the ChatPromptTemplate public API
chat = ChatPromptTemplate.from_messages(
[("ai", [{"type": "text", "text": "{q}", "nums": [1, 2, 3]}])]
)
print("2 content :", chat.format_messages(q="hello")[0].content)
# 3. Mixed list: str/dict survive, everything else disappears
p3 = DictPromptTemplate(
template={"type": "x", "mixed": ["a", 1, 2.5, {"k": "v"}, None, True]},
template_format="f-string",
)
print("3 mixed :", p3.format())
# 4. The same scalars are preserved when they are NOT inside a list
p4 = DictPromptTemplate(
template={"type": "x", "scalar": 1, "nested": {"scalar": 2}},
template_format="f-string",
)
print("4 scalars :", p4.format())
# 5. Data is destroyed even when the template has no variables at all
p5 = DictPromptTemplate(template={"nums": [1, 2]}, template_format="mustache")
print("5 no vars :", p5.format())
Error Message and Stack Trace (if applicable)
Description
DictPromptTemplate.format() is expected to substitute the template variables and return the template otherwise unchanged. Instead, any item nested inside a list or tuple that is not a str or a dict is silently deleted from the output: int, float, bool, None, and nested lists all disappear.
Actual output of the snippet above (langchain-core 1.5.3, also reproduced from master source at a1a1ad3):
1 formatted : {'type': 'tool_use', 'id': 'call_1', 'name': 'search', 'input': {'query': 'cats', 'top_k_scores': [], 'flags': []}}
2 content : [{'type': 'text', 'text': 'hello', 'nums': []}]
3 mixed : {'type': 'x', 'mixed': ['a', {'k': 'v'}]}
4 scalars : {'type': 'x', 'scalar': 1, 'nested': {'scalar': 2}}
5 no vars : {'nums': []}
Expected output:
1 formatted : {'type': 'tool_use', 'id': 'call_1', 'name': 'search', 'input': {'query': 'cats', 'top_k_scores': [1, 2, 3], 'flags': [True, None]}}
2 content : [{'type': 'text', 'text': 'hello', 'nums': [1, 2, 3]}]
3 mixed : {'type': 'x', 'mixed': ['a', 1, 2.5, {'k': 'v'}, None, True]}
4 scalars : {'type': 'x', 'scalar': 1, 'nested': {'scalar': 2}}
5 no vars : {'nums': [1, 2]}
Root cause
libs/core/langchain_core/prompts/dict.py, _insert_input_variables (lines 143-175). The list/tuple branch has no fallback for items that are neither str nor dict, so they are never appended to formatted_v:
elif isinstance(v, (list, tuple)):
formatted_v: list[str | dict[str, Any]] = []
for x in v:
if isinstance(x, str):
formatted_v.append(formatter(x, **inputs))
elif isinstance(x, dict):
formatted_v.append(
_insert_input_variables(x, inputs, template_format)
)
formatted[k] = type(v)(formatted_v) # non-str / non-dict items are gone
else:
formatted[k] = v # ... but scalars outside a list are preserved
Line 174 keeps 1 for {"scalar": 1}, while line 172 drops the same 1 for {"nums": [1]}, so the handling is inconsistent within the same function.
Why it matters
DictPromptTemplate is public (from langchain_core.prompts import DictPromptTemplate) and ChatPromptTemplate.from_messages routes every dict content block through it (prompts/chat.py, lines 516-527, formatted at 612-615 / 643-646). Any templated message whose content block carries a list of numbers or booleans loses that data before it reaches the model. Realistic cases: few-shot examples containing provider tool-use blocks such as {"type": "tool_use", ..., "input": {"ids": [1, 2, 3]}}, or blocks carrying scores, page numbers, logprobs, or image dimensions. The model silently receives an empty list.
Case 5 shows the values are destroyed even when the template contains no variables, so no formatting work justifies the mutation.
The class docstring says variables are recognized in string dict values and that this "applies recursively"; it does not document any pruning. format/aformat are documented to return "A formatted dict", and the type contract is dict[str, Any] -> dict[str, Any], so Any values pass construction and validation and are then discarded at format time.
Test coverage
libs/core/tests/unit_tests/prompts/test_dict.py never places a list (or any non-str/non-dict value) in a template, so this path is uncovered. test_chat.py has no equivalent case either.
Possible fix
Add a pass-through fallback in the list branch (widening formatted_v to list[Any]), and recurse for nested lists so they are handled like top-level ones:
else:
formatted_v.append(x)
Happy to open a PR with the fix plus regression tests if maintainers agree with the direction.
System Info
System Information
OS: Windows
OS Version: 10.0.26200
Python Version: 3.11.9 (tags/v3.11.9:de54cf5, Apr 2 2024, 10:12:12) [MSC v.1938 64 bit (AMD64)]
Package Information
langchain_core: 1.5.3
langsmith: 0.10.13
langchain_text_splitters: 1.1.2
Other Dependencies
pydantic: 2.13.4
typing-extensions: 4.16.0
Also confirmed against master source at commit a1a1ad3 (libs/core/langchain_core/prompts/dict.py).
Submission checklist
Package (Required)
Related Issues / PRs
No response
Reproduction Steps / Example Code (Python)
Error Message and Stack Trace (if applicable)
Description
DictPromptTemplate.format()is expected to substitute the template variables and return the template otherwise unchanged. Instead, any item nested inside alistortuplethat is not astror adictis silently deleted from the output:int,float,bool,None, and nested lists all disappear.Actual output of the snippet above (langchain-core 1.5.3, also reproduced from
mastersource ata1a1ad3):Expected output:
Root cause
libs/core/langchain_core/prompts/dict.py,_insert_input_variables(lines 143-175). Thelist/tuplebranch has no fallback for items that are neitherstrnordict, so they are never appended toformatted_v:Line 174 keeps
1for{"scalar": 1}, while line 172 drops the same1for{"nums": [1]}, so the handling is inconsistent within the same function.Why it matters
DictPromptTemplateis public (from langchain_core.prompts import DictPromptTemplate) andChatPromptTemplate.from_messagesroutes everydictcontent block through it (prompts/chat.py, lines 516-527, formatted at 612-615 / 643-646). Any templated message whose content block carries a list of numbers or booleans loses that data before it reaches the model. Realistic cases: few-shot examples containing provider tool-use blocks such as{"type": "tool_use", ..., "input": {"ids": [1, 2, 3]}}, or blocks carrying scores, page numbers, logprobs, or image dimensions. The model silently receives an empty list.Case 5 shows the values are destroyed even when the template contains no variables, so no formatting work justifies the mutation.
The class docstring says variables are recognized in string dict values and that this "applies recursively"; it does not document any pruning.
format/aformatare documented to return "A formatted dict", and the type contract isdict[str, Any] -> dict[str, Any], soAnyvalues pass construction and validation and are then discarded at format time.Test coverage
libs/core/tests/unit_tests/prompts/test_dict.pynever places a list (or any non-str/non-dictvalue) in a template, so this path is uncovered.test_chat.pyhas no equivalent case either.Possible fix
Add a pass-through fallback in the list branch (widening
formatted_vtolist[Any]), and recurse for nested lists so they are handled like top-level ones:Happy to open a PR with the fix plus regression tests if maintainers agree with the direction.
System Info
System Information
Package Information
Other Dependencies
Also confirmed against master source at commit a1a1ad3 (libs/core/langchain_core/prompts/dict.py).