mirror of
https://github.com/vegu-ai/talemate.git
synced 2026-09-01 19:48:52 +02:00
refactor revision rewrite
This commit is contained in:
@@ -38,3 +38,4 @@
|
||||
- "Client Config UX: Moved advanced settings (Inference Presets, Structured Data Format, Section Format, Response Length Enforcement, Prompt Caching, Rate Limit) into a dedicated Advanced tab to declutter the general view. A convenient link in the general tab provides quick access. Toggling Simple View on now resets to the General tab, and switching clients always starts on General."
|
||||
- "Prompts Tab Alert: The top-level Prompts tab now shows a warning icon when any active template overrides are outdated, so you can spot them without opening the tab."
|
||||
- "Action Confirm Timeout: The director chat action confirmation timeout is now configurable (0–60 minutes, default 3). Set to 0 to wait indefinitely. Found in the Director Chat settings under Action Confirmation."
|
||||
- "Rewrite Revision: Collapsed the targeted rewrite method from two prompts into one. The analysis and rewrite are now produced in a single LLM call with the final revision extracted from a <REVISION> tag, halving latency and token cost. Added a length guardrail that discards rewrites substantially longer than the original (mirroring the unslop safeguard), and a dynamic response length budget sized to the draft."
|
||||
|
||||
@@ -361,9 +361,16 @@ Handlers can edit `response` in-place to clean up or transform the text (the Edi
|
||||
---
|
||||
### agent.editor.revision-analysis.before
|
||||
|
||||
Emitted **before** the Editor agent requests the revision-analysis prompt.
|
||||
Emitted **before** the Editor agent requests the revision-rewrite prompt.
|
||||
Handlers can add extra analysis instructions via `dynamic_instructions` or adjust `template_vars`.
|
||||
|
||||
!!! note
|
||||
|
||||
The signal is named `revision-analysis.*` for historical reasons — it was
|
||||
introduced when analysis and rewrite were two separate prompts. They are
|
||||
now combined into a single prompt (`editor.revision-rewrite`), but the
|
||||
signal name is kept for backward compatibility.
|
||||
|
||||
!!! payload "Payload"
|
||||
|
||||
| Field | Type | Notes |
|
||||
@@ -376,8 +383,18 @@ Handlers can add extra analysis instructions via `dynamic_instructions` or adjus
|
||||
|
||||
### agent.editor.revision-analysis.after
|
||||
|
||||
Emitted after the revision-analysis prompt returns but **before** the rewrite is requested.
|
||||
Handlers may inspect or replace the `response` string.
|
||||
Emitted after the revision-rewrite prompt returns. Intended as a
|
||||
notification hook for observers of the rewrite flow.
|
||||
|
||||
!!! warning
|
||||
|
||||
Historically this signal fired between a separate analysis prompt and a
|
||||
rewrite prompt, with `response` carrying the raw analysis text that
|
||||
handlers could mutate before the rewrite ran. Analysis and rewrite are
|
||||
now combined into a single prompt, so there is no separable "analysis
|
||||
text" — `response` is not set on the emission and any mutation is
|
||||
discarded. Use [`agent.editor.revision-revise.after`](#agenteditorrevision-reviseafter)
|
||||
if you need to inspect or replace the final rewritten text.
|
||||
|
||||
!!! payload "Payload"
|
||||
|
||||
@@ -385,7 +402,6 @@ Handlers may inspect or replace the `response` string.
|
||||
|-------|------|-------|
|
||||
| `agent` | `EditorAgent` | The agent instance |
|
||||
| `template_vars` | `dict` | Same vars used for the prompt |
|
||||
| `response` | `str` | **Mutable.** Raw analysis text returned by the model |
|
||||
|
||||
## Narrator Agent Events
|
||||
|
||||
|
||||
@@ -42,7 +42,6 @@ from talemate.util import count_tokens
|
||||
from talemate.prompts import Prompt
|
||||
from talemate.prompts.response import ResponseSpec, AnchorExtractor
|
||||
from talemate.exceptions import GenerationCancelled
|
||||
import talemate.game.focal as focal
|
||||
from talemate.status import LoadingStatus
|
||||
from talemate.world_state.templates.content import PhraseDetection
|
||||
from contextvars import ContextVar
|
||||
@@ -61,6 +60,13 @@ FIX_SPEC = ResponseSpec(
|
||||
required=[], # Not required - we handle None case
|
||||
)
|
||||
|
||||
REWRITE_SPEC = ResponseSpec(
|
||||
extractors={
|
||||
"revision": AnchorExtractor(left="<REVISION>", right="</REVISION>"),
|
||||
},
|
||||
required=[], # Not required - we handle None case
|
||||
)
|
||||
|
||||
## CONFIG CONDITIONALS
|
||||
|
||||
dedupe_condition = AgentActionConditional(
|
||||
@@ -182,6 +188,26 @@ class RevisionEmission(AgentTemplateEmission):
|
||||
# the fix as hallucinated content. Unslop should trim, not expand.
|
||||
UNSLOP_MAX_LENGTH_RATIO = 1.25
|
||||
|
||||
# Maximum ratio of revision length to original length before discarding
|
||||
# the rewrite as hallucinated content. Rewrites should stay close to the
|
||||
# original length — the analysis template explicitly forbids expansion.
|
||||
REWRITE_MAX_LENGTH_RATIO = 1.25
|
||||
|
||||
# Extra token headroom added on top of the draft's token count when sizing
|
||||
# the response budget for revision prompts. Gives the model room for the
|
||||
# analysis preamble before it emits the wrapped rewrite.
|
||||
REVISION_RESPONSE_HEADROOM = 768
|
||||
|
||||
|
||||
def _format_length_ratio(new_len: int, original_len: int) -> str:
|
||||
"""
|
||||
Format a length ratio for log output, guarding against division by zero
|
||||
when the original text is empty.
|
||||
"""
|
||||
if not original_len:
|
||||
return "inf"
|
||||
return f"{new_len / original_len:.2f}"
|
||||
|
||||
## MIXIN
|
||||
|
||||
|
||||
@@ -266,7 +292,7 @@ class RevisionMixin:
|
||||
),
|
||||
"rewrite": AgentActionNote(
|
||||
color="primary",
|
||||
text="Each generation will be checked for repetition and unwanted prose. If issues are found, a rewrite of the problematic part(s) will be attempted. (+2 prompts)",
|
||||
text="Each generation will be checked for repetition and unwanted prose. If issues are found, a rewrite of the problematic part(s) will be attempted. (+1 prompt)",
|
||||
),
|
||||
},
|
||||
),
|
||||
@@ -950,7 +976,12 @@ class RevisionMixin:
|
||||
info: RevisionInformation,
|
||||
) -> str:
|
||||
"""
|
||||
Revise the text by rewriting
|
||||
Revise the text by rewriting.
|
||||
|
||||
Runs a single analysis+rewrite prompt and extracts the rewritten text
|
||||
from a ``<REVISION>...</REVISION>`` anchor. If the anchor is missing,
|
||||
or the rewrite balloons past ``REWRITE_MAX_LENGTH_RATIO``, the original
|
||||
text is returned unchanged.
|
||||
"""
|
||||
|
||||
text = info.text
|
||||
@@ -966,9 +997,6 @@ class RevisionMixin:
|
||||
|
||||
issues = await self.revision_collect_issues(text, character)
|
||||
|
||||
if loading_status:
|
||||
loading_status.max_steps = 2
|
||||
|
||||
num_issues = len(issues.log)
|
||||
|
||||
if not num_issues:
|
||||
@@ -994,13 +1022,13 @@ class RevisionMixin:
|
||||
)
|
||||
return original_text
|
||||
|
||||
# Step 4 - Rewrite
|
||||
token_count = count_tokens(text)
|
||||
response_length = token_count + REVISION_RESPONSE_HEADROOM
|
||||
|
||||
log.debug("revision_rewrite: token_count", token_count=token_count)
|
||||
|
||||
if loading_status:
|
||||
loading_status("Editor - Issues identified, analyzing text...")
|
||||
loading_status("Editor - Issues identified, rewriting text...")
|
||||
|
||||
emission = RevisionEmission(
|
||||
agent=self,
|
||||
@@ -1012,7 +1040,7 @@ class RevisionMixin:
|
||||
"text": text,
|
||||
"character": character,
|
||||
"scene": self.scene,
|
||||
"response_length": token_count,
|
||||
"response_length": response_length,
|
||||
"max_tokens": self.client.max_token_length,
|
||||
"repetition": issues.repetition,
|
||||
"bad_prose": issues.bad_prose,
|
||||
@@ -1024,52 +1052,39 @@ class RevisionMixin:
|
||||
await async_signals.get("agent.editor.revision-revise.before").send(emission)
|
||||
await async_signals.get("agent.editor.revision-analysis.before").send(emission)
|
||||
|
||||
analysis, extracted = await Prompt.request(
|
||||
"editor.revision-analysis",
|
||||
_, extracted = await Prompt.request(
|
||||
"editor.revision-rewrite",
|
||||
self.client,
|
||||
"edit_768",
|
||||
f"edit_{response_length}",
|
||||
vars=emission.template_vars,
|
||||
dedupe_enabled=False,
|
||||
response_spec=REWRITE_SPEC,
|
||||
)
|
||||
|
||||
async def rewrite_text(text: str) -> str:
|
||||
return text
|
||||
|
||||
analysis = extracted["response"]
|
||||
emission.response = analysis
|
||||
# The analysis-after signal is fired as a notification hook for
|
||||
# backward compatibility. Analysis and rewrite now share a single
|
||||
# prompt, so there is no separable analysis text to expose on the
|
||||
# emission — listeners that previously mutated `emission.response`
|
||||
# here will have no effect. See docs/user-guide/node-editor/reference/events.md.
|
||||
await async_signals.get("agent.editor.revision-analysis.after").send(emission)
|
||||
analysis = emission.response
|
||||
|
||||
focal_handler = focal.Focal(
|
||||
self.client,
|
||||
callbacks=[
|
||||
focal.Callback(
|
||||
name="rewrite_text",
|
||||
arguments=[
|
||||
focal.Argument(name="text", type="str", preserve_newlines=True),
|
||||
],
|
||||
fn=rewrite_text,
|
||||
multiple=False,
|
||||
),
|
||||
],
|
||||
max_calls=1,
|
||||
retries=1,
|
||||
scene=self.scene,
|
||||
analysis=analysis,
|
||||
text=text,
|
||||
)
|
||||
revision = extracted["revision"]
|
||||
if revision is None:
|
||||
log.debug(
|
||||
"revision_rewrite: no <REVISION> found in response, keeping original"
|
||||
)
|
||||
return original_text
|
||||
|
||||
if loading_status:
|
||||
loading_status("Editor - Rewriting text...")
|
||||
|
||||
await focal_handler.request(
|
||||
"editor.revision-rewrite",
|
||||
)
|
||||
|
||||
try:
|
||||
revision = focal_handler.state.calls[0].result
|
||||
except Exception as e:
|
||||
log.error("revision_rewrite: error", error=e)
|
||||
# Guard: if the rewrite is substantially longer than the original,
|
||||
# the model likely expanded beyond the "do not make it longer"
|
||||
# instruction — discard it.
|
||||
if len(revision) > len(text) * REWRITE_MAX_LENGTH_RATIO:
|
||||
log.warning(
|
||||
"revision_rewrite: revision is too long, discarding",
|
||||
original_len=len(text),
|
||||
revision_len=len(revision),
|
||||
ratio=_format_length_ratio(len(revision), len(text)),
|
||||
)
|
||||
return original_text
|
||||
|
||||
emission.response = revision
|
||||
@@ -1105,7 +1120,7 @@ class RevisionMixin:
|
||||
async def revision_unslop(
|
||||
self,
|
||||
info: RevisionInformation,
|
||||
response_length: int = 768,
|
||||
response_length: int = REVISION_RESPONSE_HEADROOM,
|
||||
) -> str:
|
||||
"""
|
||||
Unslop the text
|
||||
@@ -1183,7 +1198,7 @@ class RevisionMixin:
|
||||
"revision_unslop: fix is too long, discarding",
|
||||
original_len=len(text),
|
||||
fix_len=len(fix),
|
||||
ratio=f"{len(fix) / len(text):.2f}",
|
||||
ratio=_format_length_ratio(len(fix), len(text)),
|
||||
)
|
||||
return original_text
|
||||
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
{#-
|
||||
Extractors:
|
||||
- response: AsIsExtractor()
|
||||
|
||||
Override example:
|
||||
{{ set_anchor_extractor("response", "<MY_TAG>", "</MY_TAG>") }}
|
||||
{{ set_as_is_extractor("response") }}
|
||||
-#}
|
||||
{# Using common templates approach #}
|
||||
{% set volatile_placement = volatile_context_placement() %}
|
||||
|
||||
{# RENDERED CONTEXT #}
|
||||
{% set rendered_context -%}
|
||||
{% include "character-context.jinja2" -%}
|
||||
<|SECTION:ADDITIONAL INFORMATION|>
|
||||
{% include "extra-context-static.jinja2" -%}
|
||||
<|CLOSE_SECTION|>
|
||||
{% include "scene-intent.jinja2" %}
|
||||
{% endset %}
|
||||
{# END RENDERED CONTEXT #}
|
||||
|
||||
{# VOLATILE CONTEXT #}
|
||||
{% set volatile_context_text %}
|
||||
{% include "memory-context.jinja2" %}
|
||||
{% include "extra-context-dynamic.jinja2" %}
|
||||
{% endset %}
|
||||
{# END VOLATILE CONTEXT #}
|
||||
|
||||
{# SCENE CONTEXT #}
|
||||
{% set scene_context_text %}
|
||||
{% set scene_context = scene.context_history(
|
||||
budget=max_tokens-300-count_tokens(rendered_context)-count_tokens(volatile_context_text),
|
||||
min_dialogue=15,
|
||||
sections=False,
|
||||
keep_director=False,
|
||||
chapter_labels=True
|
||||
)
|
||||
-%}
|
||||
{% for scene_line in scene_context -%}
|
||||
{{ scene_line }}
|
||||
|
||||
{% endfor %}
|
||||
{% endset %}
|
||||
{# END SCENE CONTEXT #}
|
||||
|
||||
{# RENDER PROMPT #}
|
||||
{{ rendered_context }}
|
||||
{% if volatile_placement != "after_history" %}
|
||||
{% with _text=scene_context_text %}{% include "internal-note-help.jinja2" %}{% endwith %}
|
||||
{{ volatile_context_text }}
|
||||
{% endif %}
|
||||
<|SECTION:SCENE|>
|
||||
{{ scene_context_text }}
|
||||
<|CLOSE_SECTION|>
|
||||
{% if volatile_placement == "after_history" %}
|
||||
{% with _text=scene_context_text %}{% include "internal-note-help.jinja2" %}{% endwith %}
|
||||
{{ volatile_context_text }}
|
||||
{% endif %}
|
||||
|
||||
<|SECTION:DRAFT TEXT|>
|
||||
{{ text }}
|
||||
<|CLOSE_SECTION|>
|
||||
{% set issues = 0 %}
|
||||
{# repetition #}{% if repetition %}
|
||||
{% set issues = issues + 1 %}
|
||||
<|SECTION:ISSUE {{ issues }}: REPEATED TEXT|>
|
||||
These sentences have been classified as repetition and must either be substantially rewritten or removed. When removing a sentence, substitute something else, that's meaningful to the story and context.
|
||||
|
||||
{% for repeat in repetition %}- MATCH: `{{ repeat["text_a"].strip() }}`
|
||||
{% endfor %}
|
||||
<|CLOSE_SECTION|>
|
||||
{# end repetition #}{% endif %}
|
||||
{# unwanted prose #}{% if bad_prose %}
|
||||
{% set issues = issues + 1 %}
|
||||
<|SECTION:ISSUE {{ issues }}: UNWANTED PROSE|>
|
||||
These phrases or words have been identified by the director as bad, and MUST be changed accordingly.
|
||||
Important: These are semantic matches based on meaning, not literal text. Focus on the underlying concept or idea that triggered the match, rather than expecting exact phrase matches.
|
||||
|
||||
You must state your greater understanding of why these issues were flagged in the first place so you do not replace them with something equally bad or worse.
|
||||
|
||||
{% for phrase in bad_prose %}- MATCH: `{{ phrase.phrase }}`
|
||||
- INSTRUCTIONS: {{ phrase.instructions }}
|
||||
{% endfor %}
|
||||
<|CLOSE_SECTION|>
|
||||
{# end unwanted prose #}{% endif %}
|
||||
|
||||
{% include "dynamic-instructions.jinja2" %}
|
||||
<|SECTION:TASK|>
|
||||
1. Analyze each issue separately, taking into account the instructions and guidelines for it.
|
||||
2. Specifically compare against previous messages, instead of rewriting the same information often it is better to just drop the repetitive sentence entirely. Its no use to convey the SAME information again just with different words. Repeat back your understanding of this concept.
|
||||
3. Tell us your confident plan for fixing the issues.
|
||||
4. Rewrite the text to fix the issues, the best you can, according to the plan you made.
|
||||
- Keep your changes minimal, don't get carried away.
|
||||
- YOU MUST NOT MAKE IT LONGER.
|
||||
- YOU MUST NOT REWRITE PARTS THAT WEREN'T EXPLICITLY FLAGGED.
|
||||
- Make one revision in total - so analyze issues first, then fix them all at once!
|
||||
|
||||
Repeat back your understanding of the task, then work and report each step 1 - 4, sequentially.
|
||||
<|CLOSE_SECTION|>
|
||||
@@ -1,26 +1,105 @@
|
||||
<|SECTION:DRAFT TEXT|>
|
||||
{{ text }}
|
||||
<|CLOSE_SECTION|>
|
||||
<|SECTION:FUNCTION CALLING INSTRUCTIONS|>
|
||||
{{ focal.render_instructions() }}
|
||||
{#-
|
||||
Extractors:
|
||||
- revision: AnchorExtractor("<REVISION>", "</REVISION>")
|
||||
|
||||
{{
|
||||
focal.callbacks.rewrite_text.render(
|
||||
"Rewrite the draft text to fix it according to the provided analysis. Double quotes \" should be maintained (escape if you have to)",
|
||||
text="The revision of the ENTIRE draft text.",
|
||||
examples=[
|
||||
{ "text": "... rewrite the text here ..." },
|
||||
],
|
||||
|
||||
)
|
||||
}}
|
||||
Override example:
|
||||
{{ set_anchor_extractor("revision", "<MY_TAG>", "</MY_TAG>") }}
|
||||
-#}
|
||||
{# Using common templates approach #}
|
||||
{% set volatile_placement = volatile_context_placement() %}
|
||||
|
||||
{# RENDERED CONTEXT #}
|
||||
{% set rendered_context -%}
|
||||
{% include "character-context.jinja2" -%}
|
||||
<|SECTION:ADDITIONAL INFORMATION|>
|
||||
{% include "extra-context-static.jinja2" -%}
|
||||
<|CLOSE_SECTION|>
|
||||
{% include "scene-intent.jinja2" %}
|
||||
{% endset %}
|
||||
{# END RENDERED CONTEXT #}
|
||||
|
||||
{# VOLATILE CONTEXT #}
|
||||
{% set volatile_context_text %}
|
||||
{% include "memory-context.jinja2" %}
|
||||
{% include "extra-context-dynamic.jinja2" %}
|
||||
{% endset %}
|
||||
{# END VOLATILE CONTEXT #}
|
||||
|
||||
{# SCENE CONTEXT #}
|
||||
{% set scene_context_text %}
|
||||
{% set scene_context = scene.context_history(
|
||||
budget=max_tokens-300-count_tokens(rendered_context)-count_tokens(volatile_context_text),
|
||||
min_dialogue=15,
|
||||
sections=False,
|
||||
keep_director=False,
|
||||
chapter_labels=True
|
||||
)
|
||||
-%}
|
||||
{% for scene_line in scene_context -%}
|
||||
{{ scene_line }}
|
||||
|
||||
{% endfor %}
|
||||
{% endset %}
|
||||
{# END SCENE CONTEXT #}
|
||||
|
||||
{# RENDER PROMPT #}
|
||||
{{ rendered_context }}
|
||||
{% if volatile_placement != "after_history" %}
|
||||
{% with _text=scene_context_text %}{% include "internal-note-help.jinja2" %}{% endwith %}
|
||||
{{ volatile_context_text }}
|
||||
{% endif %}
|
||||
<|SECTION:SCENE|>
|
||||
{{ scene_context_text }}
|
||||
<|CLOSE_SECTION|>
|
||||
{% if volatile_placement == "after_history" %}
|
||||
{% with _text=scene_context_text %}{% include "internal-note-help.jinja2" %}{% endwith %}
|
||||
{{ volatile_context_text }}
|
||||
{% endif %}
|
||||
|
||||
<|SECTION:DRAFT TEXT|>
|
||||
{{ text }}
|
||||
<|CLOSE_SECTION|>
|
||||
<|SECTION:ANALYSIS|>
|
||||
{{ analysis }}
|
||||
{% set issues = 0 %}
|
||||
{# repetition #}{% if repetition %}
|
||||
{% set issues = issues + 1 %}
|
||||
<|SECTION:ISSUE {{ issues }}: REPEATED TEXT|>
|
||||
These sentences have been classified as repetition and must either be substantially rewritten or removed. When removing a sentence, substitute something else, that's meaningful to the story and context.
|
||||
|
||||
{% for repeat in repetition %}- MATCH: `{{ repeat["text_a"].strip() }}`
|
||||
{% endfor %}
|
||||
<|CLOSE_SECTION|>
|
||||
{# end repetition #}{% endif %}
|
||||
{# unwanted prose #}{% if bad_prose %}
|
||||
{% set issues = issues + 1 %}
|
||||
<|SECTION:ISSUE {{ issues }}: UNWANTED PROSE|>
|
||||
These phrases or words have been identified by the director as bad, and MUST be changed accordingly.
|
||||
Important: These are semantic matches based on meaning, not literal text. Focus on the underlying concept or idea that triggered the match, rather than expecting exact phrase matches.
|
||||
|
||||
You must state your greater understanding of why these issues were flagged in the first place so you do not replace them with something equally bad or worse.
|
||||
|
||||
{% for phrase in bad_prose %}- MATCH: `{{ phrase.phrase }}`
|
||||
- INSTRUCTIONS: {{ phrase.instructions }}
|
||||
{% endfor %}
|
||||
<|CLOSE_SECTION|>
|
||||
{# end unwanted prose #}{% endif %}
|
||||
|
||||
{% include "dynamic-instructions.jinja2" %}
|
||||
<|SECTION:TASK|>
|
||||
Call the `rewrite_text` function with the exact new revision of the draft text.
|
||||
<|CLOSE_SECTION|>
|
||||
1. Analyze each issue separately, taking into account the instructions and guidelines for it.
|
||||
2. Specifically compare against previous messages, instead of rewriting the same information often it is better to just drop the repetitive sentence entirely. Its no use to convey the SAME information again just with different words. Repeat back your understanding of this concept.
|
||||
3. Tell us your confident plan for fixing the issues.
|
||||
4. Rewrite the ENTIRE draft text to fix the issues, the best you can, according to the plan you made.
|
||||
- Keep your changes minimal, don't get carried away.
|
||||
- YOU MUST NOT MAKE IT LONGER.
|
||||
- YOU MUST NOT REWRITE PARTS THAT WEREN'T EXPLICITLY FLAGGED.
|
||||
- Make one revision in total - so analyze issues first, then fix them all at once!
|
||||
- Wrap ONLY the final rewritten draft in `<REVISION>` tags, exactly once, like this:
|
||||
<REVISION>
|
||||
... the full rewritten draft text goes here ...
|
||||
</REVISION>
|
||||
- The text inside `<REVISION>` must be the complete replacement for the draft — no commentary, no partial excerpts.
|
||||
|
||||
Repeat back your understanding of the task, then work and report each step 1 - 4, sequentially. End your response with the `<REVISION>` block.
|
||||
|
||||
{% include "response-length.jinja2" %}
|
||||
<|CLOSE_SECTION|>
|
||||
|
||||
63
tests/data/prompts/baselines/editor/revision_rewrite.txt
Normal file
63
tests/data/prompts/baselines/editor/revision_rewrite.txt
Normal file
@@ -0,0 +1,63 @@
|
||||
## Characters
|
||||
|
||||
### Hero
|
||||
name: Hero
|
||||
|
||||
A test character.
|
||||
|
||||
### Elena
|
||||
name: Elena
|
||||
|
||||
A test character.
|
||||
|
||||
## Additional information
|
||||
|
||||
## Classification
|
||||
Content Classification: Fantasy adventure story
|
||||
|
||||
Narrative Perspective: <Mock name='mock.perspective' id='NORMALIZED'>
|
||||
|
||||
## Intention of this story
|
||||
The overarching intention of this story. Use it to guide your decisions in the scene.
|
||||
|
||||
A fantasy adventure story.
|
||||
|
||||
## Intention of the current scene
|
||||
An exploration scene
|
||||
|
||||
The hero explores the forest.
|
||||
|
||||
## Scene
|
||||
|
||||
Elena: Hello there, traveler.
|
||||
|
||||
The sun filters through the leaves above.
|
||||
|
||||
Marcus: What brings you to these woods?
|
||||
|
||||
## Draft text
|
||||
The forest was dark and mysterious.
|
||||
|
||||
## Issue 1: repeated text
|
||||
These sentences have been classified as repetition and must either be substantially rewritten or removed. When removing a sentence, substitute something else, that's meaningful to the story and context.
|
||||
|
||||
- MATCH: `The forest was dark and mysterious.`
|
||||
|
||||
## Task
|
||||
1. Analyze each issue separately, taking into account the instructions and guidelines for it.
|
||||
2. Specifically compare against previous messages, instead of rewriting the same information often it is better to just drop the repetitive sentence entirely. Its no use to convey the SAME information again just with different words. Repeat back your understanding of this concept.
|
||||
3. Tell us your confident plan for fixing the issues.
|
||||
4. Rewrite the ENTIRE draft text to fix the issues, the best you can, according to the plan you made.
|
||||
- Keep your changes minimal, don't get carried away.
|
||||
- YOU MUST NOT MAKE IT LONGER.
|
||||
- YOU MUST NOT REWRITE PARTS THAT WEREN'T EXPLICITLY FLAGGED.
|
||||
- Make one revision in total - so analyze issues first, then fix them all at once!
|
||||
- Wrap ONLY the final rewritten draft in `<REVISION>` tags, exactly once, like this:
|
||||
<REVISION>
|
||||
... the full rewritten draft text goes here ...
|
||||
</REVISION>
|
||||
- The text inside `<REVISION>` must be the complete replacement for the draft — no commentary, no partial excerpts.
|
||||
|
||||
Repeat back your understanding of the task, then work and report each step 1 - 4, sequentially. End your response with the `<REVISION>` block.
|
||||
|
||||
The length of your response must fit within 6 paragraphs.
|
||||
@@ -0,0 +1,63 @@
|
||||
## Characters
|
||||
|
||||
### Hero
|
||||
name: Hero
|
||||
|
||||
A test character.
|
||||
|
||||
### Elena
|
||||
name: Elena
|
||||
|
||||
A test character.
|
||||
|
||||
## Additional information
|
||||
|
||||
## Classification
|
||||
Content Classification: Fantasy adventure story
|
||||
|
||||
Narrative Perspective: <Mock name='mock.perspective' id='NORMALIZED'>
|
||||
|
||||
## Intention of this story
|
||||
The overarching intention of this story. Use it to guide your decisions in the scene.
|
||||
|
||||
A fantasy adventure story.
|
||||
|
||||
## Intention of the current scene
|
||||
An exploration scene
|
||||
|
||||
The hero explores the forest.
|
||||
|
||||
## Scene
|
||||
|
||||
Elena: Hello there, traveler.
|
||||
|
||||
The sun filters through the leaves above.
|
||||
|
||||
Marcus: What brings you to these woods?
|
||||
|
||||
## Draft text
|
||||
The forest was dark and mysterious.
|
||||
|
||||
## Issue 1: repeated text
|
||||
These sentences have been classified as repetition and must either be substantially rewritten or removed. When removing a sentence, substitute something else, that's meaningful to the story and context.
|
||||
|
||||
- MATCH: `The forest was dark and mysterious.`
|
||||
|
||||
## Task
|
||||
1. Analyze each issue separately, taking into account the instructions and guidelines for it.
|
||||
2. Specifically compare against previous messages, instead of rewriting the same information often it is better to just drop the repetitive sentence entirely. Its no use to convey the SAME information again just with different words. Repeat back your understanding of this concept.
|
||||
3. Tell us your confident plan for fixing the issues.
|
||||
4. Rewrite the ENTIRE draft text to fix the issues, the best you can, according to the plan you made.
|
||||
- Keep your changes minimal, don't get carried away.
|
||||
- YOU MUST NOT MAKE IT LONGER.
|
||||
- YOU MUST NOT REWRITE PARTS THAT WEREN'T EXPLICITLY FLAGGED.
|
||||
- Make one revision in total - so analyze issues first, then fix them all at once!
|
||||
- Wrap ONLY the final rewritten draft in `<REVISION>` tags, exactly once, like this:
|
||||
<REVISION>
|
||||
... the full rewritten draft text goes here ...
|
||||
</REVISION>
|
||||
- The text inside `<REVISION>` must be the complete replacement for the draft — no commentary, no partial excerpts.
|
||||
|
||||
Repeat back your understanding of the task, then work and report each step 1 - 4, sequentially. End your response with the `<REVISION>` block.
|
||||
|
||||
The length of your response must fit within 6 paragraphs.
|
||||
@@ -0,0 +1,63 @@
|
||||
## Characters
|
||||
|
||||
### Hero
|
||||
name: Hero
|
||||
|
||||
A test character.
|
||||
|
||||
### Elena
|
||||
name: Elena
|
||||
|
||||
A test character.
|
||||
|
||||
## Additional information
|
||||
|
||||
## Classification
|
||||
Content Classification: Fantasy adventure story
|
||||
|
||||
Narrative Perspective: <Mock name='mock.perspective' id='NORMALIZED'>
|
||||
|
||||
## Intention of this story
|
||||
The overarching intention of this story. Use it to guide your decisions in the scene.
|
||||
|
||||
A fantasy adventure story.
|
||||
|
||||
## Intention of the current scene
|
||||
An exploration scene
|
||||
|
||||
The hero explores the forest.
|
||||
|
||||
## Scene
|
||||
|
||||
Elena: Hello there, traveler.
|
||||
|
||||
The sun filters through the leaves above.
|
||||
|
||||
Marcus: What brings you to these woods?
|
||||
|
||||
## Draft text
|
||||
The forest was dark and mysterious.
|
||||
|
||||
## Issue 1: repeated text
|
||||
These sentences have been classified as repetition and must either be substantially rewritten or removed. When removing a sentence, substitute something else, that's meaningful to the story and context.
|
||||
|
||||
- MATCH: `The forest was dark and mysterious.`
|
||||
|
||||
## Task
|
||||
1. Analyze each issue separately, taking into account the instructions and guidelines for it.
|
||||
2. Specifically compare against previous messages, instead of rewriting the same information often it is better to just drop the repetitive sentence entirely. Its no use to convey the SAME information again just with different words. Repeat back your understanding of this concept.
|
||||
3. Tell us your confident plan for fixing the issues.
|
||||
4. Rewrite the ENTIRE draft text to fix the issues, the best you can, according to the plan you made.
|
||||
- Keep your changes minimal, don't get carried away.
|
||||
- YOU MUST NOT MAKE IT LONGER.
|
||||
- YOU MUST NOT REWRITE PARTS THAT WEREN'T EXPLICITLY FLAGGED.
|
||||
- Make one revision in total - so analyze issues first, then fix them all at once!
|
||||
- Wrap ONLY the final rewritten draft in `<REVISION>` tags, exactly once, like this:
|
||||
<REVISION>
|
||||
... the full rewritten draft text goes here ...
|
||||
</REVISION>
|
||||
- The text inside `<REVISION>` must be the complete replacement for the draft — no commentary, no partial excerpts.
|
||||
|
||||
Repeat back your understanding of the task, then work and report each step 1 - 4, sequentially. End your response with the `<REVISION>` block.
|
||||
|
||||
The length of your response must fit within 6 paragraphs.
|
||||
@@ -0,0 +1,63 @@
|
||||
## Characters
|
||||
|
||||
### Hero
|
||||
name: Hero
|
||||
|
||||
A test character.
|
||||
|
||||
### Elena
|
||||
name: Elena
|
||||
|
||||
A test character.
|
||||
|
||||
## Additional information
|
||||
|
||||
## Classification
|
||||
Content Classification: Fantasy adventure story
|
||||
|
||||
Narrative Perspective: <Mock name='mock.perspective' id='NORMALIZED'>
|
||||
|
||||
## Intention of this story
|
||||
The overarching intention of this story. Use it to guide your decisions in the scene.
|
||||
|
||||
A fantasy adventure story.
|
||||
|
||||
## Intention of the current scene
|
||||
An exploration scene
|
||||
|
||||
The hero explores the forest.
|
||||
|
||||
## Scene
|
||||
|
||||
Elena: Hello there, traveler.
|
||||
|
||||
The sun filters through the leaves above.
|
||||
|
||||
Marcus: What brings you to these woods?
|
||||
|
||||
## Draft text
|
||||
The forest was dark and mysterious.
|
||||
|
||||
## Issue 1: repeated text
|
||||
These sentences have been classified as repetition and must either be substantially rewritten or removed. When removing a sentence, substitute something else, that's meaningful to the story and context.
|
||||
|
||||
- MATCH: `The forest was dark and mysterious.`
|
||||
|
||||
## Task
|
||||
1. Analyze each issue separately, taking into account the instructions and guidelines for it.
|
||||
2. Specifically compare against previous messages, instead of rewriting the same information often it is better to just drop the repetitive sentence entirely. Its no use to convey the SAME information again just with different words. Repeat back your understanding of this concept.
|
||||
3. Tell us your confident plan for fixing the issues.
|
||||
4. Rewrite the ENTIRE draft text to fix the issues, the best you can, according to the plan you made.
|
||||
- Keep your changes minimal, don't get carried away.
|
||||
- YOU MUST NOT MAKE IT LONGER.
|
||||
- YOU MUST NOT REWRITE PARTS THAT WEREN'T EXPLICITLY FLAGGED.
|
||||
- Make one revision in total - so analyze issues first, then fix them all at once!
|
||||
- Wrap ONLY the final rewritten draft in `<REVISION>` tags, exactly once, like this:
|
||||
<REVISION>
|
||||
... the full rewritten draft text goes here ...
|
||||
</REVISION>
|
||||
- The text inside `<REVISION>` must be the complete replacement for the draft — no commentary, no partial excerpts.
|
||||
|
||||
Repeat back your understanding of the task, then work and report each step 1 - 4, sequentially. End your response with the `<REVISION>` block.
|
||||
|
||||
The length of your response must fit within 6 paragraphs.
|
||||
@@ -0,0 +1,63 @@
|
||||
## Characters
|
||||
|
||||
### Hero
|
||||
name: Hero
|
||||
|
||||
A test character.
|
||||
|
||||
### Elena
|
||||
name: Elena
|
||||
|
||||
A test character.
|
||||
|
||||
## Additional information
|
||||
|
||||
## Classification
|
||||
Content Classification: Fantasy adventure story
|
||||
|
||||
Narrative Perspective: <Mock name='mock.perspective' id='NORMALIZED'>
|
||||
|
||||
## Intention of this story
|
||||
The overarching intention of this story. Use it to guide your decisions in the scene.
|
||||
|
||||
A fantasy adventure story.
|
||||
|
||||
## Intention of the current scene
|
||||
An exploration scene
|
||||
|
||||
The hero explores the forest.
|
||||
|
||||
## Scene
|
||||
|
||||
Elena: Hello there, traveler.
|
||||
|
||||
The sun filters through the leaves above.
|
||||
|
||||
Marcus: What brings you to these woods?
|
||||
|
||||
## Draft text
|
||||
The forest was dark and mysterious.
|
||||
|
||||
## Issue 1: repeated text
|
||||
These sentences have been classified as repetition and must either be substantially rewritten or removed. When removing a sentence, substitute something else, that's meaningful to the story and context.
|
||||
|
||||
- MATCH: `The forest was dark and mysterious.`
|
||||
|
||||
## Task
|
||||
1. Analyze each issue separately, taking into account the instructions and guidelines for it.
|
||||
2. Specifically compare against previous messages, instead of rewriting the same information often it is better to just drop the repetitive sentence entirely. Its no use to convey the SAME information again just with different words. Repeat back your understanding of this concept.
|
||||
3. Tell us your confident plan for fixing the issues.
|
||||
4. Rewrite the ENTIRE draft text to fix the issues, the best you can, according to the plan you made.
|
||||
- Keep your changes minimal, don't get carried away.
|
||||
- YOU MUST NOT MAKE IT LONGER.
|
||||
- YOU MUST NOT REWRITE PARTS THAT WEREN'T EXPLICITLY FLAGGED.
|
||||
- Make one revision in total - so analyze issues first, then fix them all at once!
|
||||
- Wrap ONLY the final rewritten draft in `<REVISION>` tags, exactly once, like this:
|
||||
<REVISION>
|
||||
... the full rewritten draft text goes here ...
|
||||
</REVISION>
|
||||
- The text inside `<REVISION>` must be the complete replacement for the draft — no commentary, no partial excerpts.
|
||||
|
||||
Repeat back your understanding of the task, then work and report each step 1 - 4, sequentially. End your response with the `<REVISION>` block.
|
||||
|
||||
The length of your final answer must fit within 6 paragraphs.
|
||||
67
tests/data/prompts/baselines_xml/editor/revision_rewrite.txt
Normal file
67
tests/data/prompts/baselines_xml/editor/revision_rewrite.txt
Normal file
@@ -0,0 +1,67 @@
|
||||
<CHARACTERS>
|
||||
### Hero
|
||||
name: Hero
|
||||
|
||||
A test character.
|
||||
|
||||
### Elena
|
||||
name: Elena
|
||||
|
||||
A test character.
|
||||
</CHARACTERS>
|
||||
|
||||
<CLASSIFICATION>
|
||||
Content Classification: Fantasy adventure story
|
||||
|
||||
Narrative Perspective: <Mock name='mock.perspective' id='NORMALIZED'>
|
||||
</CLASSIFICATION>
|
||||
|
||||
<INTENTION_OF_THIS_STORY>
|
||||
The overarching intention of this story. Use it to guide your decisions in the scene.
|
||||
|
||||
A fantasy adventure story.
|
||||
</INTENTION_OF_THIS_STORY>
|
||||
|
||||
<INTENTION_OF_THE_CURRENT_SCENE>
|
||||
An exploration scene
|
||||
|
||||
The hero explores the forest.
|
||||
</INTENTION_OF_THE_CURRENT_SCENE>
|
||||
|
||||
<SCENE>
|
||||
Elena: Hello there, traveler.
|
||||
|
||||
The sun filters through the leaves above.
|
||||
|
||||
Marcus: What brings you to these woods?
|
||||
</SCENE>
|
||||
|
||||
<DRAFT_TEXT>
|
||||
The forest was dark and mysterious.
|
||||
</DRAFT_TEXT>
|
||||
|
||||
<ISSUE_1:_REPEATED_TEXT>
|
||||
These sentences have been classified as repetition and must either be substantially rewritten or removed. When removing a sentence, substitute something else, that's meaningful to the story and context.
|
||||
|
||||
- MATCH: `The forest was dark and mysterious.`
|
||||
</ISSUE_1:_REPEATED_TEXT>
|
||||
|
||||
<TASK>
|
||||
1. Analyze each issue separately, taking into account the instructions and guidelines for it.
|
||||
2. Specifically compare against previous messages, instead of rewriting the same information often it is better to just drop the repetitive sentence entirely. Its no use to convey the SAME information again just with different words. Repeat back your understanding of this concept.
|
||||
3. Tell us your confident plan for fixing the issues.
|
||||
4. Rewrite the ENTIRE draft text to fix the issues, the best you can, according to the plan you made.
|
||||
- Keep your changes minimal, don't get carried away.
|
||||
- YOU MUST NOT MAKE IT LONGER.
|
||||
- YOU MUST NOT REWRITE PARTS THAT WEREN'T EXPLICITLY FLAGGED.
|
||||
- Make one revision in total - so analyze issues first, then fix them all at once!
|
||||
- Wrap ONLY the final rewritten draft in `<REVISION>` tags, exactly once, like this:
|
||||
<REVISION>
|
||||
... the full rewritten draft text goes here ...
|
||||
</REVISION>
|
||||
- The text inside `<REVISION>` must be the complete replacement for the draft — no commentary, no partial excerpts.
|
||||
|
||||
Repeat back your understanding of the task, then work and report each step 1 - 4, sequentially. End your response with the `<REVISION>` block.
|
||||
|
||||
The length of your response must fit within 6 paragraphs.
|
||||
</TASK>
|
||||
@@ -6,7 +6,7 @@ against stored baseline files. Run with --update-baselines to create/update.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from unittest.mock import AsyncMock
|
||||
from unittest.mock import AsyncMock, Mock
|
||||
|
||||
from ..conftest import mock_llm_client # noqa: F401
|
||||
from ..test_editor_templates import ( # noqa: F401
|
||||
@@ -35,6 +35,48 @@ class TestEditorBaselines:
|
||||
await editor.add_detail(content="Elena: Hello there.", character=character)
|
||||
baseline_checker(capture_prompt(editor), AGENT, "add_detail")
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_revision_rewrite(
|
||||
self,
|
||||
active_context,
|
||||
mock_scene,
|
||||
mock_memory_agent,
|
||||
baseline_checker,
|
||||
):
|
||||
editor = active_context
|
||||
editor.actions["revision"].enabled = True
|
||||
editor.actions["revision"].config["revision_method"].value = "rewrite"
|
||||
editor.actions["revision"].config["min_issues"].value = 1
|
||||
|
||||
# Provide history message that the draft text repeats
|
||||
history_messages = [
|
||||
Mock(message="The forest was dark and mysterious.", typ="narrator"),
|
||||
]
|
||||
mock_scene.collect_messages = Mock(return_value=history_messages)
|
||||
|
||||
# Force a repetition match so the rewrite path actually runs
|
||||
mock_memory_agent.compare_string_lists = AsyncMock(
|
||||
return_value={
|
||||
"similarity_matches": [[0, 0, 0.9]],
|
||||
"cosine_similarity_matrix": [],
|
||||
}
|
||||
)
|
||||
|
||||
# Single-prompt flow: return a well-formed <REVISION> response
|
||||
editor.client.send_prompt = AsyncMock(
|
||||
return_value="<REVISION>The forest was silent.</REVISION>"
|
||||
)
|
||||
|
||||
from talemate.agents.editor.revision import RevisionInformation
|
||||
|
||||
info = RevisionInformation(
|
||||
text="The forest was dark and mysterious.",
|
||||
character=None,
|
||||
)
|
||||
await editor.revision_rewrite(info)
|
||||
|
||||
baseline_checker(capture_prompt(editor), AGENT, "revision_rewrite")
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_revision_unslop(
|
||||
self,
|
||||
|
||||
@@ -7,7 +7,7 @@ to prompt rendering to LLM call, without making actual API calls.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from unittest.mock import Mock, AsyncMock, patch
|
||||
from unittest.mock import Mock, AsyncMock
|
||||
|
||||
import talemate.instance as instance
|
||||
from talemate.agents.editor import EditorAgent
|
||||
@@ -245,26 +245,18 @@ class TestEditorAddDetailMethod:
|
||||
class TestEditorRevisionRewriteMethod:
|
||||
"""Tests for the revision_rewrite method that calls revision templates."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_revision_rewrite_calls_client_when_issues_found(
|
||||
self, active_context, mock_scene, mock_memory_agent
|
||||
):
|
||||
"""Test that revision_rewrite calls the LLM client when issues are found."""
|
||||
editor = active_context
|
||||
|
||||
# Enable revision
|
||||
@staticmethod
|
||||
def _enable_rewrite_with_repetition(editor, mock_scene, mock_memory_agent):
|
||||
"""Enable rewrite + force one repetition match so the rewrite path runs."""
|
||||
editor.actions["revision"].enabled = True
|
||||
editor.actions["revision"].config["revision_method"].value = "rewrite"
|
||||
editor.actions["revision"].config["min_issues"].value = 1
|
||||
|
||||
# Provide history messages for comparison
|
||||
history_messages = [
|
||||
Mock(message="The forest was dark and mysterious.", typ="narrator"),
|
||||
]
|
||||
mock_scene.collect_messages = Mock(return_value=history_messages)
|
||||
|
||||
# Mock memory agent to return similarity matches (repetition detected)
|
||||
# [text_index, history_index, similarity]
|
||||
mock_memory_agent.compare_string_lists = AsyncMock(
|
||||
return_value={
|
||||
"similarity_matches": [[0, 0, 0.9]],
|
||||
@@ -272,32 +264,105 @@ class TestEditorRevisionRewriteMethod:
|
||||
}
|
||||
)
|
||||
|
||||
# Mock focal handler response
|
||||
with patch("talemate.game.focal.Focal") as mock_focal_class:
|
||||
mock_focal = AsyncMock()
|
||||
mock_focal.state = Mock()
|
||||
mock_focal.state.calls = [Mock(result="Revised text here.")]
|
||||
mock_focal.request = AsyncMock()
|
||||
mock_focal_class.return_value = mock_focal
|
||||
@pytest.mark.asyncio
|
||||
async def test_revision_rewrite_extracts_revision_tag(
|
||||
self, active_context, mock_scene, mock_memory_agent
|
||||
):
|
||||
"""Rewrite should run a single prompt and return the <REVISION> content."""
|
||||
editor = active_context
|
||||
self._enable_rewrite_with_repetition(editor, mock_scene, mock_memory_agent)
|
||||
|
||||
from talemate.agents.editor.revision import RevisionInformation
|
||||
|
||||
info = RevisionInformation(
|
||||
text="The forest was dark. The forest was quiet.",
|
||||
character=None,
|
||||
expected_revision = "The woods were silent under heavy cloud."
|
||||
editor.client.send_prompt = AsyncMock(
|
||||
return_value=(
|
||||
"Analysis: the draft repeats a prior line.\n"
|
||||
f"<REVISION>{expected_revision}</REVISION>"
|
||||
)
|
||||
)
|
||||
|
||||
await editor.revision_rewrite(info)
|
||||
from talemate.agents.editor.revision import RevisionInformation
|
||||
|
||||
# Verify the client's send_prompt was called for analysis
|
||||
editor.client.send_prompt.assert_called()
|
||||
info = RevisionInformation(
|
||||
text="The forest was dark and mysterious.",
|
||||
character=None,
|
||||
)
|
||||
|
||||
# Get the prompt that was sent
|
||||
call_args = editor.client.send_prompt.call_args
|
||||
prompt_text = str(call_args[0][0])
|
||||
response = await editor.revision_rewrite(info)
|
||||
|
||||
# Verify the prompt contains the text
|
||||
assert "forest" in prompt_text.lower()
|
||||
# Only the analysis prompt should fire — no secondary rewrite prompt
|
||||
assert editor.client.send_prompt.call_count == 1
|
||||
assert response == expected_revision
|
||||
assert "<REVISION>" not in response
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_revision_rewrite_returns_original_when_no_revision_tag(
|
||||
self, active_context, mock_scene, mock_memory_agent
|
||||
):
|
||||
"""If the analysis response has no <REVISION> tag, fall back to original."""
|
||||
editor = active_context
|
||||
self._enable_rewrite_with_repetition(editor, mock_scene, mock_memory_agent)
|
||||
|
||||
editor.client.send_prompt = AsyncMock(
|
||||
return_value="Analysis only, no revision produced."
|
||||
)
|
||||
|
||||
from talemate.agents.editor.revision import RevisionInformation
|
||||
|
||||
original_text = "The forest was dark and mysterious."
|
||||
info = RevisionInformation(text=original_text, character=None)
|
||||
|
||||
response = await editor.revision_rewrite(info)
|
||||
|
||||
assert response == original_text
|
||||
assert editor.client.send_prompt.call_count == 1
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_revision_rewrite_discards_oversized_revision(
|
||||
self, active_context, mock_scene, mock_memory_agent
|
||||
):
|
||||
"""Rewrites longer than REWRITE_MAX_LENGTH_RATIO should be discarded."""
|
||||
editor = active_context
|
||||
self._enable_rewrite_with_repetition(editor, mock_scene, mock_memory_agent)
|
||||
|
||||
original_text = "The forest was dark and mysterious."
|
||||
# Produce a fix ~3x longer than original — well above the guardrail
|
||||
bloated = " ".join([original_text] * 3)
|
||||
editor.client.send_prompt = AsyncMock(
|
||||
return_value=f"<REVISION>{bloated}</REVISION>"
|
||||
)
|
||||
|
||||
from talemate.agents.editor.revision import RevisionInformation
|
||||
|
||||
info = RevisionInformation(text=original_text, character=None)
|
||||
|
||||
response = await editor.revision_rewrite(info)
|
||||
|
||||
assert response == original_text
|
||||
assert editor.client.send_prompt.call_count == 1
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_revision_rewrite_reattaches_character_prefix(
|
||||
self, active_context, mock_scene, mock_memory_agent
|
||||
):
|
||||
"""Character dialogue should have the `Name: ` prefix preserved."""
|
||||
editor = active_context
|
||||
character = mock_scene.get_character("Elena")
|
||||
self._enable_rewrite_with_repetition(editor, mock_scene, mock_memory_agent)
|
||||
|
||||
editor.client.send_prompt = AsyncMock(
|
||||
return_value='<REVISION>"A new line entirely."</REVISION>'
|
||||
)
|
||||
|
||||
from talemate.agents.editor.revision import RevisionInformation
|
||||
|
||||
info = RevisionInformation(
|
||||
text='Elena: "The forest was dark and mysterious."',
|
||||
character=character,
|
||||
)
|
||||
|
||||
response = await editor.revision_rewrite(info)
|
||||
|
||||
assert response == 'Elena: "A new line entirely."'
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_revision_rewrite_returns_original_when_no_issues(
|
||||
|
||||
Reference in New Issue
Block a user