fix(block): Remove Python.format & Jinja templating format backward compatibility (#9229)
Python format uses `{Variable}` as the variable placeholder, while Jinja uses `{{Variable}}` as its default. Jinja is used as the main templating engine on the system, but the Python format version is still maintained for backward compatibility. However, the backward compatibility support can cause a side effect while passing JSON string value into the block that uses it: https://github.com/Significant-Gravitas/AutoGPT/issues/9194 ### Changes 🏗️ * Use `{{Variable}}` place holder format and removed `{Variable}` support in these blocks: - '363ae599-353e-4804-937e-b2ee3cef3da4', -- AgentOutputBlock - 'db7d8f02-2f44-4c55-ab7a-eae0941f0c30', -- FillTextTemplateBlock - '1f292d4a-41a4-4977-9684-7c8d560b9f91', -- AITextGeneratorBlock - 'ed55ac19-356e-4243-a6cb-bc599e9b716f' -- AIStructuredResponseGeneratorBlock * Add Jinja templating support on `AITextGeneratorBlock` & `AIStructuredResponseGeneratorBlock` * Migrated the existing database content to prevent breaking changes. ### Checklist 📋 #### For code changes: - [ ] I have clearly listed my changes in the PR description - [ ] I have made a test plan - [ ] I have tested my changes according to the test plan: <!-- Put your test plan here: --> - [ ] ... <details> <summary>Example test plan</summary> - [ ] Create from scratch and execute an agent with at least 3 blocks - [ ] Import an agent from file upload, and confirm it executes correctly - [ ] Upload agent to marketplace - [ ] Import an agent from marketplace and confirm it executes correctly - [ ] Edit an agent from monitor, and confirm it executes correctly </details> #### For configuration changes: - [ ] `.env.example` is updated or already compatible with my changes - [ ] `docker-compose.yml` is updated or already compatible with my changes - [ ] I have included a list of my configuration changes in the PR description (under **Changes**) <details> <summary>Examples of configuration changes</summary> - Changing ports - Adding new services that need to communicate with each other - Secrets or environment variable changes - New or infrastructure changes such as databases </details>
This commit is contained in:
parent
a1889e6212
commit
1670579a61
|
@ -241,7 +241,7 @@ class AgentOutputBlock(Block):
|
|||
advanced=True,
|
||||
)
|
||||
format: str = SchemaField(
|
||||
description="The format string to be used to format the recorded_value.",
|
||||
description="The format string to be used to format the recorded_value. Use Jinja2 syntax.",
|
||||
default="",
|
||||
advanced=True,
|
||||
)
|
||||
|
|
|
@ -26,8 +26,10 @@ from backend.data.model import (
|
|||
)
|
||||
from backend.util import json
|
||||
from backend.util.settings import BehaveAs, Settings
|
||||
from backend.util.text import TextFormatter
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
fmt = TextFormatter()
|
||||
|
||||
LLMProviderName = Literal[
|
||||
ProviderName.ANTHROPIC,
|
||||
|
@ -236,7 +238,9 @@ class AIStructuredResponseGeneratorBlock(Block):
|
|||
description="Number of times to retry the LLM call if the response does not match the expected format.",
|
||||
)
|
||||
prompt_values: dict[str, str] = SchemaField(
|
||||
advanced=False, default={}, description="Values used to fill in the prompt."
|
||||
advanced=False,
|
||||
default={},
|
||||
description="Values used to fill in the prompt. The values can be used in the prompt by putting them in a double curly braces, e.g. {{variable_name}}.",
|
||||
)
|
||||
max_tokens: int | None = SchemaField(
|
||||
advanced=True,
|
||||
|
@ -450,8 +454,8 @@ class AIStructuredResponseGeneratorBlock(Block):
|
|||
|
||||
values = input_data.prompt_values
|
||||
if values:
|
||||
input_data.prompt = input_data.prompt.format(**values)
|
||||
input_data.sys_prompt = input_data.sys_prompt.format(**values)
|
||||
input_data.prompt = fmt.format_string(input_data.prompt, values)
|
||||
input_data.sys_prompt = fmt.format_string(input_data.sys_prompt, values)
|
||||
|
||||
if input_data.sys_prompt:
|
||||
prompt.append({"role": "system", "content": input_data.sys_prompt})
|
||||
|
@ -578,7 +582,9 @@ class AITextGeneratorBlock(Block):
|
|||
description="Number of times to retry the LLM call if the response does not match the expected format.",
|
||||
)
|
||||
prompt_values: dict[str, str] = SchemaField(
|
||||
advanced=False, default={}, description="Values used to fill in the prompt."
|
||||
advanced=False,
|
||||
default={},
|
||||
description="Values used to fill in the prompt. The values can be used in the prompt by putting them in a double curly braces, e.g. {{variable_name}}.",
|
||||
)
|
||||
ollama_host: str = SchemaField(
|
||||
advanced=True,
|
||||
|
|
|
@ -141,10 +141,10 @@ class ExtractTextInformationBlock(Block):
|
|||
class FillTextTemplateBlock(Block):
|
||||
class Input(BlockSchema):
|
||||
values: dict[str, Any] = SchemaField(
|
||||
description="Values (dict) to be used in format"
|
||||
description="Values (dict) to be used in format. These values can be used by putting them in double curly braces in the format template. e.g. {{value_name}}.",
|
||||
)
|
||||
format: str = SchemaField(
|
||||
description="Template to format the text using `values`"
|
||||
description="Template to format the text using `values`. Use Jinja2 syntax."
|
||||
)
|
||||
|
||||
class Output(BlockSchema):
|
||||
|
@ -160,7 +160,7 @@ class FillTextTemplateBlock(Block):
|
|||
test_input=[
|
||||
{
|
||||
"values": {"name": "Alice", "hello": "Hello", "world": "World!"},
|
||||
"format": "{hello}, {world} {{name}}",
|
||||
"format": "{{hello}}, {{ world }} {{name}}",
|
||||
},
|
||||
{
|
||||
"values": {"list": ["Hello", " World!"]},
|
||||
|
|
|
@ -38,7 +38,7 @@ def create_test_graph() -> graph.Graph:
|
|||
graph.Node(
|
||||
block_id=FillTextTemplateBlock().id,
|
||||
input_default={
|
||||
"format": "{a}, {b}{c}",
|
||||
"format": "{{a}}, {{b}}{{c}}",
|
||||
"values_#_c": "!!!",
|
||||
},
|
||||
),
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import re
|
||||
|
||||
from jinja2 import BaseLoader
|
||||
from jinja2.sandbox import SandboxedEnvironment
|
||||
|
||||
|
@ -15,8 +13,5 @@ class TextFormatter:
|
|||
self.env.globals.clear()
|
||||
|
||||
def format_string(self, template_str: str, values=None, **kwargs) -> str:
|
||||
# For python.format compatibility: replace all {...} with {{..}}.
|
||||
# But avoid replacing {{...}} to {{{...}}}.
|
||||
template_str = re.sub(r"(?<!{){[ a-zA-Z0-9_]+}", r"{\g<0>}", template_str)
|
||||
template = self.env.from_string(template_str)
|
||||
return template.render(values or {}, **kwargs)
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
Warnings:
|
||||
- You are about replace a single brace string input format for the following blocks:
|
||||
- AgentOutputBlock
|
||||
- FillTextTemplateBlock
|
||||
- AITextGeneratorBlock
|
||||
- AIStructuredResponseGeneratorBlock
|
||||
with a double brace format.
|
||||
*/
|
||||
WITH to_update AS (
|
||||
SELECT
|
||||
"agentBlockId",
|
||||
"constantInput"::jsonb AS j
|
||||
FROM "AgentNode"
|
||||
WHERE
|
||||
"agentBlockId" IN (
|
||||
'363ae599-353e-4804-937e-b2ee3cef3da4', -- AgentOutputBlock
|
||||
'db7d8f02-2f44-4c55-ab7a-eae0941f0c30', -- FillTextTemplateBlock
|
||||
'1f292d4a-41a4-4977-9684-7c8d560b9f91', -- AITextGeneratorBlock
|
||||
'ed55ac19-356e-4243-a6cb-bc599e9b716f' -- AIStructuredResponseGeneratorBlock
|
||||
)
|
||||
AND (
|
||||
"constantInput"::jsonb->>'format' ~ '(?<!\{)\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}(?!\})'
|
||||
OR "constantInput"::jsonb->>'prompt' ~ '(?<!\{)\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}(?!\})'
|
||||
OR "constantInput"::jsonb->>'sys_prompt' ~ '(?<!\{)\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}(?!\})'
|
||||
)
|
||||
),
|
||||
updated_rows AS (
|
||||
SELECT
|
||||
"agentBlockId",
|
||||
(
|
||||
j
|
||||
-- Update "format" if it has a single-brace placeholder
|
||||
|| CASE WHEN j->>'format' ~ '(?<!\{)\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}(?!\})'
|
||||
THEN jsonb_build_object(
|
||||
'format',
|
||||
regexp_replace(
|
||||
j->>'format',
|
||||
'(?<!\{)\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}(?!\})',
|
||||
'{{\1}}',
|
||||
'g'
|
||||
)
|
||||
)
|
||||
ELSE '{}'::jsonb
|
||||
END
|
||||
-- Update "prompt" if it has a single-brace placeholder
|
||||
|| CASE WHEN j->>'prompt' ~ '(?<!\{)\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}(?!\})'
|
||||
THEN jsonb_build_object(
|
||||
'prompt',
|
||||
regexp_replace(
|
||||
j->>'prompt',
|
||||
'(?<!\{)\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}(?!\})',
|
||||
'{{\1}}',
|
||||
'g'
|
||||
)
|
||||
)
|
||||
ELSE '{}'::jsonb
|
||||
END
|
||||
-- Update "sys_prompt" if it has a single-brace placeholder
|
||||
|| CASE WHEN j->>'sys_prompt' ~ '(?<!\{)\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}(?!\})'
|
||||
THEN jsonb_build_object(
|
||||
'sys_prompt',
|
||||
regexp_replace(
|
||||
j->>'sys_prompt',
|
||||
'(?<!\{)\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}(?!\})',
|
||||
'{{\1}}',
|
||||
'g'
|
||||
)
|
||||
)
|
||||
ELSE '{}'::jsonb
|
||||
END
|
||||
)::text AS "newConstantInput"
|
||||
FROM to_update
|
||||
)
|
||||
UPDATE "AgentNode" AS an
|
||||
SET "constantInput" = ur."newConstantInput"
|
||||
FROM updated_rows ur
|
||||
WHERE an."agentBlockId" = ur."agentBlockId";
|
|
@ -102,7 +102,7 @@ async def assert_sample_graph_executions(
|
|||
assert exec.graph_exec_id == graph_exec_id
|
||||
assert exec.output_data == {"output": ["Hello, World!!!"]}
|
||||
assert exec.input_data == {
|
||||
"format": "{a}, {b}{c}",
|
||||
"format": "{{a}}, {{b}}{{c}}",
|
||||
"values": {"a": "Hello", "b": "World", "c": "!!!"},
|
||||
"values_#_a": "Hello",
|
||||
"values_#_b": "World",
|
||||
|
|
Loading…
Reference in New Issue