mirror of
https://github.com/agentscope-ai/ReMe.git
synced 2026-11-12 08:45:15 +00:00
fix(frontmatter-read): JSON-encode keys too so every answer entry stays on one line
A multiline YAML key broke the one-key-per-line layout because keys were interpolated raw. Route keys through the same json.dumps renderer as values, update the expected answers to the quoted-key form, correct the renderer docstring and mixed-key test comments (JSON collapses int 1 and string "1" keys alike — the metadata keeps the distinction), and pin the rich-type answer to the quoted "created": "2026-10-12" rendering.
This commit is contained in:
parent
e6f6e027cb
commit
0a55578143
2 changed files with 30 additions and 36 deletions
|
|
@ -2,8 +2,9 @@
|
|||
|
||||
Cheap structured read — frontmatter only, no body. Use ``body:read``
|
||||
for the post-frontmatter content slice, or whole-file ``read`` when
|
||||
you want both at once. The answer is textual — ``- key: value`` per
|
||||
line — because MCP callers only ever see ``answer``; the parsed
|
||||
you want both at once. The answer is textual — one JSON-encoded
|
||||
``- "key": value`` entry per line, so every key and value stays on a
|
||||
single line — because MCP callers only ever see ``answer``; the parsed
|
||||
mapping itself is returned in ``metadata["frontmatter"]`` (and
|
||||
``exists: false`` in metadata when the target doesn't exist).
|
||||
|
||||
|
|
@ -22,17 +23,19 @@ from ...components import R
|
|||
|
||||
|
||||
def _render_value(value) -> str:
|
||||
"""Render one frontmatter value on a single answer line.
|
||||
"""Render one frontmatter key or value on a single answer line.
|
||||
|
||||
``frontmatter_read`` is served straight to the LLM, so the answer
|
||||
has to carry the frontmatter itself. ``json.dumps`` (with
|
||||
``default=str`` for YAML-only types such as dates, and
|
||||
``ensure_ascii=False`` for non-ASCII) preserves the structural
|
||||
distinction between a literal ``"\\n"`` two-character sequence and
|
||||
an actual newline, between the string ``"null"`` and YAML ``null``,
|
||||
and between mixed mapping key types (an int ``1`` and a string
|
||||
``"1"`` round-trip with their JSON-form distinction). The
|
||||
one-key-per-line layout is enforced upstream by the caller.
|
||||
has to carry the frontmatter itself, one ``- "key": value`` entry
|
||||
per line. ``json.dumps`` (with ``default=str`` for YAML-only types
|
||||
such as dates, and ``ensure_ascii=False`` for non-ASCII) keeps each
|
||||
entry on one line and preserves the distinction between a literal
|
||||
``"\\n"`` two-character sequence and an actual newline, and between
|
||||
the string ``"null"`` and YAML ``null``. Presentation limitation:
|
||||
JSON stringifies mapping keys, so an int ``1`` and a string ``"1"``
|
||||
both render as ``"1"`` (only the structured metadata keeps them
|
||||
apart, and re-parsing the fragment keeps the last value). The
|
||||
fallback covers values JSON refuses, recursive aliases included.
|
||||
"""
|
||||
try:
|
||||
return json.dumps(value, ensure_ascii=False, default=str)
|
||||
|
|
@ -40,16 +43,6 @@ def _render_value(value) -> str:
|
|||
return repr(value)
|
||||
|
||||
|
||||
def _render_key(key) -> str:
|
||||
"""Render a frontmatter key (the caller formats ``key: value`` lines).
|
||||
|
||||
Keys are always strings in YAML frontmatter, so they do not need
|
||||
the json.dumps structural pinning. Return them as-is so the answer
|
||||
reads as ``- key: value`` rather than ``- "key": value``.
|
||||
"""
|
||||
return str(key)
|
||||
|
||||
|
||||
@R.register("frontmatter_read_step")
|
||||
class FrontmatterReadStep(BaseStep):
|
||||
"""Read a Markdown file's frontmatter (YAML metadata only)."""
|
||||
|
|
@ -96,7 +89,7 @@ class FrontmatterReadStep(BaseStep):
|
|||
return
|
||||
self.context.response.success = True
|
||||
header = f"Read frontmatter from {probed} ({len(meta)} key(s))"
|
||||
rendered = [f"- {_render_key(key)}: {_render_value(value)}" for key, value in meta.items()]
|
||||
rendered = [f"- {_render_value(key)}: {_render_value(value)}" for key, value in meta.items()]
|
||||
self.context.response.answer = f"{header}:\n" + "\n".join(rendered) if rendered else f"{header}: (no keys)"
|
||||
self.context.response.metadata.update({"path": path, "exists": True, "frontmatter": meta, **resolved})
|
||||
self.logger.info(f"[{self.name}] path={path} keys={len(meta)}")
|
||||
|
|
|
|||
|
|
@ -208,7 +208,7 @@ async def test_read_answer_carries_the_frontmatter_itself():
|
|||
store = await _make_store()
|
||||
resp = await _run(FrontmatterReadStep, store, path="notes/post")
|
||||
assert resp.success is True
|
||||
assert resp.answer == 'Read frontmatter from notes/post.md (2 key(s)):\n- name: "n"\n- tags: ["a"]'
|
||||
assert resp.answer == 'Read frontmatter from notes/post.md (2 key(s)):\n- "name": "n"\n- "tags": ["a"]'
|
||||
assert resp.metadata["frontmatter"] == {"name": "n", "tags": ["a"]}
|
||||
await store.close()
|
||||
|
||||
|
|
@ -229,10 +229,10 @@ async def test_read_answer_preserves_value_structure_and_reports_empty():
|
|||
assert resp.success is True
|
||||
assert resp.answer == (
|
||||
"Read frontmatter from notes/rich.md (4 key(s)):\n"
|
||||
'- name: "Cobalt"\n'
|
||||
'- description: "line one line two"\n'
|
||||
'- tags: ["a", "b"]\n'
|
||||
'- nested: {"x": 1}'
|
||||
'- "name": "Cobalt"\n'
|
||||
'- "description": "line one line two"\n'
|
||||
'- "tags": ["a", "b"]\n'
|
||||
'- "nested": {"x": 1}'
|
||||
)
|
||||
assert resp.metadata["frontmatter"] == {
|
||||
"name": "Cobalt",
|
||||
|
|
@ -279,15 +279,16 @@ async def test_read_answer_tolerates_rich_yaml_types():
|
|||
store = await _make_store()
|
||||
resp = await _run(FrontmatterReadStep, store, path="notes/types.md")
|
||||
assert resp.success is True
|
||||
assert resp.answer == (
|
||||
"Read frontmatter from notes/types.md (6 key(s)):\n"
|
||||
'- created: 2026-10-12\n'
|
||||
'- schedule: {"1": "one", "label": "two"}\n'
|
||||
"- tags: []\n"
|
||||
'- aliases: ["a, b", "c"]\n'
|
||||
'- command: "echo a b"\n'
|
||||
'- description: "line one\\nline two"'
|
||||
)
|
||||
expected_lines = [
|
||||
"Read frontmatter from notes/types.md (6 key(s)):",
|
||||
'- "created": "2026-10-12"',
|
||||
'- "schedule": {"1": "one", "label": "two"}',
|
||||
'- "tags": []',
|
||||
'- "aliases": ["a, b", "c"]',
|
||||
'- "command": "echo a b"',
|
||||
'- "description": "line one\\nline two"',
|
||||
]
|
||||
assert resp.answer == "\n".join(expected_lines)
|
||||
assert resp.metadata["frontmatter"] == {
|
||||
"created": date(2026, 10, 12),
|
||||
"schedule": {1: "one", "label": "two"},
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue