216 lines
10 KiB
Python
216 lines
10 KiB
Python
import os
|
||
from dotenv import load_dotenv
|
||
from crewai import Agent, Task, Crew, Process
|
||
from langchain_openai import ChatOpenAI
|
||
|
||
# 加载环境变量 (OPENAI_API_KEY)
|
||
load_dotenv()
|
||
|
||
# 初始化 LLM,使用 Deepseek API
|
||
# Please install OpenAI SDK first: `pip3 install openai`
|
||
|
||
|
||
llm = ChatOpenAI(
|
||
api_key=os.getenv('DEEPSEEK_API_KEY'),
|
||
base_url=os.getenv('DEEPSEEK_API_BASE'),
|
||
model_name="deepseek/deepseek-chat",
|
||
temperature=0.7,
|
||
)
|
||
|
||
# --- 全局工具和上下文加载 ---
|
||
def load_jargon_dictionary(file_path='livestream_jargon.txt'):
|
||
"""加载直播间黑话字典作为上下文"""
|
||
try:
|
||
with open(file_path, 'r', encoding='utf-8') as f:
|
||
return f.read()
|
||
except FileNotFoundError:
|
||
print(f"警告: 黑话字典文件 {file_path} 未找到。")
|
||
return ""
|
||
|
||
# 加载字典内容
|
||
jargon_context = load_jargon_dictionary()
|
||
|
||
# --- 定义智能体 (Agents) ---
|
||
|
||
# 1. 文本预处理专员
|
||
cleaner_agent = Agent(
|
||
role='文本预处理专员',
|
||
goal='修正语音转录稿中的错别字,移除无效语气词,并将因多人抢话导致的断裂句子进行逻辑合并,使文本流畅易读。',
|
||
backstory=(
|
||
'你是一位经验丰富的转录稿校对员,对中文口语表达习惯了如指掌。'
|
||
'你特别擅长处理直播领域的文本,能够精准识别并修正行业黑话相关的错别字,'
|
||
'同时去除“嗯、啊、是吧、你知道吧”等冗余信息,让混乱的原文变得清晰、专业。'
|
||
),
|
||
verbose=True,
|
||
allow_delegation=False,
|
||
llm=llm
|
||
)
|
||
|
||
# 2. 主题内容筛选师
|
||
analyst_agent = Agent(
|
||
role='互联网娱乐直播主题分析师',
|
||
goal='从清洗后的文本中,精准筛选并提取出所有与“互联网娱乐直播运营策略、技巧、建议、分析”相关的核心内容。',
|
||
backstory=(
|
||
'你是一位深耕直播行业多年的分析师,能够迅速从海量对话中识别出真正有价值的“干货”。'
|
||
'你的任务是过滤掉日常闲聊、跑题内容、纯粹的情绪表达以及与直播运营无关的对话,'
|
||
'只保留那些能为直播从业者提供具体指导和启发的内容。'
|
||
),
|
||
verbose=True,
|
||
allow_delegation=False,
|
||
llm=llm
|
||
)
|
||
|
||
# 3. 知识结构化专家
|
||
architect_agent = Agent(
|
||
role='知识结构化专家',
|
||
goal='将提炼出的核心内容,识别出其中的问答(QA)对,并将其转换成指定的JSON格式的知识条目。',
|
||
backstory=(
|
||
'你是一名顶级的知识管理专家,擅长将非结构化的信息转化为条理清晰、易于检索的知识库。'
|
||
'你将接收一份关于直播运营的纯文本“干货”,你的任务是将其中的每一个独立的知识点或问答,'
|
||
'都 meticulously 地构造成一个标准的JSON对象,确保所有字段都得到恰当、准确的填充。'
|
||
),
|
||
verbose=True,
|
||
allow_delegation=False,
|
||
llm=llm
|
||
)
|
||
|
||
# --- 定义任务 (Tasks) ---
|
||
|
||
def create_processing_tasks(jargon_context):
|
||
"""
|
||
为单个MD文件创建处理任务流
|
||
"""
|
||
# 任务1: 清洗文本
|
||
task_clean = Task(
|
||
description=(
|
||
'1. 读取原始Markdown文本:`{markdown_text}`\n'
|
||
'2. 参考这份《直播间黑话字典》进行校对:`{jargon_dictionary}`\n'
|
||
'3. **核心要求**:\n'
|
||
' a. **修正错别字**: 特别是字典中提到的黑话,例如将“场馆”修正为“场观”。\n'
|
||
' b. **移除无效词**: 删除如“嗯”、“啊”、“哼”、“是吧”、“你知道吧?”、“完了”等大量出现的口水词。\n'
|
||
' c. **合并断句**: 将不同发言人之间因快速抢话而打断的句子,根据逻辑重新组合成完整、通顺的段落。\n'
|
||
' d. **保留发言人标识**: 在处理后,依然保留“发言人X”的标识,但可以合并连续的、属于同一发言人的段落。\n'
|
||
'4. **输出**: 返回处理后的、干净流畅的完整文本。'
|
||
),
|
||
expected_output='一份经过校对、去冗、合并断句后的,但仍保留发言人信息的纯净文本。',
|
||
agent=cleaner_agent,
|
||
llm=llm
|
||
)
|
||
|
||
# 任务2: 筛选核心内容
|
||
task_filter = Task(
|
||
description=(
|
||
'1. 分析由上一步任务输出的纯净文本。\n'
|
||
'2. **核心要求**: 识别并抽取出所有直接讨论“互联网娱乐直播”的策略、技巧、数据分析、观众互动、变现方法、内容创作、平台规则等核心“干货”内容。\n'
|
||
'3. **需要剔除的内容**: \n'
|
||
' - 与主题无关的闲聊(例如讨论超市老板娘的例子,除非它被明确用来解释一个直播策略)。\n'
|
||
' - 直播间的日常互动和喊话(例如“帮老王点点赞”、“加个灯牌”),除非它被用作一个具体策略的示例。\n'
|
||
' - 无法形成完整知识点的碎片化对话。\n'
|
||
'4. **输出**: 返回一个只包含高价值、与主题强相关内容的文本块。'
|
||
),
|
||
expected_output='一个浓缩了所有关于直播运营策略和技巧的精华文本。',
|
||
agent=analyst_agent,
|
||
context=[task_clean], # 明确任务依赖
|
||
llm=llm
|
||
)
|
||
|
||
# 任务3: 结构化为JSON
|
||
task_structure = Task(
|
||
description=(
|
||
'1. 分析上一步筛选出的精华文本。\n'
|
||
'2. **核心要求**: 识别文本中每一个独立的知识点或Q&A对(包括自问自答形式)。对于每一个识别出的“答案”部分,严格按照以下JSON结构进行封装。一个答案封装成一个JSON对象。如果文本包含多个知识点,则输出一个JSON列表。\n'
|
||
'3. **JSON模板**:\n'
|
||
'```json\n'
|
||
'{{\n'
|
||
' "answer_id": "一个独特的、基于内容的英文小写下划线ID,例如: pk_strategy_001",\n'
|
||
' "summary": "用一句话精准概括这个知识点的核心思想。",\n'
|
||
' "core_strategy": [\n'
|
||
' {{\n'
|
||
' "step": 1,\n'
|
||
' "title": "策略步骤的标题",\n'
|
||
' "content": "对该步骤的具体描述。"\n'
|
||
' }}\n'
|
||
' ],\n'
|
||
' "script_examples": ["从原文中提炼或总结出的可直接使用的话术示例。"],\n'
|
||
' "common_pitfalls": ["根据原文内容,总结出的常见错误做法或思维误区。"],\n'
|
||
' "case_study": {{\n'
|
||
' "streamer_name": "如果原文提到,则填写主播名",\n'
|
||
' "description": "如果原文提到相关案例,则进行描述。若无,则明确说明。"\n'
|
||
' }},\n'
|
||
' "source_reference": {{\n'
|
||
' "video_title": "可以基于文件名或内容推断,例如 livestream_part_1",\n'
|
||
' "timestamp": "从原文中找到这个知识点开始的时间戳,例如 08:41"\n'
|
||
' }}\n'
|
||
'}}\n'
|
||
'```\n'
|
||
'4. **填充指南**:\n'
|
||
' - `answer_id`: 必须创造一个有意义的ID。\n'
|
||
' - 如果某项内容(如`case_study`)在原文中完全没有提及,请在该字段的description中明确注明“原文未提及相关案例”。\n'
|
||
' - `core_strategy`可以包含多个步骤,如果知识点本身就是一个整体,则只包含一个step。\n'
|
||
' - `source_reference`中的时间戳要尽可能精确。\n'
|
||
'5. **输出**: 一个包含一个或多个JSON对象的字符串,格式为JSON数组(即 `[ { ... }, { ... } ]`)。'
|
||
),
|
||
expected_output='一个严格符合模板的JSON字符串数组,其中每个对象都是一个从原文提炼出的结构化知识点。',
|
||
agent=architect_agent,
|
||
context=[task_filter], # 明确任务依赖
|
||
llm=llm
|
||
)
|
||
|
||
return [task_clean, task_filter, task_structure]
|
||
|
||
# --- 主执行流程 ---
|
||
def main():
|
||
input_dir = 'raw_md_files'
|
||
output_dir = 'processed_json_outputs'
|
||
|
||
if not os.path.exists(output_dir):
|
||
os.makedirs(output_dir)
|
||
|
||
md_files = [f for f in os.listdir(input_dir) if f.endswith('.md')]
|
||
|
||
for md_file in md_files:
|
||
print(f"\n{'='*20} 正在处理文件: {md_file} {'='*20}")
|
||
input_file_path = os.path.join(input_dir, md_file)
|
||
|
||
with open(input_file_path, 'r', encoding='utf-8') as f:
|
||
markdown_content = f.read()
|
||
|
||
# 为当前文件创建任务和Crew
|
||
tasks = create_processing_tasks(jargon_context)
|
||
livestream_crew = Crew(
|
||
agents=[cleaner_agent, analyst_agent, architect_agent],
|
||
tasks=tasks,
|
||
process=Process.sequential,
|
||
verbose=True # verbose=2会打印出思考过程,便于调试
|
||
)
|
||
|
||
# 启动Crew,传入动态数据
|
||
result = livestream_crew.kickoff(inputs={
|
||
'markdown_text': markdown_content,
|
||
'jargon_dictionary': jargon_context
|
||
})
|
||
|
||
print(f"\n{'*'*20} 文件 {md_file} 处理完成 {'*'*20}")
|
||
print("最终输出结果:")
|
||
print(result)
|
||
|
||
# 保存结果
|
||
output_filename = os.path.splitext(md_file)[0] + '.json'
|
||
output_file_path = os.path.join(output_dir, output_filename)
|
||
with open(output_file_path, 'w', encoding='utf-8') as f:
|
||
# 从CrewOutput对象中提取原始字符串输出
|
||
final_json_string = result.raw
|
||
|
||
# (可选但推荐) 清理可能存在的多余标记,确保文件是纯净的JSON
|
||
if final_json_string.startswith("```json"):
|
||
final_json_string = final_json_string[7:]
|
||
if final_json_string.endswith("```"):
|
||
final_json_string = final_json_string[:-3]
|
||
final_json_string = final_json_string.strip()
|
||
|
||
f.write(final_json_string) # <--- 使用 .raw 属性
|
||
|
||
print(f"结果已保存至: {output_file_path}")
|
||
|
||
if __name__ == '__main__':
|
||
main() |