feat(13-07): integrate SemanticSegmentationEngine into SRTProcessor
- 添加 enable_semantic_segmentation 参数到 SRTProcessor.__init__ - 启用时使用 SemanticSegmentationEngine,禁用时使用 SemanticSegmenter - AI 不可用时自动降级到规则分段 - 更新 _generate_markdown() 支持带标题的 VideoSegment 输出 - 调整步骤顺序:先加载元数据,再进行语义分段 - 更新 UnifiedSegmentationStrategy.segment() 返回 List[VideoSegment] - 添加代理方法到 SemanticSegmentationEngine 以保持向后兼容 - 创建全面的集成测试(9 个测试用例) - 更新现有语义引擎测试以适配新 API Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -11,7 +11,8 @@ from kb_workflow.srt.semantic_engine import (
|
||||
extract_json_from_llm_response,
|
||||
VideoSegment,
|
||||
SegmentationResult,
|
||||
SemanticSegmentationEngine
|
||||
SemanticSegmentationEngine,
|
||||
UnifiedSegmentationStrategy
|
||||
)
|
||||
|
||||
|
||||
@@ -236,15 +237,17 @@ class TestChineseSentenceBoundary:
|
||||
Segment(4, 15.0, 20.0, "这是第四句话……")
|
||||
]
|
||||
|
||||
engine = SemanticSegmentationEngine(ai_client=mock_client)
|
||||
result = engine.segment(segments, {"title": "测试", "video_type": "知识类"})
|
||||
# 使用新的策略模式 API
|
||||
from kb_workflow.srt.semantic_engine import UnifiedSegmentationStrategy
|
||||
strategy = UnifiedSegmentationStrategy(ai_client=mock_client)
|
||||
result = strategy.segment(segments, {"title": "测试", "video_type": "知识类"})
|
||||
|
||||
# 验证分段边界在标点后
|
||||
assert len(result) == 2
|
||||
# 第一个分段在问号后结束
|
||||
assert result[0].strip().endswith("?")
|
||||
assert result[0].content.strip().endswith("?")
|
||||
# 第二个分段在省略号后结束
|
||||
assert result[1].strip().endswith("……")
|
||||
assert result[1].content.strip().endswith("……")
|
||||
|
||||
|
||||
# ============================================================================
|
||||
@@ -307,7 +310,8 @@ class TestTopicBoundaryDetection:
|
||||
}, ensure_ascii=False)
|
||||
|
||||
mock_client = MockAIClient(llm_response)
|
||||
engine = SemanticSegmentationEngine(ai_client=mock_client)
|
||||
strategy = UnifiedSegmentationStrategy(ai_client=mock_client)
|
||||
engine = SemanticSegmentationEngine(strategy=strategy)
|
||||
|
||||
metadata = {
|
||||
"title": "AI技术介绍",
|
||||
@@ -318,8 +322,8 @@ class TestTopicBoundaryDetection:
|
||||
|
||||
# 验证:结果包含两个段落
|
||||
assert len(paragraphs) == 2, f"Expected 2 paragraphs, got {len(paragraphs)}"
|
||||
assert "机器学习是人工智能" in paragraphs[0], "First paragraph should mention 机器学习"
|
||||
assert "深度学习是机器学习" in paragraphs[1], "Second paragraph should mention 深度学习"
|
||||
assert "机器学习是人工智能" in paragraphs[0].content, "First paragraph should mention 机器学习"
|
||||
assert "深度学习是机器学习" in paragraphs[1].content, "Second paragraph should mention 深度学习"
|
||||
|
||||
def test_semantic_completeness(self):
|
||||
"""测试单个话题内容完整,不混入其他话题
|
||||
@@ -346,7 +350,8 @@ class TestTopicBoundaryDetection:
|
||||
}, ensure_ascii=False)
|
||||
|
||||
mock_client = MockAIClient(llm_response)
|
||||
engine = SemanticSegmentationEngine(ai_client=mock_client)
|
||||
strategy = UnifiedSegmentationStrategy(ai_client=mock_client)
|
||||
engine = SemanticSegmentationEngine(strategy=strategy)
|
||||
|
||||
metadata = {
|
||||
"title": "优化算法介绍",
|
||||
@@ -357,10 +362,10 @@ class TestTopicBoundaryDetection:
|
||||
|
||||
# 验证:单个段落包含完整内容
|
||||
assert len(paragraphs) == 1, f"Expected 1 paragraph, got {len(paragraphs)}"
|
||||
assert "梯度下降是一种优化算法" in paragraphs[0]
|
||||
assert "批量梯度下降" in paragraphs[0]
|
||||
assert "随机梯度下降" in paragraphs[0]
|
||||
assert "小批量梯度下降" in paragraphs[0]
|
||||
assert "梯度下降是一种优化算法" in paragraphs[0].content
|
||||
assert "批量梯度下降" in paragraphs[0].content
|
||||
assert "随机梯度下降" in paragraphs[0].content
|
||||
assert "小批量梯度下降" in paragraphs[0].content
|
||||
|
||||
def test_no_mixed_topics(self):
|
||||
"""测试分段不混入不同话题的内容
|
||||
@@ -390,7 +395,8 @@ class TestTopicBoundaryDetection:
|
||||
}, ensure_ascii=False)
|
||||
|
||||
mock_client = MockAIClient(llm_response)
|
||||
engine = SemanticSegmentationEngine(ai_client=mock_client)
|
||||
strategy = UnifiedSegmentationStrategy(ai_client=mock_client)
|
||||
engine = SemanticSegmentationEngine(strategy=strategy)
|
||||
|
||||
metadata = {
|
||||
"title": "AI技术介绍",
|
||||
@@ -402,7 +408,7 @@ class TestTopicBoundaryDetection:
|
||||
# 验证:分段内容不混入其他话题
|
||||
assert len(paragraphs) == 2
|
||||
# 第一个段落不应该包含深度学习的内容(除了可能的引用)
|
||||
assert "深度学习" not in paragraphs[0] or "子领域" in paragraphs[0]
|
||||
assert "深度学习" not in paragraphs[0].content or "子领域" in paragraphs[0].content
|
||||
# 第二个段落可以提到"机器学习"因为它是"深度学习的超类"
|
||||
|
||||
def _create_multi_topic_segments(self) -> list[Segment]:
|
||||
@@ -559,7 +565,8 @@ class TestSentenceBoundaryProtection:
|
||||
mock_client.generate.return_value = mock_response
|
||||
|
||||
# Create engine and segment
|
||||
engine = SemanticSegmentationEngine(ai_client=mock_client)
|
||||
strategy = UnifiedSegmentationStrategy(ai_client=mock_client)
|
||||
engine = SemanticSegmentationEngine(strategy=strategy)
|
||||
metadata = {"title": "机器学习介绍", "duration": 9.0}
|
||||
paragraphs = engine.segment(segments, metadata)
|
||||
|
||||
@@ -571,9 +578,10 @@ class TestSentenceBoundaryProtection:
|
||||
sentence_endings = r'[。?!……]$'
|
||||
|
||||
for para in paragraphs:
|
||||
para_stripped = para.strip()
|
||||
para_content = para.content if hasattr(para, 'content') else para
|
||||
para_stripped = para_content.strip()
|
||||
assert re.search(sentence_endings, para_stripped), \
|
||||
f"段落应以标点符号结尾: {para[:50]}..."
|
||||
f"段落应以标点符号结尾: {para_content[:50]}..."
|
||||
|
||||
def test_sentence_boundary_in_prompt(self):
|
||||
"""Test: SEGMENTATION_PROMPT 包含句边界保护指令"""
|
||||
@@ -626,7 +634,8 @@ class TestArgumentCompleteness:
|
||||
mock_client.generate.return_value = mock_response
|
||||
|
||||
# Create engine and segment
|
||||
engine = SemanticSegmentationEngine(ai_client=mock_client)
|
||||
strategy = UnifiedSegmentationStrategy(ai_client=mock_client)
|
||||
engine = SemanticSegmentationEngine(strategy=strategy)
|
||||
metadata = {"title": "深度学习介绍", "duration": 12.0}
|
||||
paragraphs = engine.segment(segments, metadata)
|
||||
|
||||
@@ -634,7 +643,7 @@ class TestArgumentCompleteness:
|
||||
assert len(paragraphs) == 1
|
||||
|
||||
# Verify argument completeness - premise and conclusion together
|
||||
first_para = paragraphs[0]
|
||||
first_para = paragraphs[0].content
|
||||
assert "首先" in first_para
|
||||
assert "因此" in first_para
|
||||
|
||||
@@ -698,7 +707,8 @@ class TestParallelStructurePreservation:
|
||||
mock_client.generate.return_value = mock_response
|
||||
|
||||
# Create engine and segment
|
||||
engine = SemanticSegmentationEngine(ai_client=mock_client)
|
||||
strategy = UnifiedSegmentationStrategy(ai_client=mock_client)
|
||||
engine = SemanticSegmentationEngine(strategy=strategy)
|
||||
metadata = {"title": "数据处理流程", "duration": 12.0}
|
||||
paragraphs = engine.segment(segments, metadata)
|
||||
|
||||
@@ -706,9 +716,9 @@ class TestParallelStructurePreservation:
|
||||
assert len(paragraphs) == 3
|
||||
|
||||
# Verify each parallel point is complete in its segment
|
||||
assert "首先" in paragraphs[0]
|
||||
assert "其次" in paragraphs[1]
|
||||
assert "最后" in paragraphs[2]
|
||||
assert "首先" in paragraphs[0].content
|
||||
assert "其次" in paragraphs[1].content
|
||||
assert "最后" in paragraphs[2].content
|
||||
|
||||
def test_no_special_handling_for_parallel_in_prompt(self):
|
||||
"""Test: SEGMENTATION_PROMPT 不包含并列结构特殊处理
|
||||
|
||||
Reference in New Issue
Block a user