honcho icon indicating copy to clipboard operation
honcho copied to clipboard

feat: add jinja templating for prompts

Open Rajat-Ahuja1997 opened this issue 5 months ago • 1 comments

  • Resolves https://linear.app/plastic-labs/issue/DEV-1194

Summary by CodeRabbit

  • New Features

    • Prompt system moved to template-driven rendering with several new customizable templates and a template rendering utility.
    • New dream consolidation settings and related template path added; additional deriver and dialectic template paths added.
  • Documentation

    • Example config and env templates updated with new template path entries and commented placeholders.
  • Chores

    • Added Jinja2 dependency.
  • Tests

    • Added tests for template rendering and prompt generation.

Rajat-Ahuja1997 avatar Nov 03 '25 18:11 Rajat-Ahuja1997

Walkthrough

Externalizes prompt construction into Jinja2 templates and a TemplateManager; adds templates and template-path config entries; refactors deriver, dialectic, and dreamer prompt functions to render templates; adds Jinja2 dependency and updates dependency-group format; and adds tests for template rendering and prompts.

Changes

Cohort / File(s) Summary
Configuration & Packaging
\.env.template, config.toml.example, pyproject.toml
Adds commented template path variables to .env.template; introduces [dream] config and several template-path keys in config.toml.example; adds jinja2>=3.1.6 and migrates dev deps to [dependency-groups] in pyproject.toml.
Config Schema
src/config.py
Adds template-path fields and defaults: DeriverSettings (CRITICAL_ANALYSIS_TEMPLATE, PEER_CARD_TEMPLATE), DialecticSettings (DIALECTIC_TEMPLATE, QUERY_GENERATION_TEMPLATE), and DreamSettings (CONSOLIDATION_TEMPLATE).
Prompt Refactor — Deriver
src/deriver/prompts.py
Replaces inline prompt assembly with render_template(settings.DERIVER.*, context) calls; removes cleandoc and internal string assembly; imports settings and render_template.
Prompt Refactor — Dialectic
src/dialectic/prompts.py
Replaces inline prompt construction with render_template() using settings.DIALECTIC.*; updates dialectic_prompt to accept observer and observed as keyword-only and to use observer_peer_card/observed_peer_card naming in context.
Prompt Refactor — Dreamer
src/dreamer/prompts.py
Replaces inline consolidation prompt with render_template(settings.DREAM.CONSOLIDATION_TEMPLATE, {...}); removes cleandoc and returns rendered template.
Template Rendering Infra
src/utils/templates.py
Adds TemplateManager using Jinja2 PackageLoader for src/templates, configures env (autoescape, whitespace, join_lines filter), provides cached get_template_manager() and render_template() helper with error propagation and logging.
Jinja2 Templates
src/templates/...
src/templates/deriver/critical_analysis.jinja, src/templates/deriver/peer_card.jinja, src/templates/dialectic/dialectic.jinja, src/templates/dialectic/query_generation.jinja, src/templates/dreamer/consolidation.jinja
Adds five templates capturing prior inline prompt logic (critical analysis, peer card, dialectic synthesis, query generation, consolidation) with placeholders and conditional blocks for optional data.
Tests
tests/utils/test_templates.py
Adds tests for TemplateManager, render_template, and prompt rendering (deriver, dialectic, dreamer) covering edge cases, deterministic behavior, special-character handling, and None/empty inputs.

Sequence Diagram(s)

sequenceDiagram
    participant PromptFn as Prompt Function
    participant Settings as Config Settings
    participant API as render_template()
    participant TM as TemplateManager
    participant Jinja as Jinja2 Env

    rect rgb(235,245,255)
    Note over PromptFn,Settings: template-driven prompt generation
    PromptFn->>Settings: read TEMPLATE path (e.g. DERIVER.CRITICAL_ANALYSIS_TEMPLATE)
    PromptFn->>API: render_template(template_name, context)
    API->>TM: get_template_manager()
    TM->>Jinja: env.get_template(template_name)
    Jinja-->>TM: Template object
    TM->>Jinja: template.render(context)
    Jinja-->>TM: rendered string
    TM-->>API: trimmed rendered string
    API-->>PromptFn: return prompt text
    end

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

  • Pay special attention to:
    • Behavioral parity between previous inline prompt text and the new Jinja templates (critical_analysis, dialectic).
    • dialectic_prompt signature change and all call sites.
    • TemplateManager error handling, template lookup paths, and the join_lines filter semantics.
    • Tests covering None/empty inputs, deterministic outputs, and special-character escaping.

"I stitched my thoughts in a Jinja thread,
Templates hum where lines once spread.
Configs set, tests hop in queue,
I render, I sniff—new prompts anew.
🐇🥕"

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding Jinja templating for prompts across the codebase.
Linked Issues check ✅ Passed The PR fully addresses DEV-1194 objectives: prompts are moved to Jinja templates in dedicated files, and the template rendering system enables ML team integration for dataset generation.
Out of Scope Changes check ✅ Passed All changes are scoped to Jinja templating: template files added, config updated with template paths, and dependency (jinja2) added. The dev-dependencies restructuring in pyproject.toml aligns with the dependency addition.
Docstring Coverage ✅ Passed Docstring coverage is 97.14% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • [ ] 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • [ ] Create PR with unit tests
  • [ ] Post copyable unit tests in a comment
  • [ ] Commit unit tests in branch rajat/prompt-jinja

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0a81b107c6eba50ddc242ec9d378a83c0be145ba and 2e00447b67726f35317fdeee6a1e59983dcac359.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (14)
  • .env.template (3 hunks)
  • config.toml.example (3 hunks)
  • pyproject.toml (1 hunks)
  • src/config.py (3 hunks)
  • src/deriver/prompts.py (3 hunks)
  • src/dialectic/prompts.py (3 hunks)
  • src/dreamer/prompts.py (2 hunks)
  • src/templates/deriver/critical_analysis.jinja (1 hunks)
  • src/templates/deriver/peer_card.jinja (1 hunks)
  • src/templates/dialectic/dialectic.jinja (1 hunks)
  • src/templates/dialectic/query_generation.jinja (1 hunks)
  • src/templates/dreamer/consolidation.jinja (1 hunks)
  • src/utils/templates.py (1 hunks)
  • tests/utils/test_templates.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Follow isort conventions with absolute imports preferred Use snake_case for variables and functions; PascalCase for classes Limit line length to 88 characters (Black compatible) Use explicit error handling with appropriate exception types Write Google style docstrings Use structured logging with context instead of print statements Lint code with Ruff Type-check code with basedpyright Format code with Ruff formatter

Files:

  • src/dreamer/prompts.py
  • tests/utils/test_templates.py
  • src/utils/templates.py
  • src/config.py
  • src/deriver/prompts.py
  • src/dialectic/prompts.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Write tests using pytest

Files:

  • tests/utils/test_templates.py
src/config.py

📄 CodeRabbit inference engine (CLAUDE.md)

Load configuration using hierarchical config (config.toml overridden by environment variables)

Files:

  • src/config.py
src/deriver/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Implement background processing (deriver) under src/deriver/

Files:

  • src/deriver/prompts.py
src/dialectic/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Keep Dialectic API logic and prompts under src/dialectic/

Files:

  • src/dialectic/prompts.py
🧠 Learnings (8)
📓 Common learnings
Learnt from: Rajat-Ahuja1997
Repo: plastic-labs/honcho PR: 144
File: src/crud.py:1250-1250
Timestamp: 2025-06-26T18:39:54.942Z
Learning: Rajat-Ahuja1997 is comfortable exposing token counts in MessageCreate schema as a public property when it improves code maintainability and avoids type checking errors.
📚 Learning: 2025-08-13T18:53:55.318Z
Learnt from: dr-frmr
Repo: plastic-labs/honcho PR: 192
File: src/deriver/prompts.py:61-70
Timestamp: 2025-08-13T18:53:55.318Z
Learning: In src/deriver/prompts.py, the critical_analysis_prompt function intentionally uses "if history is not None" rather than "if history" to preserve the semantic distinction between None (no history provided) and empty string (processed but empty history content).

Applied to files:

  • tests/utils/test_templates.py
  • src/deriver/prompts.py
📚 Learning: 2025-06-26T18:39:54.942Z
Learnt from: Rajat-Ahuja1997
Repo: plastic-labs/honcho PR: 144
File: src/crud.py:1250-1250
Timestamp: 2025-06-26T18:39:54.942Z
Learning: Rajat-Ahuja1997 is comfortable exposing token counts in MessageCreate schema as a public property when it improves code maintainability and avoids type checking errors.

Applied to files:

  • tests/utils/test_templates.py
📚 Learning: 2025-09-25T19:53:21.360Z
Learnt from: Rajat-Ahuja1997
Repo: plastic-labs/honcho PR: 218
File: src/deriver/deriver.py:169-183
Timestamp: 2025-09-25T19:53:21.360Z
Learning: In the deriver token estimation, the approach uses empty inputs to get the base prompt template token count via `estimate_base_prompt_tokens()`, then manually adds tokens for actual inputs (peer_card, working_representation, new_turns). This avoids double-counting wrapper overhead since the base prompt already includes template structure.

Applied to files:

  • config.toml.example
  • .env.template
  • src/config.py
  • src/deriver/prompts.py
  • src/dialectic/prompts.py
📚 Learning: 2025-09-25T19:50:53.145Z
Learnt from: Rajat-Ahuja1997
Repo: plastic-labs/honcho PR: 218
File: src/config.py:223-224
Timestamp: 2025-09-25T19:50:53.145Z
Learning: In src/config.py, the MAX_INPUT_TOKENS field has le=23000 constraint which is intentional due to model limitations, not a configuration restriction. The underlying AI model has a hard cap of 23000 tokens for input.

Applied to files:

  • config.toml.example
  • src/config.py
📚 Learning: 2025-10-07T19:30:13.747Z
Learnt from: CR
Repo: plastic-labs/honcho PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-07T19:30:13.747Z
Learning: Applies to src/dialectic/**/*.py : Keep Dialectic API logic and prompts under src/dialectic/

Applied to files:

  • config.toml.example
  • .env.template
  • src/dialectic/prompts.py
📚 Learning: 2025-09-25T15:45:13.243Z
Learnt from: Rajat-Ahuja1997
Repo: plastic-labs/honcho PR: 216
File: src/deriver/queue_manager.py:10-10
Timestamp: 2025-09-25T15:45:13.243Z
Learning: In the honcho project, nanoid>=2.0.0 is properly declared as a dependency in pyproject.toml, so nanoid imports work correctly at runtime.

Applied to files:

  • pyproject.toml
📚 Learning: 2025-09-25T15:45:13.243Z
Learnt from: Rajat-Ahuja1997
Repo: plastic-labs/honcho PR: 216
File: src/deriver/queue_manager.py:10-10
Timestamp: 2025-09-25T15:45:13.243Z
Learning: The nanoid dependency is declared in pyproject.toml for the honcho project, so imports of nanoid should work correctly at runtime.

Applied to files:

  • pyproject.toml
🧬 Code graph analysis (4)
src/dreamer/prompts.py (2)
src/utils/representation.py (1)
  • Representation (101-319)
src/utils/templates.py (1)
  • render_template (83-95)
tests/utils/test_templates.py (5)
src/deriver/prompts.py (2)
  • critical_analysis_prompt (17-50)
  • peer_card_prompt (53-74)
src/dialectic/prompts.py (2)
  • dialectic_prompt (5-41)
  • query_generation_prompt (44-61)
src/dreamer/prompts.py (1)
  • consolidation_prompt (6-23)
src/utils/representation.py (3)
  • DeductiveObservation (70-98)
  • ExplicitObservation (44-67)
  • Representation (101-319)
src/utils/templates.py (4)
  • TemplateManager (18-69)
  • get_template_manager (73-80)
  • render_template (83-95)
  • join_lines (39-41)
src/deriver/prompts.py (2)
src/utils/representation.py (2)
  • Representation (101-319)
  • is_empty (129-133)
src/utils/templates.py (1)
  • render_template (83-95)
src/dialectic/prompts.py (1)
src/utils/templates.py (1)
  • render_template (83-95)
🪛 Ruff (0.14.4)
tests/utils/test_templates.py

282-282: Implicitly concatenated string literals on one line

Combine string literals

(ISC001)


282-282: Implicitly concatenated string literals on one line

Combine string literals

(ISC001)


308-308: Implicitly concatenated string literals on one line

Combine string literals

(ISC001)


311-311: Implicitly concatenated string literals on one line

Combine string literals

(ISC001)

src/utils/templates.py

39-39: Dynamically typed expressions (typing.Any) are disallowed in items

(ANN401)


62-62: Logging statement uses f-string

(G004)


63-63: Use raise without specifying exception name

Remove exception name

(TRY201)


65-65: Logging statement uses f-string

(G004)


66-66: Use raise without specifying exception name

Remove exception name

(TRY201)


68-68: Logging statement uses f-string

(G004)


69-69: Use raise without specifying exception name

Remove exception name

(TRY201)

🔇 Additional comments (1)
src/dreamer/prompts.py (1)

20-22: Centralizing consolidation prompt via templates looks great.

render_template integration keeps the JSON payload intact and routes through the shared manager, so the consolidation prompt now benefits from the same templating pipeline as the other prompt modules.


Comment @coderabbitai help to get the list of available commands and usage tips.

coderabbitai[bot] avatar Nov 03 '25 18:11 coderabbitai[bot]