增加了前后文一致性,提取技术解决方案,如果没有就自己编一个
This commit is contained in:
parent
3d12f9d065
commit
58d628347e
@ -115,12 +115,15 @@ toc_prompts:
|
|||||||
{criteria_info}
|
{criteria_info}
|
||||||
【招标上下文摘录】:
|
【招标上下文摘录】:
|
||||||
{context_snippets}
|
{context_snippets}
|
||||||
|
【全局技术一致性提示】:
|
||||||
|
{tech_consistency_prompt}
|
||||||
|
|
||||||
生成要求:
|
生成要求:
|
||||||
1. 为每个评分项生成对应的子标题名称(不要包含编号)
|
1. 为每个评分项生成对应的子标题名称(不要包含编号)
|
||||||
2. 重要评分项可添加三级子标题(不要包含编号)
|
2. 重要评分项可添加三级子标题(不要包含编号)
|
||||||
3. 充分参考上下文摘录中的专业术语和业务背景,体现定制化判断
|
3. 充分参考上下文摘录中的专业术语和业务背景,体现定制化判断
|
||||||
4. 只返回标题文本,编号由Word自动管理
|
4. 必须遵循“全局技术一致性提示”中的架构/技术选型/术语口径,确保全篇一致
|
||||||
|
5. 只返回标题文本,编号由Word自动管理
|
||||||
|
|
||||||
返回JSON格式:
|
返回JSON格式:
|
||||||
{{
|
{{
|
||||||
|
|||||||
@ -9,6 +9,7 @@ from typing import Dict, Any, Optional
|
|||||||
|
|
||||||
from ..base import AgentBuilder, BaseAgent, BaseAgentFactory
|
from ..base import AgentBuilder, BaseAgent, BaseAgentFactory
|
||||||
from ...nodes.toc import (
|
from ...nodes.toc import (
|
||||||
|
PrepareTechConsistencyPromptNode,
|
||||||
GroupCriteriaNode,
|
GroupCriteriaNode,
|
||||||
GenerateFirstLevelNode,
|
GenerateFirstLevelNode,
|
||||||
GenerateSubChaptersNode,
|
GenerateSubChaptersNode,
|
||||||
@ -44,7 +45,8 @@ class TocAgentBuilder(AgentBuilder):
|
|||||||
builder = cls(interaction_handler)
|
builder = cls(interaction_handler)
|
||||||
|
|
||||||
# 添加所有节点
|
# 添加所有节点
|
||||||
builder.add_node(GroupCriteriaNode()) \
|
builder.add_node(PrepareTechConsistencyPromptNode()) \
|
||||||
|
.add_node(GroupCriteriaNode()) \
|
||||||
.add_node(GenerateFirstLevelNode()) \
|
.add_node(GenerateFirstLevelNode()) \
|
||||||
.add_node(GenerateSubChaptersNode()) \
|
.add_node(GenerateSubChaptersNode()) \
|
||||||
.add_node(ReviewStructureNode()) \
|
.add_node(ReviewStructureNode()) \
|
||||||
@ -55,7 +57,7 @@ class TocAgentBuilder(AgentBuilder):
|
|||||||
.add_node(OptimizeWithFeedbackNode())
|
.add_node(OptimizeWithFeedbackNode())
|
||||||
|
|
||||||
# 设置入口点
|
# 设置入口点
|
||||||
builder.set_entry("group_criteria")
|
builder.set_entry("prepare_tech_consistency_prompt")
|
||||||
|
|
||||||
# 配置工作流程
|
# 配置工作流程
|
||||||
builder._configure_workflow()
|
builder._configure_workflow()
|
||||||
@ -65,6 +67,12 @@ class TocAgentBuilder(AgentBuilder):
|
|||||||
|
|
||||||
def _configure_workflow(self) -> None:
|
def _configure_workflow(self) -> None:
|
||||||
"""配置工作流程"""
|
"""配置工作流程"""
|
||||||
|
self.add_conditional_edge(
|
||||||
|
"prepare_tech_consistency_prompt",
|
||||||
|
should_continue,
|
||||||
|
{"continue": "group_criteria", "end": "END"}
|
||||||
|
)
|
||||||
|
|
||||||
# 添加条件边 - 每个节点都检查是否应该继续
|
# 添加条件边 - 每个节点都检查是否应该继续
|
||||||
self.add_conditional_edge(
|
self.add_conditional_edge(
|
||||||
"group_criteria",
|
"group_criteria",
|
||||||
|
|||||||
@ -13,6 +13,7 @@ from .adjust_chapters import AdjustChaptersNode
|
|||||||
from .finalize_chapters import FinalizeChaptersNode
|
from .finalize_chapters import FinalizeChaptersNode
|
||||||
from .user_feedback import UserFeedbackNode
|
from .user_feedback import UserFeedbackNode
|
||||||
from .optimize_with_feedback import OptimizeWithFeedbackNode
|
from .optimize_with_feedback import OptimizeWithFeedbackNode
|
||||||
|
from .prepare_tech_consistency_prompt import PrepareTechConsistencyPromptNode
|
||||||
|
|
||||||
# 辅助组件
|
# 辅助组件
|
||||||
from .factories import ChapterFactory
|
from .factories import ChapterFactory
|
||||||
@ -36,6 +37,7 @@ __all__ = [
|
|||||||
"FinalizeChaptersNode",
|
"FinalizeChaptersNode",
|
||||||
"UserFeedbackNode",
|
"UserFeedbackNode",
|
||||||
"OptimizeWithFeedbackNode",
|
"OptimizeWithFeedbackNode",
|
||||||
|
"PrepareTechConsistencyPromptNode",
|
||||||
|
|
||||||
# 辅助组件
|
# 辅助组件
|
||||||
"ChapterFactory",
|
"ChapterFactory",
|
||||||
|
|||||||
@ -52,6 +52,7 @@ class GenerateSubChaptersNode(BaseNode, TocNodeBase):
|
|||||||
self.log_step_info("context_searcher", f"上下文检索器初始化失败: {exc}")
|
self.log_step_info("context_searcher", f"上下文检索器初始化失败: {exc}")
|
||||||
|
|
||||||
guidance_map = dict(state.get("chapter_guidance_map", {}))
|
guidance_map = dict(state.get("chapter_guidance_map", {}))
|
||||||
|
tech_consistency_prompt = (state.get("tech_consistency_prompt") or "").strip() or None
|
||||||
|
|
||||||
# 为每个章节生成子标题
|
# 为每个章节生成子标题
|
||||||
enhanced_chapters = []
|
enhanced_chapters = []
|
||||||
@ -59,7 +60,8 @@ class GenerateSubChaptersNode(BaseNode, TocNodeBase):
|
|||||||
enhanced_chapter = self._enhance_chapter_with_subs(
|
enhanced_chapter = self._enhance_chapter_with_subs(
|
||||||
chapter,
|
chapter,
|
||||||
technical_criteria,
|
technical_criteria,
|
||||||
context_searcher
|
context_searcher,
|
||||||
|
tech_consistency_prompt,
|
||||||
)
|
)
|
||||||
enhanced_chapters.append(enhanced_chapter)
|
enhanced_chapters.append(enhanced_chapter)
|
||||||
|
|
||||||
@ -75,7 +77,8 @@ class GenerateSubChaptersNode(BaseNode, TocNodeBase):
|
|||||||
def _enhance_chapter_with_subs(self,
|
def _enhance_chapter_with_subs(self,
|
||||||
chapter: DocumentChapter,
|
chapter: DocumentChapter,
|
||||||
technical_criteria: List[ScoringCriteria],
|
technical_criteria: List[ScoringCriteria],
|
||||||
context_searcher: Optional[DocumentContextSearcher] = None) -> DocumentChapter:
|
context_searcher: Optional[DocumentContextSearcher] = None,
|
||||||
|
tech_consistency_prompt: Optional[str] = None) -> DocumentChapter:
|
||||||
"""为章节增强子标题
|
"""为章节增强子标题
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -99,7 +102,8 @@ class GenerateSubChaptersNode(BaseNode, TocNodeBase):
|
|||||||
sub_chapters_data = LLMHelper.generate_sub_chapters_ai(
|
sub_chapters_data = LLMHelper.generate_sub_chapters_ai(
|
||||||
corresponding_criteria,
|
corresponding_criteria,
|
||||||
chapter,
|
chapter,
|
||||||
context_snippets=context_snippets
|
context_snippets=context_snippets,
|
||||||
|
tech_consistency_prompt=tech_consistency_prompt,
|
||||||
)
|
)
|
||||||
|
|
||||||
if sub_chapters_data:
|
if sub_chapters_data:
|
||||||
|
|||||||
@ -79,15 +79,19 @@ class LLMHelper:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def generate_sub_chapters_ai(criteria_list: List[ScoringCriteria],
|
def generate_sub_chapters_ai(
|
||||||
parent_chapter: DocumentChapter,
|
criteria_list: List[ScoringCriteria],
|
||||||
context_snippets: Optional[List[str]] = None) -> Optional[List[Dict[str, Any]]]:
|
parent_chapter: DocumentChapter,
|
||||||
|
context_snippets: Optional[List[str]] = None,
|
||||||
|
tech_consistency_prompt: Optional[str] = None,
|
||||||
|
) -> Optional[List[Dict[str, Any]]]:
|
||||||
"""AI生成子章节数据
|
"""AI生成子章节数据
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
criteria_list: 对应的评分项列表
|
criteria_list: 对应的评分项列表
|
||||||
parent_chapter: 父章节
|
parent_chapter: 父章节
|
||||||
context_snippets: 招标文档的相关片段
|
context_snippets: 招标文档的相关片段
|
||||||
|
tech_consistency_prompt: 全局技术一致性提示词(用于统一术语/架构口径)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
子章节数据列表,失败时返回None
|
子章节数据列表,失败时返回None
|
||||||
@ -100,12 +104,14 @@ class LLMHelper:
|
|||||||
# 从配置获取提示词
|
# 从配置获取提示词
|
||||||
prompt_manager = get_prompt_manager()
|
prompt_manager = get_prompt_manager()
|
||||||
context_text = "\n".join(context_snippets) if context_snippets else "(暂无上下文摘录)"
|
context_text = "\n".join(context_snippets) if context_snippets else "(暂无上下文摘录)"
|
||||||
|
consistency_text = (tech_consistency_prompt or "").strip() or "(暂无全局技术一致性提示)"
|
||||||
|
|
||||||
prompt = prompt_manager.get_toc_prompt(
|
prompt = prompt_manager.get_toc_prompt(
|
||||||
"generate_sub_chapters",
|
"generate_sub_chapters",
|
||||||
parent_title=parent_chapter.title,
|
parent_title=parent_chapter.title,
|
||||||
criteria_info=chr(10).join(criteria_info),
|
criteria_info=chr(10).join(criteria_info),
|
||||||
context_snippets=context_text
|
context_snippets=context_text,
|
||||||
|
tech_consistency_prompt=consistency_text,
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
241
src/bidmaster/nodes/toc/prepare_tech_consistency_prompt.py
Normal file
241
src/bidmaster/nodes/toc/prepare_tech_consistency_prompt.py
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
"""全局技术一致性提示词准备节点
|
||||||
|
|
||||||
|
在目录生成开始时:
|
||||||
|
1) 从评分项/招标材料中抽取“技术要求”
|
||||||
|
2) 从知识库RAG中检索“技术解决方案”
|
||||||
|
3) 若两者齐全:拼装成提示词提供给子标题撰写AI使用
|
||||||
|
4) 否则:生成一段几百字内的“系统架构+技术选型+整体建设要求”作为统一参考
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import re
|
||||||
|
import textwrap
|
||||||
|
from typing import Any, Dict, Iterable, List
|
||||||
|
|
||||||
|
from ..base import BaseNode, NodeContext
|
||||||
|
from ...tools.parser import ScoringCriteria
|
||||||
|
from .base_mixins import TocNodeBase
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
_REQ_KEYWORDS = (
|
||||||
|
"须",
|
||||||
|
"应",
|
||||||
|
"必须",
|
||||||
|
"需要",
|
||||||
|
"要求",
|
||||||
|
"提供",
|
||||||
|
"包括",
|
||||||
|
"满足",
|
||||||
|
"支持",
|
||||||
|
"不少于",
|
||||||
|
"不得",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PrepareTechConsistencyPromptNode(BaseNode, TocNodeBase):
|
||||||
|
"""在目录生成开始前准备全局技术一致性提示词"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
return "prepare_tech_consistency_prompt"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def description(self) -> str:
|
||||||
|
return "生成全局技术一致性提示词"
|
||||||
|
|
||||||
|
def execute(self, state: Dict[str, Any], context: NodeContext) -> Dict[str, Any]:
|
||||||
|
return self.safe_execute(self._do_prepare, state, "生成全局技术一致性提示词")
|
||||||
|
|
||||||
|
def _do_prepare(self, state: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
criteria: List[ScoringCriteria] = list(state.get("technical_criteria") or [])
|
||||||
|
|
||||||
|
tender_requirements = self._extract_tender_requirements(criteria)
|
||||||
|
rag_solutions = self._search_rag_solutions(state, criteria, tender_requirements)
|
||||||
|
|
||||||
|
if tender_requirements and rag_solutions:
|
||||||
|
prompt = self._build_prompt_from_sources(tender_requirements, rag_solutions)
|
||||||
|
source = "tender+rag"
|
||||||
|
else:
|
||||||
|
prompt = self._build_fallback_prompt(tender_requirements, criteria)
|
||||||
|
source = "fallback"
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
"技术一致性提示词生成完成: source=%s, req=%s条, rag=%s条",
|
||||||
|
source,
|
||||||
|
len(tender_requirements),
|
||||||
|
len(rag_solutions),
|
||||||
|
)
|
||||||
|
|
||||||
|
return self._update_state(
|
||||||
|
state,
|
||||||
|
tech_consistency_prompt=prompt,
|
||||||
|
tech_consistency_source=source,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _extract_tender_requirements(self, criteria: Iterable[ScoringCriteria]) -> List[str]:
|
||||||
|
seen: set[str] = set()
|
||||||
|
collected: List[str] = []
|
||||||
|
|
||||||
|
def _add(text: str) -> None:
|
||||||
|
clean = self._normalize(text)
|
||||||
|
if not clean or clean in seen:
|
||||||
|
return
|
||||||
|
seen.add(clean)
|
||||||
|
collected.append(text.strip())
|
||||||
|
|
||||||
|
for item in criteria:
|
||||||
|
desc = (getattr(item, "description", "") or "").strip()
|
||||||
|
name = (getattr(item, "item_name", "") or "").strip()
|
||||||
|
if not desc and not name:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if desc:
|
||||||
|
for seg in self._split_sentences(desc):
|
||||||
|
if any(key in seg for key in _REQ_KEYWORDS):
|
||||||
|
_add(seg)
|
||||||
|
elif name:
|
||||||
|
_add(name)
|
||||||
|
|
||||||
|
return collected[:10]
|
||||||
|
|
||||||
|
def _search_rag_solutions(
|
||||||
|
self,
|
||||||
|
state: Dict[str, Any],
|
||||||
|
criteria: List[ScoringCriteria],
|
||||||
|
tender_requirements: List[str],
|
||||||
|
) -> List[str]:
|
||||||
|
rag_tool = state.get("rag_tool")
|
||||||
|
if rag_tool is None:
|
||||||
|
try:
|
||||||
|
from ...tools.rag import RAGTool
|
||||||
|
|
||||||
|
rag_tool = RAGTool()
|
||||||
|
except Exception as exc:
|
||||||
|
logger.warning("RAGTool初始化失败,将走fallback: %s", exc)
|
||||||
|
return []
|
||||||
|
|
||||||
|
query = self._build_solution_query(criteria, tender_requirements)
|
||||||
|
try:
|
||||||
|
results = rag_tool.search(query, k=5)
|
||||||
|
except Exception as exc:
|
||||||
|
logger.warning("RAG检索失败,将走fallback: %s", exc)
|
||||||
|
return []
|
||||||
|
|
||||||
|
snippets: List[str] = []
|
||||||
|
for item in results or []:
|
||||||
|
content = (item or {}).get("content") or ""
|
||||||
|
score = float((item or {}).get("score") or 0.0)
|
||||||
|
if not content.strip():
|
||||||
|
continue
|
||||||
|
if score and score < 0.2:
|
||||||
|
continue
|
||||||
|
snippets.append(self._truncate(content, 240))
|
||||||
|
|
||||||
|
return snippets[:4]
|
||||||
|
|
||||||
|
def _build_solution_query(
|
||||||
|
self,
|
||||||
|
criteria: List[ScoringCriteria],
|
||||||
|
tender_requirements: List[str],
|
||||||
|
) -> str:
|
||||||
|
seeds: List[str] = [
|
||||||
|
"技术解决方案",
|
||||||
|
"技术路线",
|
||||||
|
"总体架构",
|
||||||
|
"系统架构",
|
||||||
|
"技术选型",
|
||||||
|
"性能",
|
||||||
|
"安全",
|
||||||
|
"运维",
|
||||||
|
]
|
||||||
|
|
||||||
|
for item in criteria[:8]:
|
||||||
|
name = (item.item_name or "").strip()
|
||||||
|
if name:
|
||||||
|
seeds.append(name)
|
||||||
|
|
||||||
|
for req in tender_requirements[:3]:
|
||||||
|
seeds.append(req)
|
||||||
|
|
||||||
|
return " ".join(self._dedup_keep_order(seeds))
|
||||||
|
|
||||||
|
def _build_prompt_from_sources(self, tender_requirements: List[str], rag_solutions: List[str]) -> str:
|
||||||
|
req_block = "\n".join(f"- {self._truncate(r, 120)}" for r in tender_requirements[:8])
|
||||||
|
sol_block = "\n".join(f"- {self._truncate(s, 160)}" for s in rag_solutions[:4])
|
||||||
|
|
||||||
|
return (
|
||||||
|
"请在后续子标题生成时统一技术路线与术语,避免同一概念多种叫法。\n"
|
||||||
|
"【招标技术要求摘录】\n"
|
||||||
|
f"{req_block}\n\n"
|
||||||
|
"【知识库技术解决方案摘录】\n"
|
||||||
|
f"{sol_block}\n\n"
|
||||||
|
"生成约束:子标题应优先使用上述摘录中的专业术语/模块命名方式,并保持跨章节一致。"
|
||||||
|
).strip()
|
||||||
|
|
||||||
|
def _build_fallback_prompt(self, tender_requirements: List[str], criteria: List[ScoringCriteria]) -> str:
|
||||||
|
hint_text = " ".join(tender_requirements) or " ".join(
|
||||||
|
(c.item_name + " " + (c.description or "")) for c in criteria[:8]
|
||||||
|
)
|
||||||
|
hints = self._infer_hints(hint_text)
|
||||||
|
|
||||||
|
architecture = "前端多端+后端服务层+数据层+集成与运维安全体系"
|
||||||
|
if "云边端协同" in hints:
|
||||||
|
architecture = "云边端协同(云平台+边缘节点+终端设备)+统一数据与运维体系"
|
||||||
|
|
||||||
|
tech_stack = "前端Web/移动端;后端服务采用可扩展框架并容器化部署;数据层采用关系型数据库+缓存+对象存储;日志/监控/告警一体化。"
|
||||||
|
if "物联网" in hints:
|
||||||
|
tech_stack = "支持物联网接入(MQTT/HTTP等)与设备管理;后端服务容器化部署;数据层关系型数据库+时序/缓存;日志/监控/告警一体化。"
|
||||||
|
|
||||||
|
requirements = "建设要求:接口标准化与可复用;安全合规与权限审计;性能指标可量化;高可用与可扩展;交付文档齐全、便于运维。"
|
||||||
|
|
||||||
|
return (
|
||||||
|
"未检索到可直接复用的技术解决方案摘录,以下为统一参考(几百字内):\n"
|
||||||
|
f"系统架构:{architecture}。\n"
|
||||||
|
f"技术选型:{tech_stack}\n"
|
||||||
|
f"{requirements}\n"
|
||||||
|
"生成约束:后续子标题命名应围绕上述架构拆分模块,并保持全篇术语一致。"
|
||||||
|
).strip()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _split_sentences(text: str) -> List[str]:
|
||||||
|
parts = re.split(r"[\n;;。]+", text)
|
||||||
|
return [p.strip().strip("::·•") for p in parts if p.strip()]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _normalize(text: str) -> str:
|
||||||
|
return re.sub(r"\s+", "", (text or "")).strip().lower()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _truncate(text: str, limit: int) -> str:
|
||||||
|
cleaned = " ".join((text or "").split())
|
||||||
|
return textwrap.shorten(cleaned, width=limit, placeholder="...")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _dedup_keep_order(items: Iterable[str]) -> List[str]:
|
||||||
|
seen: set[str] = set()
|
||||||
|
result: List[str] = []
|
||||||
|
for item in items:
|
||||||
|
key = PrepareTechConsistencyPromptNode._normalize(item)
|
||||||
|
if not key or key in seen:
|
||||||
|
continue
|
||||||
|
seen.add(key)
|
||||||
|
result.append(item)
|
||||||
|
return result
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _infer_hints(text: str) -> List[str]:
|
||||||
|
blob = text or ""
|
||||||
|
hits: List[str] = []
|
||||||
|
if "云边" in blob or "边缘" in blob:
|
||||||
|
hits.append("云边端协同")
|
||||||
|
if "物联网" in blob or "终端" in blob or "传感" in blob:
|
||||||
|
hits.append("物联网")
|
||||||
|
if "数据中台" in blob or "数据中心" in blob or "数据治理" in blob:
|
||||||
|
hits.append("数据中台")
|
||||||
|
if "安全" in blob or "等保" in blob:
|
||||||
|
hits.append("安全合规")
|
||||||
|
return hits
|
||||||
66
tests/unit/test_tech_consistency_prompt.py
Normal file
66
tests/unit/test_tech_consistency_prompt.py
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
from bidmaster.nodes.base import NodeContext
|
||||||
|
from bidmaster.nodes.toc.prepare_tech_consistency_prompt import (
|
||||||
|
PrepareTechConsistencyPromptNode,
|
||||||
|
)
|
||||||
|
from bidmaster.tools.parser import ScoringCriteria, TechnicalCategory
|
||||||
|
|
||||||
|
|
||||||
|
class DummyRagTool:
|
||||||
|
def __init__(self, results):
|
||||||
|
self._results = results
|
||||||
|
self.last_query = None
|
||||||
|
|
||||||
|
def search(self, query: str, k: int = 5):
|
||||||
|
self.last_query = query
|
||||||
|
return list(self._results)
|
||||||
|
|
||||||
|
|
||||||
|
def _criteria(description: str):
|
||||||
|
return [
|
||||||
|
ScoringCriteria(
|
||||||
|
item_name="技术方案-基本要求",
|
||||||
|
max_score=3.0,
|
||||||
|
description=description,
|
||||||
|
category=TechnicalCategory.TECHNICAL_SOLUTION,
|
||||||
|
chapter_id="chapter_01_technical_solution",
|
||||||
|
original_index=0,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_prepare_prompt_uses_tender_and_rag_when_available():
|
||||||
|
rag = DummyRagTool(
|
||||||
|
results=[
|
||||||
|
{"content": "系统采用云边端协同架构,边缘侧负责就近处理与缓存。", "score": 0.9},
|
||||||
|
{"content": "技术选型:容器化部署+统一监控告警+权限审计。", "score": 0.8},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
state = {
|
||||||
|
"technical_criteria": _criteria("供应商须提供总体架构设计方案,包含技术选型与安全策略。"),
|
||||||
|
"rag_tool": rag,
|
||||||
|
}
|
||||||
|
|
||||||
|
node = PrepareTechConsistencyPromptNode()
|
||||||
|
result = node.execute(state, NodeContext())
|
||||||
|
|
||||||
|
assert result["tech_consistency_source"] == "tender+rag"
|
||||||
|
prompt = result["tech_consistency_prompt"]
|
||||||
|
assert "招标技术要求摘录" in prompt
|
||||||
|
assert "知识库技术解决方案摘录" in prompt
|
||||||
|
assert "云边端协同" in prompt
|
||||||
|
|
||||||
|
|
||||||
|
def test_prepare_prompt_falls_back_when_rag_missing():
|
||||||
|
rag = DummyRagTool(results=[])
|
||||||
|
state = {
|
||||||
|
"technical_criteria": _criteria("供应商须提供总体架构设计方案,包含技术选型与安全策略。"),
|
||||||
|
"rag_tool": rag,
|
||||||
|
}
|
||||||
|
|
||||||
|
node = PrepareTechConsistencyPromptNode()
|
||||||
|
result = node.execute(state, NodeContext())
|
||||||
|
|
||||||
|
assert result["tech_consistency_source"] == "fallback"
|
||||||
|
prompt = result["tech_consistency_prompt"]
|
||||||
|
assert "系统架构" in prompt
|
||||||
|
assert "技术选型" in prompt
|
||||||
@ -9,8 +9,9 @@ from bidmaster.utils.document_context import DocumentContext, DocumentContextMat
|
|||||||
def test_generate_sub_chapters_uses_context(monkeypatch):
|
def test_generate_sub_chapters_uses_context(monkeypatch):
|
||||||
captured = {}
|
captured = {}
|
||||||
|
|
||||||
def fake_generate(criteria_list, parent_chapter, context_snippets=None):
|
def fake_generate(criteria_list, parent_chapter, context_snippets=None, tech_consistency_prompt=None):
|
||||||
captured["context"] = context_snippets
|
captured["context"] = context_snippets
|
||||||
|
captured["consistency"] = tech_consistency_prompt
|
||||||
return [{"title": "示例小节", "level": 2, "score": 5, "children": []}]
|
return [{"title": "示例小节", "level": 2, "score": 5, "children": []}]
|
||||||
|
|
||||||
class DummySearcher:
|
class DummySearcher:
|
||||||
@ -52,6 +53,7 @@ def test_generate_sub_chapters_uses_context(monkeypatch):
|
|||||||
"preliminary_chapters": [chapter],
|
"preliminary_chapters": [chapter],
|
||||||
"technical_criteria": criteria,
|
"technical_criteria": criteria,
|
||||||
"document_context": DocumentContext("demo.docx", "test-model", []),
|
"document_context": DocumentContext("demo.docx", "test-model", []),
|
||||||
|
"tech_consistency_prompt": "统一术语:云边端协同/边缘节点/终端设备",
|
||||||
}
|
}
|
||||||
|
|
||||||
node = GenerateSubChaptersNode()
|
node = GenerateSubChaptersNode()
|
||||||
@ -59,6 +61,7 @@ def test_generate_sub_chapters_uses_context(monkeypatch):
|
|||||||
|
|
||||||
assert result["preliminary_chapters"][0].children
|
assert result["preliminary_chapters"][0].children
|
||||||
assert captured["context"] and "项目概述" in captured["context"][0]
|
assert captured["context"] and "项目概述" in captured["context"][0]
|
||||||
|
assert "统一术语" in (captured["consistency"] or "")
|
||||||
|
|
||||||
|
|
||||||
def test_user_feedback_auto_triggers_optimization():
|
def test_user_feedback_auto_triggers_optimization():
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user