feat: 在标题生成Agent中增加AI审核交互节点
1. 新增ApplyReviewSuggestionsNode节点 - 展示AI审查建议给用户 - 支持用户选择要应用的建议 - 支持交互/静默/程序化三种模式 2. 新增AdjustChaptersNode节点 - 根据用户选择的AI建议调整章节结构 - 完全基于AI的智能调整,无硬编码逻辑 - 安全的错误处理和回退机制 3. 更新工作流架构 - 新流程: ReviewStructure → ApplyReviewSuggestions → AdjustChapters → FinalizeChapters - 专业的单一职责原则,每个节点功能明确 - 完善的状态传递和错误处理 4. 修复AnalysisAgent集成问题 - 保留TocAgent的完整审查结果 - 正确使用调整后的章节结构 - 修复interaction_handler传递问题 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
bc3ea2e439
commit
52733d68c2
@ -307,10 +307,18 @@ def generate_toc_with_agent_node(state: AnalysisAgentState) -> AnalysisAgentStat
|
||||
|
||||
logger.info(f"目录生成完成: {len(chapters)}个章节")
|
||||
|
||||
# 更新状态
|
||||
# 更新状态 - 使用TocAgent的完整结果
|
||||
state["preliminary_chapters"] = chapters
|
||||
state["technical_criteria"] = technical_criteria
|
||||
state["structure_review"] = {} # TocAgent已包含审查
|
||||
|
||||
# 保留TocAgent的审查结果和其他状态
|
||||
if result.get("structure_review"):
|
||||
state["structure_review"] = result["structure_review"]
|
||||
if result.get("approved_suggestions"):
|
||||
state["approved_suggestions"] = result["approved_suggestions"]
|
||||
if result.get("adjusted_chapters"):
|
||||
state["adjusted_chapters"] = result["adjusted_chapters"]
|
||||
|
||||
state["current_step"] = "generate_toc"
|
||||
state["progress"] = 0.85
|
||||
|
||||
@ -335,12 +343,17 @@ def finalize_structure_node(state: AnalysisAgentState) -> AnalysisAgentState:
|
||||
preliminary_chapters = state["preliminary_chapters"]
|
||||
structure_review = state.get("structure_review", {})
|
||||
|
||||
# 获取最终章节(优先使用调整后的章节)
|
||||
final_chapters = state.get("adjusted_chapters")
|
||||
if not final_chapters:
|
||||
final_chapters = state.get("final_chapters", preliminary_chapters)
|
||||
|
||||
# 创建最终的标书结构
|
||||
bid_structure = BidStructure(
|
||||
project_name=f"标书项目-{Path(state['source_file']).stem}",
|
||||
scoring_criteria=technical_criteria,
|
||||
deviation_items=state["deviation_items"],
|
||||
chapters=preliminary_chapters, # 使用TocAgent生成的章节
|
||||
chapters=final_chapters, # 使用最终的章节(可能是调整后的)
|
||||
scoring_file=state["source_file"]
|
||||
)
|
||||
|
||||
|
||||
@ -14,6 +14,8 @@ from ...nodes.toc import (
|
||||
GenerateFirstLevelNode,
|
||||
GenerateSubChaptersNode,
|
||||
ReviewStructureNode,
|
||||
ApplyReviewSuggestionsNode,
|
||||
AdjustChaptersNode,
|
||||
FinalizeChaptersNode
|
||||
)
|
||||
from ...nodes.toc.base_mixins import WorkflowUtilsMixin
|
||||
@ -45,6 +47,8 @@ class TocAgentBuilder(AgentBuilder):
|
||||
.add_node(GenerateFirstLevelNode()) \
|
||||
.add_node(GenerateSubChaptersNode()) \
|
||||
.add_node(ReviewStructureNode()) \
|
||||
.add_node(ApplyReviewSuggestionsNode()) \
|
||||
.add_node(AdjustChaptersNode()) \
|
||||
.add_node(FinalizeChaptersNode())
|
||||
|
||||
# 设置入口点
|
||||
@ -80,6 +84,18 @@ class TocAgentBuilder(AgentBuilder):
|
||||
self.add_conditional_edge(
|
||||
"review_structure",
|
||||
should_continue,
|
||||
{"continue": "apply_suggestions", "end": "END"}
|
||||
)
|
||||
|
||||
self.add_conditional_edge(
|
||||
"apply_suggestions",
|
||||
should_continue,
|
||||
{"continue": "adjust_chapters", "end": "END"}
|
||||
)
|
||||
|
||||
self.add_conditional_edge(
|
||||
"adjust_chapters",
|
||||
should_continue,
|
||||
{"continue": "finalize_chapters", "end": "END"}
|
||||
)
|
||||
|
||||
@ -134,6 +150,9 @@ class TocAgent(BaseAgent, BaseAgentFactory):
|
||||
if template_file:
|
||||
initial_state["preset_template_file"] = template_file
|
||||
|
||||
# 添加交互处理器到状态中
|
||||
initial_state["interaction_handler"] = self.builder.interaction_handler
|
||||
|
||||
return await self.execute(initial_state)
|
||||
|
||||
def generate_toc_sync(self,
|
||||
|
||||
@ -8,6 +8,8 @@ from .group_criteria import GroupCriteriaNode
|
||||
from .generate_first_level import GenerateFirstLevelNode
|
||||
from .generate_sub_chapters import GenerateSubChaptersNode
|
||||
from .review_structure import ReviewStructureNode
|
||||
from .apply_suggestions import ApplyReviewSuggestionsNode
|
||||
from .adjust_chapters import AdjustChaptersNode
|
||||
from .finalize_chapters import FinalizeChaptersNode
|
||||
|
||||
# 辅助组件
|
||||
@ -27,6 +29,8 @@ __all__ = [
|
||||
"GenerateFirstLevelNode",
|
||||
"GenerateSubChaptersNode",
|
||||
"ReviewStructureNode",
|
||||
"ApplyReviewSuggestionsNode",
|
||||
"AdjustChaptersNode",
|
||||
"FinalizeChaptersNode",
|
||||
|
||||
# 辅助组件
|
||||
|
||||
234
src/bidmaster/nodes/toc/adjust_chapters.py
Normal file
234
src/bidmaster/nodes/toc/adjust_chapters.py
Normal file
@ -0,0 +1,234 @@
|
||||
"""AI调整章节结构的节点
|
||||
|
||||
专门负责根据用户选择的AI建议调整章节结构。
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Dict, List, Any
|
||||
|
||||
from ..base import BaseNode, NodeContext
|
||||
from ...tools.parser import DocumentChapter
|
||||
from .base_mixins import TocNodeBase
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AdjustChaptersNode(BaseNode, TocNodeBase):
|
||||
"""AI调整章节结构的节点"""
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "adjust_chapters"
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
return "AI调整章节结构"
|
||||
|
||||
def execute(self, state: Dict[str, Any], context: NodeContext) -> Dict[str, Any]:
|
||||
"""执行AI章节调整"""
|
||||
return self.safe_execute(self._do_adjust_chapters, state, "AI调整章节结构")
|
||||
|
||||
def _do_adjust_chapters(self, state: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""执行实际的AI章节调整逻辑"""
|
||||
# 验证必需字段
|
||||
required_fields = ["preliminary_chapters"]
|
||||
if not self.validate_required_fields(state, required_fields):
|
||||
raise ValueError("缺少初步章节数据")
|
||||
|
||||
preliminary_chapters = state["preliminary_chapters"]
|
||||
approved_suggestions = state.get("approved_suggestions", [])
|
||||
|
||||
if not approved_suggestions:
|
||||
# 没有用户选择的建议,直接返回原章节结构
|
||||
logger.info("用户未选择任何AI建议,保持原有章节结构")
|
||||
return self._update_state(state, adjusted_chapters=preliminary_chapters)
|
||||
|
||||
# 使用AI根据建议调整章节结构
|
||||
adjusted_chapters = self._ai_adjust_chapters_with_suggestions(
|
||||
preliminary_chapters, approved_suggestions)
|
||||
|
||||
self.log_step_info("adjust_chapters",
|
||||
f"AI根据{len(approved_suggestions)}条建议调整章节,生成{len(adjusted_chapters)}个章节")
|
||||
|
||||
return self._update_state(state, adjusted_chapters=adjusted_chapters)
|
||||
|
||||
def _ai_adjust_chapters_with_suggestions(self,
|
||||
preliminary_chapters: List[DocumentChapter],
|
||||
approved_suggestions: List[Dict[str, Any]]) -> List[DocumentChapter]:
|
||||
"""让AI根据用户选择的建议调整章节结构
|
||||
|
||||
Args:
|
||||
preliminary_chapters: 原始章节结构
|
||||
approved_suggestions: 用户批准的AI建议
|
||||
|
||||
Returns:
|
||||
AI调整后的章节结构
|
||||
"""
|
||||
try:
|
||||
# 构建给AI的提示词
|
||||
adjustment_prompt = self._build_adjustment_prompt(preliminary_chapters, approved_suggestions)
|
||||
|
||||
# 调用LLM进行章节调整
|
||||
from .llm_helper import LLMHelper
|
||||
response = LLMHelper.call_llm_with_retry(adjustment_prompt)
|
||||
|
||||
if not response:
|
||||
logger.error("AI章节调整失败,使用原始章节结构")
|
||||
return preliminary_chapters
|
||||
|
||||
# 解析AI返回的调整结果
|
||||
adjusted_data = LLMHelper.parse_ai_json_response(response)
|
||||
adjusted_chapters = self._parse_adjusted_chapters(adjusted_data)
|
||||
|
||||
if adjusted_chapters:
|
||||
logger.info(f"AI成功调整章节结构,共{len(adjusted_chapters)}个章节")
|
||||
return adjusted_chapters
|
||||
else:
|
||||
logger.warning("AI调整结果解析失败,使用原始章节结构")
|
||||
return preliminary_chapters
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"AI章节调整过程失败: {e}")
|
||||
return preliminary_chapters
|
||||
|
||||
def _build_adjustment_prompt(self,
|
||||
chapters: List[DocumentChapter],
|
||||
suggestions: List[Dict[str, Any]]) -> str:
|
||||
"""构建AI章节调整的提示词
|
||||
|
||||
Args:
|
||||
chapters: 原始章节列表
|
||||
suggestions: 用户选择的建议
|
||||
|
||||
Returns:
|
||||
给AI的提示词
|
||||
"""
|
||||
# 格式化原始章节结构
|
||||
chapters_text = self._format_chapters_for_prompt(chapters)
|
||||
|
||||
# 格式化用户选择的建议
|
||||
suggestions_text = self._format_suggestions_for_prompt(suggestions)
|
||||
|
||||
prompt = f"""
|
||||
请根据用户选择的建议调整以下标书章节结构。
|
||||
|
||||
【当前章节结构】:
|
||||
{chapters_text}
|
||||
|
||||
【用户选择应用的建议】:
|
||||
{suggestions_text}
|
||||
|
||||
【调整要求】:
|
||||
1. 根据用户选择的建议对章节结构进行合理调整
|
||||
2. 保持章节的逻辑性和完整性
|
||||
3. 每个章节都要有明确的标题和层级
|
||||
4. 保持专业的标书格式
|
||||
|
||||
返回JSON格式:
|
||||
{{
|
||||
"adjusted_chapters": [
|
||||
{{
|
||||
"id": "chapter_1_technical_solution",
|
||||
"title": "技术方案",
|
||||
"level": 1,
|
||||
"score": 0,
|
||||
"children": [
|
||||
{{
|
||||
"id": "chapter_2_1_architecture",
|
||||
"title": "系统架构设计",
|
||||
"level": 2,
|
||||
"score": 0,
|
||||
"children": []
|
||||
}}
|
||||
]
|
||||
}}
|
||||
]
|
||||
}}
|
||||
|
||||
只返回JSON:"""
|
||||
|
||||
return prompt
|
||||
|
||||
def _format_chapters_for_prompt(self, chapters: List[DocumentChapter]) -> str:
|
||||
"""格式化章节结构用于提示词"""
|
||||
lines = []
|
||||
for chapter in chapters:
|
||||
lines.append(f"{chapter.title} (level: {chapter.level})")
|
||||
# 递归显示子章节
|
||||
self._format_children_for_prompt(chapter.children, lines, 1)
|
||||
return "\n".join(lines)
|
||||
|
||||
def _format_children_for_prompt(self, children: List[DocumentChapter], lines: List[str], indent: int):
|
||||
"""递归格式化子章节"""
|
||||
for child in children:
|
||||
indent_str = " " * indent
|
||||
lines.append(f"{indent_str}{child.title} (level: {child.level})")
|
||||
if child.children:
|
||||
self._format_children_for_prompt(child.children, lines, indent + 1)
|
||||
|
||||
def _format_suggestions_for_prompt(self, suggestions: List[Dict[str, Any]]) -> str:
|
||||
"""格式化建议列表用于提示词"""
|
||||
lines = []
|
||||
for i, suggestion in enumerate(suggestions, 1):
|
||||
# 动态格式化建议内容,不预设字段
|
||||
suggestion_text = f"{i}. "
|
||||
for key, value in suggestion.items():
|
||||
if value:
|
||||
suggestion_text += f"{key}: {value}, "
|
||||
lines.append(suggestion_text.rstrip(", "))
|
||||
return "\n".join(lines)
|
||||
|
||||
def _parse_adjusted_chapters(self, adjusted_data: Dict[str, Any]) -> List[DocumentChapter]:
|
||||
"""解析AI调整后的章节数据
|
||||
|
||||
Args:
|
||||
adjusted_data: AI返回的调整数据
|
||||
|
||||
Returns:
|
||||
解析后的章节列表
|
||||
"""
|
||||
try:
|
||||
chapters_data = adjusted_data.get("adjusted_chapters", [])
|
||||
chapters = []
|
||||
|
||||
for chapter_data in chapters_data:
|
||||
chapter = self._create_chapter_from_data(chapter_data)
|
||||
if chapter:
|
||||
chapters.append(chapter)
|
||||
|
||||
return chapters
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"解析AI调整章节失败: {e}")
|
||||
return []
|
||||
|
||||
def _create_chapter_from_data(self, chapter_data: Dict[str, Any]) -> DocumentChapter:
|
||||
"""从数据创建章节对象
|
||||
|
||||
Args:
|
||||
chapter_data: 章节数据
|
||||
|
||||
Returns:
|
||||
创建的章节对象
|
||||
"""
|
||||
try:
|
||||
chapter = DocumentChapter(
|
||||
id=chapter_data.get("id", ""),
|
||||
title=chapter_data.get("title", ""),
|
||||
level=chapter_data.get("level", 1),
|
||||
score=chapter_data.get("score", 0),
|
||||
children=[]
|
||||
)
|
||||
|
||||
# 递归处理子章节
|
||||
children_data = chapter_data.get("children", [])
|
||||
for child_data in children_data:
|
||||
child_chapter = self._create_chapter_from_data(child_data)
|
||||
if child_chapter:
|
||||
chapter.children.append(child_chapter)
|
||||
|
||||
return chapter
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"创建章节对象失败: {e}")
|
||||
return None
|
||||
124
src/bidmaster/nodes/toc/apply_suggestions.py
Normal file
124
src/bidmaster/nodes/toc/apply_suggestions.py
Normal file
@ -0,0 +1,124 @@
|
||||
"""应用AI审查建议的节点
|
||||
|
||||
让用户选择是否应用AI审查建议,完全依赖AI的分类结果,不做任何人为预设。
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Dict, List, Any
|
||||
|
||||
from ..base import BaseNode, NodeContext
|
||||
from .base_mixins import TocNodeBase
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ApplyReviewSuggestionsNode(BaseNode, TocNodeBase):
|
||||
"""应用AI审查建议的节点"""
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "apply_suggestions"
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
return "应用AI审查建议"
|
||||
|
||||
def execute(self, state: Dict[str, Any], context: NodeContext) -> Dict[str, Any]:
|
||||
"""执行建议应用交互"""
|
||||
return self.safe_execute(self._do_apply_suggestions, state, "应用AI审查建议")
|
||||
|
||||
def _do_apply_suggestions(self, state: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""执行实际的建议应用逻辑"""
|
||||
logger.info("🔥🔥🔥 ApplyReviewSuggestionsNode 正在执行!!!")
|
||||
# 验证必需字段
|
||||
required_fields = ["preliminary_chapters", "structure_review"]
|
||||
if not self.validate_required_fields(state, required_fields):
|
||||
raise ValueError("缺少章节数据或审查结果")
|
||||
|
||||
structure_review = state["structure_review"]
|
||||
|
||||
# 获取交互处理器
|
||||
interaction_handler = state.get("interaction_handler")
|
||||
|
||||
if not interaction_handler:
|
||||
# 如果没有交互处理器,跳过用户选择,不应用任何建议
|
||||
logger.warning("未找到交互处理器,跳过建议应用")
|
||||
approved_suggestions = []
|
||||
else:
|
||||
# 用户选择要应用的建议
|
||||
approved_suggestions = self._get_user_approval(structure_review, interaction_handler)
|
||||
|
||||
# 将用户选择的建议保存到状态中,供FinalizeChaptersNode使用
|
||||
self.log_step_info("apply_suggestions",
|
||||
f"用户选择应用{len(approved_suggestions)}条AI建议")
|
||||
|
||||
return self._update_state(state, approved_suggestions=approved_suggestions)
|
||||
|
||||
def _get_user_approval(self, structure_review: Dict[str, Any],
|
||||
interaction_handler) -> List[Dict[str, Any]]:
|
||||
"""获取用户对AI建议的批准
|
||||
|
||||
Args:
|
||||
structure_review: AI审查结果(完全使用原始数据)
|
||||
interaction_handler: 交互处理器
|
||||
|
||||
Returns:
|
||||
用户批准的建议列表
|
||||
"""
|
||||
# 直接使用AI返回的原始数据,不做任何预设
|
||||
suggestions = structure_review.get("suggestions", [])
|
||||
overall_assessment = structure_review.get("overall_assessment", "")
|
||||
optimization_score = structure_review.get("optimization_score", "")
|
||||
|
||||
if not suggestions:
|
||||
logger.info("AI未提供优化建议")
|
||||
return []
|
||||
|
||||
try:
|
||||
# 展示AI的完整评估结果
|
||||
assessment_text = f"AI审查评估:\n"
|
||||
if overall_assessment:
|
||||
assessment_text += f"总体评价: {overall_assessment}\n"
|
||||
if optimization_score:
|
||||
assessment_text += f"优化评分: {optimization_score}\n"
|
||||
assessment_text += f"AI识别出{len(suggestions)}条建议"
|
||||
|
||||
# 询问用户是否要查看并选择建议
|
||||
view_suggestions = interaction_handler(
|
||||
interaction_type="confirm",
|
||||
prompt=f"{assessment_text}\n是否查看具体建议并选择应用?",
|
||||
default=True,
|
||||
key="view_ai_suggestions"
|
||||
)
|
||||
|
||||
if not view_suggestions:
|
||||
return []
|
||||
|
||||
# 逐个展示AI建议,让用户选择
|
||||
approved_suggestions = []
|
||||
for i, suggestion in enumerate(suggestions, 1):
|
||||
# 完全使用AI提供的原始建议内容,不做任何格式化预设
|
||||
suggestion_text = f"AI建议{i}:\n"
|
||||
|
||||
# 动态展示AI建议中的所有字段,不预设字段名
|
||||
for key, value in suggestion.items():
|
||||
if value: # 只显示有值的字段
|
||||
suggestion_text += f"{key}: {value}\n"
|
||||
|
||||
# 让用户选择是否应用这条AI建议
|
||||
apply_suggestion = interaction_handler(
|
||||
interaction_type="confirm",
|
||||
prompt=f"{suggestion_text}是否应用此AI建议?",
|
||||
default=False, # 默认不应用,让用户主动选择
|
||||
key=f"apply_ai_suggestion_{i}"
|
||||
)
|
||||
|
||||
if apply_suggestion:
|
||||
approved_suggestions.append(suggestion)
|
||||
|
||||
return approved_suggestions
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取用户建议选择失败: {e}")
|
||||
# 出错时不应用任何建议,保持安全
|
||||
return []
|
||||
@ -153,9 +153,9 @@ class WorkflowUtilsMixin:
|
||||
Returns:
|
||||
"continue" 如果应该继续,"end" 如果应该停止
|
||||
"""
|
||||
if state.get("error") or not state.get("should_continue", True):
|
||||
return "end"
|
||||
return "continue"
|
||||
result = "end" if (state.get("error") or not state.get("should_continue", True)) else "continue"
|
||||
logger.info(f"🔥🔥🔥 should_continue_workflow: {result}, error={state.get('error')}, should_continue={state.get('should_continue', True)}")
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def validate_required_fields(state: Dict[str, Any],
|
||||
|
||||
@ -35,11 +35,11 @@ class FinalizeChaptersNode(BaseNode, TocNodeBase):
|
||||
if not self.validate_required_fields(state, ["preliminary_chapters"]):
|
||||
raise ValueError("缺少初步章节数据")
|
||||
|
||||
preliminary_chapters = state["preliminary_chapters"]
|
||||
structure_review = state.get("structure_review", {})
|
||||
# 获取调整后的章节(如果有)或原始章节
|
||||
adjusted_chapters = state.get("adjusted_chapters", state["preliminary_chapters"])
|
||||
|
||||
# 应用审查建议并最终确定
|
||||
final_chapters = self._finalize_with_review(preliminary_chapters, structure_review)
|
||||
# 应用最终确定逻辑(主要是确保标准章节存在)
|
||||
final_chapters = adjusted_chapters
|
||||
|
||||
# 确保标准章节存在
|
||||
final_chapters = self._ensure_standard_chapters(final_chapters)
|
||||
@ -50,47 +50,6 @@ class FinalizeChaptersNode(BaseNode, TocNodeBase):
|
||||
final_chapters=final_chapters,
|
||||
should_continue=False)
|
||||
|
||||
def _finalize_with_review(self,
|
||||
preliminary_chapters: List[DocumentChapter],
|
||||
structure_review: Dict[str, Any]) -> List[DocumentChapter]:
|
||||
"""根据审查结果最终确定章节
|
||||
|
||||
Args:
|
||||
preliminary_chapters: 初步章节列表
|
||||
structure_review: AI审查结果
|
||||
|
||||
Returns:
|
||||
最终章节列表
|
||||
"""
|
||||
final_chapters = preliminary_chapters.copy()
|
||||
|
||||
# 应用高优先级建议
|
||||
suggestions = structure_review.get("suggestions", [])
|
||||
high_priority_suggestions = [
|
||||
s for s in suggestions
|
||||
if s.get("priority") == "high"
|
||||
]
|
||||
|
||||
for suggestion in high_priority_suggestions:
|
||||
suggestion_type = suggestion.get("type", "")
|
||||
description = suggestion.get("description", "")
|
||||
|
||||
if suggestion_type == "add":
|
||||
logger.info(f"应用高优先级建议-添加: {description}")
|
||||
# 这里可以根据具体建议内容添加章节
|
||||
# 当前版本保持简单,只记录日志
|
||||
|
||||
elif suggestion_type == "modify":
|
||||
logger.info(f"应用高优先级建议-修改: {description}")
|
||||
# 这里可以根据具体建议内容修改章节
|
||||
# 当前版本保持简单,只记录日志
|
||||
|
||||
elif suggestion_type == "reorder":
|
||||
logger.info(f"应用高优先级建议-重排: {description}")
|
||||
# 这里可以根据具体建议内容重新排序
|
||||
# 当前版本保持简单,只记录日志
|
||||
|
||||
return final_chapters
|
||||
|
||||
def _ensure_standard_chapters(self, chapters: List[DocumentChapter]) -> List[DocumentChapter]:
|
||||
"""确保包含标准章节
|
||||
|
||||
Loading…
Reference in New Issue
Block a user