fix(core): Filter empty content blocks from formatted prompts (#32519)

Co-authored-by: Mason Daugherty <mason@langchain.dev>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Jacob Lee
2025-11-07 11:39:25 -08:00
committed by GitHub
parent d8b94007c1
commit 46971447df
2 changed files with 361 additions and 14 deletions

View File

@@ -587,14 +587,15 @@ class _StringImageMessagePromptTemplate(BaseMessagePromptTemplate):
for prompt in self.prompt: for prompt in self.prompt:
inputs = {var: kwargs[var] for var in prompt.input_variables} inputs = {var: kwargs[var] for var in prompt.input_variables}
if isinstance(prompt, StringPromptTemplate): if isinstance(prompt, StringPromptTemplate):
formatted: str | ImageURL | dict[str, Any] = prompt.format(**inputs) formatted_text: str = prompt.format(**inputs)
content.append({"type": "text", "text": formatted}) if formatted_text != "":
content.append({"type": "text", "text": formatted_text})
elif isinstance(prompt, ImagePromptTemplate): elif isinstance(prompt, ImagePromptTemplate):
formatted = prompt.format(**inputs) formatted_image: ImageURL = prompt.format(**inputs)
content.append({"type": "image_url", "image_url": formatted}) content.append({"type": "image_url", "image_url": formatted_image})
elif isinstance(prompt, DictPromptTemplate): elif isinstance(prompt, DictPromptTemplate):
formatted = prompt.format(**inputs) formatted_dict: dict[str, Any] = prompt.format(**inputs)
content.append(formatted) content.append(formatted_dict)
return self._msg_class( return self._msg_class(
content=content, additional_kwargs=self.additional_kwargs content=content, additional_kwargs=self.additional_kwargs
) )
@@ -617,16 +618,15 @@ class _StringImageMessagePromptTemplate(BaseMessagePromptTemplate):
for prompt in self.prompt: for prompt in self.prompt:
inputs = {var: kwargs[var] for var in prompt.input_variables} inputs = {var: kwargs[var] for var in prompt.input_variables}
if isinstance(prompt, StringPromptTemplate): if isinstance(prompt, StringPromptTemplate):
formatted: str | ImageURL | dict[str, Any] = await prompt.aformat( formatted_text: str = await prompt.aformat(**inputs)
**inputs if formatted_text != "":
) content.append({"type": "text", "text": formatted_text})
content.append({"type": "text", "text": formatted})
elif isinstance(prompt, ImagePromptTemplate): elif isinstance(prompt, ImagePromptTemplate):
formatted = await prompt.aformat(**inputs) formatted_image: ImageURL = await prompt.aformat(**inputs)
content.append({"type": "image_url", "image_url": formatted}) content.append({"type": "image_url", "image_url": formatted_image})
elif isinstance(prompt, DictPromptTemplate): elif isinstance(prompt, DictPromptTemplate):
formatted = prompt.format(**inputs) formatted_dict: dict[str, Any] = prompt.format(**inputs)
content.append(formatted) content.append(formatted_dict)
return self._msg_class( return self._msg_class(
content=content, additional_kwargs=self.additional_kwargs content=content, additional_kwargs=self.additional_kwargs
) )

View File

@@ -1193,3 +1193,350 @@ def test_dict_message_prompt_template_errors_on_jinja2() -> None:
_ = ChatPromptTemplate.from_messages( _ = ChatPromptTemplate.from_messages(
[("human", [prompt])], template_format="jinja2" [("human", [prompt])], template_format="jinja2"
) )
def test_rendering_prompt_with_conditionals_no_empty_text_blocks() -> None:
manifest = {
"lc": 1,
"type": "constructor",
"id": ["langchain_core", "prompts", "chat", "ChatPromptTemplate"],
"kwargs": {
"messages": [
{
"lc": 1,
"type": "constructor",
"id": [
"langchain_core",
"prompts",
"chat",
"SystemMessagePromptTemplate",
],
"kwargs": {
"prompt": {
"lc": 1,
"type": "constructor",
"id": [
"langchain_core",
"prompts",
"prompt",
"PromptTemplate",
],
"kwargs": {
"input_variables": [],
"template_format": "mustache",
"template": "Always echo back whatever I send you.",
},
},
},
},
{
"lc": 1,
"type": "constructor",
"id": [
"langchain_core",
"prompts",
"chat",
"HumanMessagePromptTemplate",
],
"kwargs": {
"prompt": [
{
"lc": 1,
"type": "constructor",
"id": [
"langchain_core",
"prompts",
"prompt",
"PromptTemplate",
],
"kwargs": {
"input_variables": [],
"template_format": "mustache",
"template": "Here is the teacher's prompt:",
"additional_content_fields": {
"text": "Here is the teacher's prompt:",
},
},
},
{
"lc": 1,
"type": "constructor",
"id": [
"langchain_core",
"prompts",
"prompt",
"PromptTemplate",
],
"kwargs": {
"input_variables": ["promptDescription"],
"template_format": "mustache",
"template": '"{{promptDescription}}"\n',
"additional_content_fields": {
"text": '"{{promptDescription}}"\n',
},
},
},
{
"lc": 1,
"type": "constructor",
"id": [
"langchain_core",
"prompts",
"prompt",
"PromptTemplate",
],
"kwargs": {
"input_variables": [],
"template_format": "mustache",
"template": "Here is the expected answer or success criteria given by the teacher:", # noqa: E501
"additional_content_fields": {
"text": "Here is the expected answer or success criteria given by the teacher:", # noqa: E501
},
},
},
{
"lc": 1,
"type": "constructor",
"id": [
"langchain_core",
"prompts",
"prompt",
"PromptTemplate",
],
"kwargs": {
"input_variables": ["expectedResponse"],
"template_format": "mustache",
"template": '"{{expectedResponse}}"\n',
"additional_content_fields": {
"text": '"{{expectedResponse}}"\n',
},
},
},
{
"lc": 1,
"type": "constructor",
"id": [
"langchain_core",
"prompts",
"prompt",
"PromptTemplate",
],
"kwargs": {
"input_variables": [],
"template_format": "mustache",
"template": "Note: This may be just one example of many possible correct ways for the student to respond.\n", # noqa: E501
"additional_content_fields": {
"text": "Note: This may be just one example of many possible correct ways for the student to respond.\n", # noqa: E501
},
},
},
{
"lc": 1,
"type": "constructor",
"id": [
"langchain_core",
"prompts",
"prompt",
"PromptTemplate",
],
"kwargs": {
"input_variables": [],
"template_format": "mustache",
"template": "For your evaluation of the student's response:\n", # noqa: E501
"additional_content_fields": {
"text": "For your evaluation of the student's response:\n", # noqa: E501
},
},
},
{
"lc": 1,
"type": "constructor",
"id": [
"langchain_core",
"prompts",
"prompt",
"PromptTemplate",
],
"kwargs": {
"input_variables": [],
"template_format": "mustache",
"template": "Here is a transcript of the student's explanation:", # noqa: E501
"additional_content_fields": {
"text": "Here is a transcript of the student's explanation:", # noqa: E501
},
},
},
{
"lc": 1,
"type": "constructor",
"id": [
"langchain_core",
"prompts",
"prompt",
"PromptTemplate",
],
"kwargs": {
"input_variables": ["responseTranscript"],
"template_format": "mustache",
"template": '"{{responseTranscript}}"\n',
"additional_content_fields": {
"text": '"{{responseTranscript}}"\n',
},
},
},
{
"lc": 1,
"type": "constructor",
"id": [
"langchain_core",
"prompts",
"prompt",
"PromptTemplate",
],
"kwargs": {
"input_variables": ["readingFluencyAnalysis"],
"template_format": "mustache",
"template": "{{#readingFluencyAnalysis}} For this task, the student's reading pronunciation and fluency were important. Here is analysis of the student's oral response: \"{{readingFluencyAnalysis}}\" {{/readingFluencyAnalysis}}", # noqa: E501
"additional_content_fields": {
"text": "{{#readingFluencyAnalysis}} For this task, the student's reading pronunciation and fluency were important. Here is analysis of the student's oral response: \"{{readingFluencyAnalysis}}\" {{/readingFluencyAnalysis}}", # noqa: E501
},
},
},
{
"lc": 1,
"type": "constructor",
"id": [
"langchain_core",
"prompts",
"prompt",
"PromptTemplate",
],
"kwargs": {
"input_variables": ["readingFluencyAnalysis"],
"template_format": "mustache",
"template": "{{#readingFluencyAnalysis}}Root analysis of the student's response (step 3) in this oral analysis rather than inconsistencies in the transcript.{{/readingFluencyAnalysis}}", # noqa: E501
"additional_content_fields": {
"text": "{{#readingFluencyAnalysis}}Root analysis of the student's response (step 3) in this oral analysis rather than inconsistencies in the transcript.{{/readingFluencyAnalysis}}", # noqa: E501
},
},
},
{
"lc": 1,
"type": "constructor",
"id": [
"langchain_core",
"prompts",
"prompt",
"PromptTemplate",
],
"kwargs": {
"input_variables": ["readingFluencyAnalysis"],
"template_format": "mustache",
"template": "{{#readingFluencyAnalysis}}Remember this is a student, so we care about general fluency - not voice acting. {{/readingFluencyAnalysis}}\n", # noqa: E501
"additional_content_fields": {
"text": "{{#readingFluencyAnalysis}}Remember this is a student, so we care about general fluency - not voice acting. {{/readingFluencyAnalysis}}\n", # noqa: E501
},
},
},
{
"lc": 1,
"type": "constructor",
"id": [
"langchain_core",
"prompts",
"prompt",
"PromptTemplate",
],
"kwargs": {
"input_variables": ["multipleChoiceAnalysis"],
"template_format": "mustache",
"template": "{{#multipleChoiceAnalysis}}Here is an analysis of the student's multiple choice response: {{multipleChoiceAnalysis}}{{/multipleChoiceAnalysis}}\n", # noqa: E501
"additional_content_fields": {
"text": "{{#multipleChoiceAnalysis}}Here is an analysis of the student's multiple choice response: {{multipleChoiceAnalysis}}{{/multipleChoiceAnalysis}}\n", # noqa: E501
},
},
},
{
"lc": 1,
"type": "constructor",
"id": [
"langchain_core",
"prompts",
"prompt",
"PromptTemplate",
],
"kwargs": {
"input_variables": [],
"template_format": "mustache",
"template": "Here is the student's whiteboard:\n",
"additional_content_fields": {
"text": "Here is the student's whiteboard:\n",
},
},
},
{
"lc": 1,
"type": "constructor",
"id": [
"langchain_core",
"prompts",
"image",
"ImagePromptTemplate",
],
"kwargs": {
"template": {
"url": "{{whiteboard}}",
},
"input_variables": ["whiteboard"],
"template_format": "mustache",
"additional_content_fields": {
"image_url": {
"url": "{{whiteboard}}",
},
},
},
},
],
"additional_options": {},
},
},
],
"input_variables": [
"promptDescription",
"expectedResponse",
"responseTranscript",
"readingFluencyAnalysis",
"readingFluencyAnalysis",
"readingFluencyAnalysis",
"multipleChoiceAnalysis",
"whiteboard",
],
"template_format": "mustache",
"metadata": {
"lc_hub_owner": "jacob",
"lc_hub_repo": "mustache-conditionals",
"lc_hub_commit_hash": "836ad82d512409ea6024fb760b76a27ba58fc68b1179656c0ba2789778686d46", # noqa: E501
},
},
}
# Load the ChatPromptTemplate from the manifest
template = load(manifest)
# Format with conditional data - rules is empty, so mustache conditionals
# should not render
result = template.invoke(
{
"promptDescription": "What is the capital of the USA?",
"expectedResponse": "Washington, D.C.",
"responseTranscript": "Washington, D.C.",
"readingFluencyAnalysis": None,
"multipleChoiceAnalysis": "testing2",
"whiteboard": "https://foo.com/bar.png",
}
)
content = result.messages[1].content
assert isinstance(content, list)
assert not [
block for block in content if block["type"] == "text" and block["text"] == ""
]