Toggle navigation
Toggle navigation
This project
Loading...
Sign in
万朱浩
/
Venue-Ops
Go to a project
Toggle navigation
Projects
Groups
Snippets
Help
Toggle navigation pinning
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Network
Create a new issue
Builds
Commits
Authored by
马一丁
2025-12-04 18:49:36 +0800
Browse Files
Options
Browse Files
Download
Email Patches
Plain Diff
Commit
a3f49ca4fcbeb180e171b5df0007850c472bc9ea
a3f49ca4
1 parent
af8c8815
Optimize the Display of Single-Agent Speech Blocks
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
90 additions
and
16 deletions
ReportEngine/ir/__init__.py
ReportEngine/ir/schema.py
ReportEngine/ir/validator.py
ReportEngine/nodes/chapter_generation_node.py
ReportEngine/prompts/prompts.py
ReportEngine/renderers/html_renderer.py
tests/test_report_engine_sanitization.py
ReportEngine/ir/__init__.py
View file @
a3f49ca
...
...
@@ -11,6 +11,7 @@ from .schema import (
CHAPTER_JSON_SCHEMA_TEXT
,
ALLOWED_BLOCK_TYPES
,
ALLOWED_INLINE_MARKS
,
ENGINE_AGENT_TITLES
,
)
from
.validator
import
IRValidator
...
...
@@ -20,5 +21,6 @@ __all__ = [
"CHAPTER_JSON_SCHEMA_TEXT"
,
"ALLOWED_BLOCK_TYPES"
,
"ALLOWED_INLINE_MARKS"
,
"ENGINE_AGENT_TITLES"
,
"IRValidator"
,
]
...
...
ReportEngine/ir/schema.py
View file @
a3f49ca
...
...
@@ -45,6 +45,12 @@ ALLOWED_BLOCK_TYPES: List[str] = [
"toc"
,
]
ENGINE_AGENT_TITLES
:
Dict
[
str
,
str
]
=
{
"insight"
:
"Insight Agent"
,
"media"
:
"Media Agent"
,
"query"
:
"Query Agent"
,
}
# ====== Schema定义 ======
inline_mark_schema
:
Dict
[
str
,
Any
]
=
{
"type"
:
"object"
,
...
...
@@ -190,7 +196,21 @@ engine_quote_block: Dict[str, Any] = {
"items"
:
{
"$ref"
:
"#/definitions/block"
},
},
},
"required"
:
[
"type"
,
"engine"
,
"blocks"
],
"required"
:
[
"type"
,
"engine"
,
"blocks"
,
"title"
],
"allOf"
:
[
{
"if"
:
{
"properties"
:
{
"engine"
:
{
"const"
:
"insight"
}}},
"then"
:
{
"properties"
:
{
"title"
:
{
"const"
:
ENGINE_AGENT_TITLES
[
"insight"
]}}},
},
{
"if"
:
{
"properties"
:
{
"engine"
:
{
"const"
:
"media"
}}},
"then"
:
{
"properties"
:
{
"title"
:
{
"const"
:
ENGINE_AGENT_TITLES
[
"media"
]}}},
},
{
"if"
:
{
"properties"
:
{
"engine"
:
{
"const"
:
"query"
}}},
"then"
:
{
"properties"
:
{
"title"
:
{
"const"
:
ENGINE_AGENT_TITLES
[
"query"
]}}},
},
],
"additionalProperties"
:
True
,
}
...
...
@@ -384,4 +404,5 @@ __all__ = [
"ALLOWED_BLOCK_TYPES"
,
"CHAPTER_JSON_SCHEMA"
,
"CHAPTER_JSON_SCHEMA_TEXT"
,
"ENGINE_AGENT_TITLES"
,
]
...
...
ReportEngine/ir/validator.py
View file @
a3f49ca
...
...
@@ -10,7 +10,12 @@ from __future__ import annotations
from
typing
import
Any
,
Dict
,
List
,
Tuple
from
.schema
import
ALLOWED_BLOCK_TYPES
,
ALLOWED_INLINE_MARKS
,
IR_VERSION
from
.schema
import
(
ALLOWED_BLOCK_TYPES
,
ALLOWED_INLINE_MARKS
,
ENGINE_AGENT_TITLES
,
IR_VERSION
,
)
class
IRValidator
:
...
...
@@ -142,9 +147,20 @@ class IRValidator:
self
,
block
:
Dict
[
str
,
Any
],
path
:
str
,
errors
:
List
[
str
]
):
"""单引擎发言块需标注engine并包含子blocks"""
engine
=
block
.
get
(
"engine"
)
engine_raw
=
block
.
get
(
"engine"
)
engine
=
engine_raw
.
lower
()
if
isinstance
(
engine_raw
,
str
)
else
None
if
engine
not
in
{
"insight"
,
"media"
,
"query"
}:
errors
.
append
(
f
"{path}.engine 取值非法: {engine}"
)
errors
.
append
(
f
"{path}.engine 取值非法: {engine_raw}"
)
title
=
block
.
get
(
"title"
)
expected_title
=
ENGINE_AGENT_TITLES
.
get
(
engine
)
if
engine
else
None
if
title
is
None
:
errors
.
append
(
f
"{path}.title 缺失"
)
elif
not
isinstance
(
title
,
str
):
errors
.
append
(
f
"{path}.title 必须是字符串"
)
elif
expected_title
and
title
!=
expected_title
:
errors
.
append
(
f
"{path}.title 必须与engine一致,使用对应Agent名称: {expected_title}"
)
inner
=
block
.
get
(
"blocks"
)
if
not
isinstance
(
inner
,
list
)
or
not
inner
:
errors
.
append
(
f
"{path}.blocks 必须是非空数组"
)
...
...
ReportEngine/nodes/chapter_generation_node.py
View file @
a3f49ca
...
...
@@ -16,7 +16,12 @@ from typing import Any, Dict, List, Tuple, Callable, Optional, Set
from
loguru
import
logger
from
..core
import
TemplateSection
,
ChapterStorage
from
..ir
import
ALLOWED_BLOCK_TYPES
,
ALLOWED_INLINE_MARKS
,
IRValidator
from
..ir
import
(
ALLOWED_BLOCK_TYPES
,
ALLOWED_INLINE_MARKS
,
ENGINE_AGENT_TITLES
,
IRValidator
,
)
from
..prompts
import
(
SYSTEM_PROMPT_CHAPTER_JSON
,
SYSTEM_PROMPT_CHAPTER_JSON_REPAIR
,
...
...
@@ -1081,7 +1086,13 @@ class ChapterGenerationNode(BaseNode):
block
["
rows
"]
=
rows
def
_sanitize_engine_quote_block
(
self
,
block
:
Dict
[
str
,
Any
]):
"""
engineQuote
内部仅允许
paragraph
,且仅保留
bold
/
italic
样式"""
"""
engineQuote
仅用于单
Agent
发言,内部仅允许
paragraph
且
title
需锁定
Agent
名称"""
engine_raw
=
block.get
("
engine
")
engine
=
engine_raw.lower
()
if
isinstance
(
engine_raw
,
str
)
else
None
if
engine
not
in
ENGINE_AGENT_TITLES
:
engine
=
"
insight
"
block
["
engine
"]
=
engine
block
["
title
"]
=
ENGINE_AGENT_TITLES
[
engine
]
allowed_marks
=
{"
bold
",
"
italic
"}
raw_blocks
=
block.get
("
blocks
")
candidates
=
raw_blocks
if
isinstance
(
raw_blocks
,
list
)
else
([
raw_blocks
]
if
raw_blocks
else
[])
...
...
ReportEngine/prompts/prompts.py
View file @
a3f49ca
...
...
@@ -306,7 +306,7 @@ SYSTEM_PROMPT_CHAPTER_JSON = f"""
5. 表格需给出rows/cells/align,KPI卡请使用kpiGrid,分割线用hr。
6. 如需引用图表/交互组件,统一用widgetType表示(例如chart.js/line、chart.js/doughnut)。
7. 鼓励结合outline中列出的子标题,生成多层heading与细粒度内容,同时可补充callout、blockquote等。
8.
如需标注某个引擎的原话,请用 block.type="engineQuote",engine 取值 insight/media/query(仅限这三种
),内部 blocks 只允许 paragraph,paragraph.inlines 的 marks 仅可使用 bold/italic(可留空),禁止在 engineQuote 中放表格/图表/引用/公式等。
8.
engineQuote 仅用于呈现单Agent的原话:使用 block.type="engineQuote",engine 取值 insight/media/query,title 必须固定为对应Agent名字(insight->Insight Agent,media->Media Agent,query->Query Agent,不可自定义
),内部 blocks 只允许 paragraph,paragraph.inlines 的 marks 仅可使用 bold/italic(可留空),禁止在 engineQuote 中放表格/图表/引用/公式等。
9. 如果chapterPlan中包含target/min/max或sections细分预算,请尽量贴合,必要时在notes允许的范围内突破,同时在结构上体现详略;
10. 一级标题需使用中文数字(“一、二、三”),二级标题使用阿拉伯数字(“1.1、1.2”),heading.text中直接写好编号,与outline顺序对应;
11. 严禁输出外部图片/AI生图链接,仅可使用Chart.js图表、表格、色块、callout等HTML原生组件;如需视觉辅助请改为文字描述或数据表;
...
...
ReportEngine/renderers/html_renderer.py
View file @
a3f49ca
...
...
@@ -20,6 +20,7 @@ from pathlib import Path
from
typing
import
Any
,
Dict
,
List
from
loguru
import
logger
from
ReportEngine.ir.schema
import
ENGINE_AGENT_TITLES
from
ReportEngine.utils.chart_validator
import
(
ChartValidator
,
ChartRepairer
,
...
...
@@ -1287,15 +1288,10 @@ class HTMLRenderer:
def
_render_engine_quote
(
self
,
block
:
Dict
[
str
,
Any
])
->
str
:
"""渲染单Engine发言块,带独立配色与标题"""
engine_raw
=
(
block
.
get
(
"engine"
)
or
""
)
.
lower
()
engine
=
engine_raw
if
engine_raw
in
{
"insight"
,
"media"
,
"query"
}
else
"insight"
title
=
(
block
.
get
(
"title"
)
or
{
"insight"
:
"Insight Engine 发言"
,
"media"
:
"Media Engine 发言"
,
"query"
:
"Query Engine 发言"
,
}
.
get
(
engine
,
"Engine 发言"
)
)
engine
=
engine_raw
if
engine_raw
in
ENGINE_AGENT_TITLES
else
"insight"
expected_title
=
ENGINE_AGENT_TITLES
.
get
(
engine
,
ENGINE_AGENT_TITLES
[
"insight"
])
title_raw
=
block
.
get
(
"title"
)
if
isinstance
(
block
.
get
(
"title"
),
str
)
else
""
title
=
title_raw
if
title_raw
==
expected_title
else
expected_title
inner
=
self
.
_render_blocks
(
block
.
get
(
"blocks"
,
[]))
return
(
f
'<div class="engine-quote engine-{self._escape_attr(engine)}">'
...
...
tests/test_report_engine_sanitization.py
View file @
a3f49ca
...
...
@@ -63,6 +63,7 @@ class ChapterSanitizationTestCase(unittest.TestCase):
{
"type"
:
"engineQuote"
,
"engine"
:
"insight"
,
"title"
:
"Insight Agent"
,
"blocks"
:
[
{
"type"
:
"paragraph"
,
...
...
@@ -87,6 +88,7 @@ class ChapterSanitizationTestCase(unittest.TestCase):
{
"type"
:
"engineQuote"
,
"engine"
:
"media"
,
"title"
:
"Media Agent"
,
"blocks"
:
[
{
"type"
:
"math"
,
"latex"
:
"x=y"
},
{
...
...
@@ -129,6 +131,7 @@ class ChapterSanitizationTestCase(unittest.TestCase):
node
.
_sanitize_chapter_blocks
(
chapter
)
eq_block
=
chapter
[
"blocks"
][
0
]
self
.
assertEqual
(
eq_block
[
"type"
],
"engineQuote"
)
self
.
assertEqual
(
eq_block
.
get
(
"title"
),
"Query Agent"
)
inner_blocks
=
eq_block
.
get
(
"blocks"
)
self
.
assertTrue
(
all
(
b
.
get
(
"type"
)
==
"paragraph"
for
b
in
inner_blocks
))
marks
=
inner_blocks
[
0
][
"inlines"
][
0
]
.
get
(
"marks"
)
...
...
@@ -136,6 +139,31 @@ class ChapterSanitizationTestCase(unittest.TestCase):
marks2
=
inner_blocks
[
1
][
"inlines"
][
0
]
.
get
(
"marks"
)
self
.
assertEqual
(
marks2
,
[{
"type"
:
"bold"
}])
def
test_engine_quote_title_must_match_engine
(
self
):
validator
=
IRValidator
()
chapter
=
{
"chapterId"
:
"S1"
,
"title"
:
"Engine 引用校验"
,
"anchor"
:
"section-1"
,
"order"
:
1
,
"blocks"
:
[
{
"type"
:
"engineQuote"
,
"engine"
:
"query"
,
"title"
:
"Media Agent"
,
"blocks"
:
[
{
"type"
:
"paragraph"
,
"inlines"
:
[{
"text"
:
"错误标题"
}],
}
],
}
],
}
valid
,
errors
=
validator
.
validate_chapter
(
chapter
)
self
.
assertFalse
(
valid
)
self
.
assertTrue
(
any
(
"title 必须与engine一致"
in
err
for
err
in
errors
))
if
__name__
==
"__main__"
:
unittest
.
main
()
...
...
Please
register
or
login
to post a comment