{"record":{"id":"ea8d1debfb086f7e","repo":"langchain-ai/langchain","slug":"invalid-variable-name-var-r-in-f-string-template","errorCode":null,"errorMessage":"Invalid variable name {var!r} in f-string template. Variable names cannot contain attribute access (.) or indexing ([]).","messagePattern":"Invalid variable name (.+?) in f-string template\\. Variable names cannot contain attribute access \\(\\.\\) or indexing \\(\\[\\]\\)\\.","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"libs/core/langchain_core/prompts/string.py","lineNumber":240,"sourceCode":"def _parse_f_string_fields(template: str) -> list[tuple[str, str | None]]:\n    fields: list[tuple[str, str | None]] = []\n    for _, field_name, format_spec, _ in Formatter().parse(template):\n        if field_name is not None:\n            fields.append((field_name, format_spec))\n    return fields\n\n\ndef validate_f_string_template(template: str) -> list[str]:\n    \"\"\"Validate an f-string template and return its input variables.\"\"\"\n    input_variables = set()\n    for var, format_spec in _parse_f_string_fields(template):\n        if \".\" in var or \"[\" in var or \"]\" in var:\n            msg = (\n                f\"Invalid variable name {var!r} in f-string template. \"\n                f\"Variable names cannot contain attribute \"\n                f\"access (.) or indexing ([]).\"\n            )\n            raise ValueError(msg)\n\n        if var.isdigit():\n            msg = (\n                f\"Invalid variable name {var!r} in f-string template. \"\n                f\"Variable names cannot be all digits as they are interpreted \"\n                f\"as positional arguments.\"\n            )\n            raise ValueError(msg)\n\n        if format_spec and (\"{\" in format_spec or \"}\" in format_spec):\n            msg = (\n                \"Invalid format specifier in f-string template. \"\n                \"Nested replacement fields are not allowed.\"\n            )\n            raise ValueError(msg)\n\n        input_variables.add(var)\n","sourceCodeStart":222,"sourceCodeEnd":258,"githubUrl":"https://github.com/langchain-ai/langchain/blob/e32fa9a52eab3b61ad7a45399bfde59b3e580fc4/libs/core/langchain_core/prompts/string.py#L222-L258","documentation":"Raised by `validate_f_string_template` when a template using `template_format='f-string'` contains a replacement field whose variable name includes attribute access or indexing, such as `{obj.attr}` or `{items[0]}`. LangChain formats templates with `str.format`-style replacement but only allows plain variable names, because attribute/index access would require passing real objects rather than the plain values LangChain prompt inputs carry. The offending name is included via `{var!r}`.","triggerScenarios":"`PromptTemplate.from_template(\"Hello {user.name}\")` or any template containing `{a.b}` / `{list[0]}` while `template_format` is the default 'f-string'. Validation runs during `check_valid_template` at construction (when validating) or during `validate_f_string_template` calls like variable inference.","commonSituations":"Copying Python f-string code (`f\"{user.name}\"`) into a prompt template verbatim; templates authored for Jinja2 (`{{ obj.attr }}` converted to `{obj.attr}`) where attribute access is expected; prompt engineers assuming full str.format semantics.","solutions":["Restructure the input so the value is passed directly: pre-compute `name = user.name` and use `{name}` in the template","If real attribute traversal is required, switch the template format to `template_format=\"jinja2\"` (after installing jinja2), which supports attribute access","For indexing, pass the element itself: compute `first = items[0]` and reference `{first}`"],"exampleFix":"# before\nprompt = PromptTemplate.from_template(\"Hello {user.name}, you have {items[0]} message\")\n\n# after\nprompt = PromptTemplate.from_template(\"Hello {name}, you have {first_message}\")\nprompt.format(name=user.name, first_message=items[0])","handlingStrategy":"validation","validationCode":"import re\n\ndef has_clean_fstring_vars(template: str) -> bool:\n    return not any((\".\" in v or \"[\" in v or \"]\" in v)\n                   for v in re.findall(r\"\\{([a-zA-Z_][a-zA-Z0-9_.\\[\\]]*)\\}\", template))\n\nassert has_clean_fstring_vars(template)  # before PromptTemplate.from_template(template)","typeGuard":null,"tryCatchPattern":"try:\n    p = PromptTemplate.from_template(template)\nexcept ValueError as e:\n    if \"attribute access\" in str(e) or \"indexing\" in str(e):\n        # flatten inputs instead: pass user.name as name\n        raise ValueError(f\"flatten dotted/indexed inputs before templating: {e}\") from e\n    raise","preventionTips":["Never copy Python f-strings with attribute access into prompt templates verbatim","Flatten inputs to plain primitives before formatting (dicts of str/int)","Choose template_format='jinja2' up front if attribute traversal is a real requirement"],"tags":["prompts","f-string","template-validation","valueerror"],"backgroundTag":null,"analyzedSha":"e32fa9a52eab3b61ad7a45399bfde59b3e580fc4","analyzedAt":"2026-08-14T18:42:09.092Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}