ToolCallingLLMCaller does not render Jinja2 template variables in actor system prompts #11221

Open
opened 2026-05-15 05:36:22 +00:00 by hurui200320 · 0 comments
Member

Metadata

Commit Message: feat(actor-run): render Jinja2 system prompt templates in ToolCallingLLMCaller
Branch: feature/m3-tool-calling-jinja2-context

Background and Context

SimpleLLMAgent.process() renders the actor's system_prompt: config through
Jinja2 before handing it to the LLM, using the graph's global context dict
(e.g. {"writing_stage": "intro", "paper_details": {...}}). This lets system
prompts use template expressions:

system_prompt: |
  You are a {{ writing_stage }} writer.
  Topic: {{ paper_details.topic }}

ToolCallingAgent.process() — introduced in #11211 — receives the same
context dict, but never forwards it to ToolCallingLLMCaller.invoke().
The system prompt is therefore used as a raw string; any {{ ... }} expressions
appear verbatim in the SystemMessage sent to the LLM.

ToolCallingLLMCaller already has _render_prompt() and self._template_env
(a jinja2.sandbox.SandboxedEnvironment), but they are never called during
invoke().

Expected Behavior

When ToolCallingLLMCaller.invoke() builds the SystemMessage, it should call
_render_prompt(system_prompt, context) so that template expressions in the
actor's system_prompt: are expanded against the graph's global context —
exactly as SimpleLLMAgent does.

Acceptance Criteria

  • An actor config with system_prompt: "You are a {{ stage }} expert." and
    a context dict {"stage": "code-review"} produces a SystemMessage with
    content "You are a code-review expert." when run via ToolCallingAgent.
  • An actor config with no template expressions in system_prompt is
    unaffected (render is a no-op for plain strings).
  • If context is None or empty, rendering falls back gracefully (no
    exception; unexpanded variables remain as-is per Jinja2 default).
  • Existing actor run BDD scenarios continue to pass.

Supporting Information

Relevant code locations:

  • src/cleveragents/reactive/tool_agent.pyToolCallingAgent.process()
    receives context but does not forward it.
  • src/cleveragents/reactive/tool_caller.pyToolCallingLLMCaller.invoke()
    needs to accept and use a render_context dict for Jinja2 rendering.
  • src/cleveragents/reactive/stream_router.pySimpleLLMAgent._render_prompt()
    is the reference implementation to follow.

Introduced as follow-on to #11211.

Subtasks

  • Add optional render_context: dict[str, Any] | None = None parameter to
    ToolCallingLLMCaller.__init__() so the caller can hold the context for the
    lifetime of the run.
  • In ToolCallingLLMCaller.invoke(), call
    self._render_prompt(system_prompt, self._render_context or {}) before
    building the SystemMessage (same pattern as SimpleLLMAgent.process()).
  • In ToolCallingAgent.process(), pass render_context=context when
    constructing ToolCallingLLMCaller(self._actor_config, render_context=context).
  • Write BDD scenarios:
    (A) system prompt with {{ var }} is expanded correctly;
    (B) plain system prompt is unchanged;
    (C) context=None does not raise.
  • Verify coverage ≥ 96.5% via nox -s coverage_report.
  • Run nox (all default sessions), fix any errors.

Definition of Done

This issue is complete when:

  • All subtasks above are completed and checked off.
  • A Git commit is created where the first line of the commit message
    matches the Commit Message in Metadata exactly, followed by a blank line,
    then additional lines providing relevant details about the implementation.
  • The commit is pushed to the remote on the branch matching the Branch in
    Metadata exactly.
  • The commit is submitted as a pull request to master, reviewed, and
    merged before this issue is marked done.
## Metadata ``` Commit Message: feat(actor-run): render Jinja2 system prompt templates in ToolCallingLLMCaller Branch: feature/m3-tool-calling-jinja2-context ``` ## Background and Context `SimpleLLMAgent.process()` renders the actor's `system_prompt:` config through Jinja2 before handing it to the LLM, using the graph's global context dict (e.g. `{"writing_stage": "intro", "paper_details": {...}}`). This lets system prompts use template expressions: ```yaml system_prompt: | You are a {{ writing_stage }} writer. Topic: {{ paper_details.topic }} ``` `ToolCallingAgent.process()` — introduced in #11211 — receives the same `context` dict, but never forwards it to `ToolCallingLLMCaller.invoke()`. The system prompt is therefore used as a raw string; any `{{ ... }}` expressions appear verbatim in the `SystemMessage` sent to the LLM. `ToolCallingLLMCaller` already has `_render_prompt()` and `self._template_env` (a `jinja2.sandbox.SandboxedEnvironment`), but they are never called during `invoke()`. ## Expected Behavior When `ToolCallingLLMCaller.invoke()` builds the `SystemMessage`, it should call `_render_prompt(system_prompt, context)` so that template expressions in the actor's `system_prompt:` are expanded against the graph's global context — exactly as `SimpleLLMAgent` does. ## Acceptance Criteria - [ ] An actor config with `system_prompt: "You are a {{ stage }} expert."` and a context dict `{"stage": "code-review"}` produces a `SystemMessage` with content `"You are a code-review expert."` when run via `ToolCallingAgent`. - [ ] An actor config with no template expressions in `system_prompt` is unaffected (render is a no-op for plain strings). - [ ] If `context` is `None` or empty, rendering falls back gracefully (no exception; unexpanded variables remain as-is per Jinja2 default). - [ ] Existing `actor run` BDD scenarios continue to pass. ## Supporting Information Relevant code locations: - `src/cleveragents/reactive/tool_agent.py` — `ToolCallingAgent.process()` receives `context` but does not forward it. - `src/cleveragents/reactive/tool_caller.py` — `ToolCallingLLMCaller.invoke()` needs to accept and use a `render_context` dict for Jinja2 rendering. - `src/cleveragents/reactive/stream_router.py` — `SimpleLLMAgent._render_prompt()` is the reference implementation to follow. Introduced as follow-on to #11211. ## Subtasks - [ ] Add optional `render_context: dict[str, Any] | None = None` parameter to `ToolCallingLLMCaller.__init__()` so the caller can hold the context for the lifetime of the run. - [ ] In `ToolCallingLLMCaller.invoke()`, call `self._render_prompt(system_prompt, self._render_context or {})` before building the `SystemMessage` (same pattern as `SimpleLLMAgent.process()`). - [ ] In `ToolCallingAgent.process()`, pass `render_context=context` when constructing `ToolCallingLLMCaller(self._actor_config, render_context=context)`. - [ ] Write BDD scenarios: (A) system prompt with `{{ var }}` is expanded correctly; (B) plain system prompt is unchanged; (C) `context=None` does not raise. - [ ] Verify coverage ≥ 96.5% via `nox -s coverage_report`. - [ ] Run `nox` (all default sessions), fix any errors. ## Definition of Done This issue is complete when: - All subtasks above are completed and checked off. - A Git commit is created where the **first line** of the commit message matches the Commit Message in Metadata exactly, followed by a blank line, then additional lines providing relevant details about the implementation. - The commit is pushed to the remote on the branch matching the **Branch** in Metadata exactly. - The commit is submitted as a **pull request** to `master`, reviewed, and **merged** before this issue is marked done.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
cleveragents/cleveragents-core#11221
No description provided.