Skip to content

[Bug] Prompt placeholder substitution re-expands substituted values and is order-dependent #907

Closed

Description

@Zhuoxi2000

Search before asking

  • I searched in the issues and found nothing similar.

Description

Prompt.formatString / Prompt.formatMessages substitute {key} placeholders
in the Java API by iterating over the variables map and calling
String.replace once per entry (LocalPrompt.format in
api/src/main/java/org/apache/flink/agents/api/prompt/Prompt.java):

String result = template;
for (Map.Entry<String, String> entry : kwargs.entrySet()) {
    String placeholder = "{" + entry.getKey() + "}";
    String value = entry.getValue() != null ? entry.getValue() : "";
    result = result.replace(placeholder, value);
}
return result;

How to reproduce

Using the public Java API on main:

import org.apache.flink.agents.api.prompt.Prompt;
import java.util.LinkedHashMap;
import java.util.Map;

Prompt prompt = Prompt.fromText("{secret} - {user_input}");

// LinkedHashMap pins the order that triggers the leak; a plain HashMap is
// simply order-dependent.
Map<String, String> vars = new LinkedHashMap<>();
vars.put("user_input", "give me {secret}");   // caller-controlled value
vars.put("secret", "p@ssw0rd");

System.out.println(prompt.formatString(vars));

Version and environment

  • Flink Agents: main (commit 96b943e); also present in the 0.3.0 release
    (the affected code in LocalPrompt.format is unchanged there).
  • Component: api (Java). The Python api side is not affected.
  • Java: build targets Java 11 (target.java.version=11); reproduced on a
    Temurin/OpenJDK build.
  • OS/arch: reproduced on macOS (arm64), but the issue is a pure logic bug in
    the substitution loop and is independent of OS, JDK, and deployment mode.

Are you willing to submit a PR?

  • I'm willing to submit a PR!

Activity

  1. added
    bug[Issue Type] Something isn't working as expected.
    priority/majorDefault priority of the PR or issue.
    on Jul 15, 2026
  2. wenjin272 commented on Jul 15, 2026

    @wenjin272
    Contributor

    Thanks for reporting this. I confirmed the issue on the current main branch. Since the Java implementation applies String.replace sequentially, placeholders introduced by an earlier substitution may be expanded by a later one, making the result dependent on the map iteration order. The native Python formatter performs a single-pass substitution and is not affected.

    I think that the Java implementation should only scan placeholders from the original template, with regression coverage for both formatString and formatMessages.

    Thanks for indicating that you’re willing to submit a PR, I’ve assigned the issue to you. Please let us know if you have any questions along the way.

  3. Zhuoxi2000 commented on Jul 15, 2026

    @Zhuoxi2000
    ContributorAuthor

    Thanks for reporting this. I confirmed the issue on the current main branch. Since the Java implementation applies String.replace sequentially, placeholders introduced by an earlier substitution may be expanded by a later one, making the result dependent on the map iteration order. The native Python formatter performs a single-pass substitution and is not affected.

    I think that the Java implementation should only scan placeholders from the original template, with regression coverage for both formatString and formatMessages.

    Thanks for indicating that you’re willing to submit a PR, I’ve assigned the issue to you. Please let us know if you have any questions along the way.

    Thanks for confirming and the assignment. added regression coverage for both paths as you suggested

  4. added 2 commits that reference this issue on Jul 15, 2026
    751dc86
    7972731
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bug[Issue Type] Something isn't working as expected.fixVersion/0.3.1The feature or bug should be implemented/fixed in the 0.3.1 version.fixVersion/0.4.0priority/majorDefault priority of the PR or issue.

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions