Spaces:
Sleeping
Sleeping
File size: 20,840 Bytes
2666e45 e3a0508 2666e45 ba7141d 2666e45 ba7141d e3a0508 ba7141d e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 ba7141d 2666e45 e3a0508 9d4d891 e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 ba7141d 2666e45 ba7141d 2666e45 e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 ba7141d 2666e45 e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 ba7141d 2666e45 e3a0508 2666e45 e3a0508 ba7141d e3a0508 2666e45 ba7141d 2666e45 ba7141d 2666e45 ba7141d 2666e45 e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 8785609 2666e45 8785609 e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 e3a0508 62c3609 e3a0508 62c3609 e3a0508 62c3609 e3a0508 2666e45 e3a0508 2666e45 e3a0508 2666e45 ba7141d 2666e45 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 |
"""
AI 博客助手 - 多Agent协作系统
使用 LangGraph + Streamlit + OpenAI/Claude 实现
功能:
1. 研究员 Agent:搜索和收集信息
2. 作家 Agent:撰写博客内容
3. 编辑 Agent:审核和优化内容
"""
import streamlit as st
from typing import TypedDict, Annotated, List
import operator
from datetime import datetime
import json
import os
# 检查并安装依赖
try:
from langgraph.graph import StateGraph, END
from langchain_core.messages import HumanMessage, SystemMessage
except ImportError:
st.error("""
⚠️ 缺少必要的依赖包!请运行:
```
pip install langgraph langchain langchain-core langchain-openai
```
""")
st.stop()
# ============= 状态定义 =============
class BlogState(TypedDict):
topic: str # 博客主题
research_notes: str # 研究笔记
draft: str # 草稿
final_blog: str # 最终博客
feedback: str # 编辑反馈
messages: Annotated[List[str], operator.add] # 消息历史
revision_count: int # 修订次数
use_mock: bool # 是否使用模拟模式
# ============= LLM 配置 =============
def get_llm_response(prompt: str, system_prompt: str, use_mock: bool = False):
"""获取 LLM 响应(支持 OpenAI 或模拟模式)"""
if use_mock:
# 模拟模式:返回预设响应
return f"[模拟响应] 基于提示生成的内容:\n{prompt[:100]}..."
try:
# 尝试使用 OpenAI
from langchain_openai import ChatOpenAI
api_key = os.environ.get("OPENAI_API_KEY") or st.session_state.get("openai_key")
if not api_key:
return "[错误] 请配置 OpenAI API Key"
llm = ChatOpenAI(
model="gpt-4o-mini",
temperature=0.7,
api_key=api_key
)
messages = [
SystemMessage(content=system_prompt),
HumanMessage(content=prompt)
]
response = llm.invoke(messages)
return response.content
except Exception as e:
return f"[LLM 调用失败] {str(e)}\n\n使用模拟模式生成内容..."
# ============= Agent 节点 =============
class ResearcherAgent:
"""研究员 Agent - 负责收集信息"""
def __call__(self, state: BlogState) -> BlogState:
topic = state["topic"]
use_mock = state.get("use_mock", True)
system_prompt = """你是一位专业的研究员,擅长收集和整理信息。
请为给定的博客主题提供详细的研究笔记,包括:
1. 主题背景和重要性
2. 关键概念和术语
3. 目标读者分析
4. 建议的写作角度
5. 相关案例和数据"""
user_prompt = f"""请为以下博客主题进行研究:
主题:{topic}
请提供结构化的研究笔记。"""
if use_mock:
# 模拟响应
research = f"""# {topic} 研究笔记
## 1. 主题背景
{topic}是当前技术领域的重要话题,具有广泛的应用价值和发展前景。
## 2. 关键概念
- 核心概念:{topic}的基本定义和原理
- 技术栈:相关技术和工具
- 应用场景:实际使用案例
## 3. 目标读者
- 初学者:需要入门指导
- 进阶开发者:寻求最佳实践
- 技术决策者:关注价值和ROI
## 4. 建议角度
- 实用性:提供可操作的指南
- 深度:探讨技术细节
- 前瞻性:展望未来趋势
## 5. 参考资料
- 官方文档
- 技术博客
- 开源项目"""
else:
research = get_llm_response(user_prompt, system_prompt, use_mock)
state["research_notes"] = research
state["messages"].append(f"✅ 研究员完成调研:已收集 {topic} 相关信息")
return state
class WriterAgent:
"""作家 Agent - 负责撰写内容"""
def __call__(self, state: BlogState) -> BlogState:
topic = state["topic"]
research = state["research_notes"]
feedback = state.get("feedback", "")
use_mock = state.get("use_mock", True)
if feedback:
system_prompt = """你是一位经验丰富的技术博客作家。
请根据编辑的反馈修改博客内容,确保:
1. 解决所有提出的问题
2. 保持专业和易读
3. 添加必要的示例和说明"""
user_prompt = f"""请修改以下博客内容:
原始主题:{topic}
研究笔记:
{research}
编辑反馈:
{feedback}
请提供修订后的完整博客内容。"""
else:
system_prompt = """你是一位经验丰富的技术博客作家。
请撰写高质量的博客文章,要求:
1. 结构清晰,有引言、正文、总结
2. 内容准确,有深度
3. 包含代码示例(如适用)
4. 语言流畅,易于理解
5. 使用 Markdown 格式"""
user_prompt = f"""请根据以下研究笔记撰写博客:
主题:{topic}
研究笔记:
{research}
请撰写一篇完整的博客文章。"""
if use_mock:
# 模拟响应
draft = f"""# {topic}:深入解析与实践指南
*作者:AI博客助手 | 日期:{datetime.now().strftime('%Y-%m-%d')}*
## 引言
在当今快速发展的技术领域,{topic}正在成为开发者和企业关注的焦点。本文将全面介绍{topic}的核心概念、应用场景和实践经验,帮助读者深入理解并掌握这一技术。
## 什么是{topic}?
{topic}是一种[技术描述],它通过[工作原理]来实现[核心功能]。与传统方法相比,{topic}具有以下优势:
- **优势一**:提高效率和性能
- **优势二**:降低复杂度
- **优势三**:增强可维护性
## 核心特性
### 1. 特性一:创新性
{topic}采用了创新的方法来解决传统问题...
### 2. 特性二:可扩展性
系统架构设计灵活,支持水平和垂直扩展...
### 3. 特性三:易用性
提供友好的 API 和工具,降低使用门槛...
## 快速开始
让我们通过一个简单的例子来了解如何使用{topic}:
```python
# 示例代码
def example_function():
\"\"\"这是一个{topic}的简单示例\"\"\"
# 初始化
config = {{
'name': '{topic}',
'version': '1.0'
}}
# 执行操作
result = process(config)
return result
# 运行示例
if __name__ == "__main__":
output = example_function()
print(f"结果: {{output}}")
```
## 实践案例
### 案例一:实际应用场景
在[场景描述]中,我们使用{topic}实现了[功能]...
### 案例二:性能优化
通过应用{topic},系统性能提升了[数据]...
## 最佳实践
基于实际经验,以下是使用{topic}的最佳实践:
1. **从简单开始**:先掌握基础功能,再深入高级特性
2. **阅读文档**:官方文档是最好的学习资源
3. **参与社区**:加入技术社区,交流经验
4. **持续学习**:关注最新发展和更新
## 常见问题
**Q1: {topic}适合什么场景?**
A: {topic}特别适合[场景列表]...
**Q2: 如何优化性能?**
A: 可以通过[优化方法]来提升性能...
**Q3: 有哪些注意事项?**
A: 需要注意[注意事项列表]...
## 未来展望
{topic}的发展前景广阔,未来可能会看到:
- 更多的集成和生态系统
- 性能和功能的持续优化
- 更广泛的行业应用
## 总结
通过本文,我们全面了解了{topic}的核心概念、应用实践和最佳经验。{topic}作为一项重要技术,值得每位开发者学习和掌握。
希望这篇文章对你有所帮助。如果有任何问题或建议,欢迎在评论区讨论!
---
**参考资料**
- 官方文档
- 技术社区
- 开源项目
**标签**: {topic}, 技术, 教程, 最佳实践
{f"*本文修订次数: {state['revision_count']}*" if state['revision_count'] > 0 else ""}
"""
else:
draft = get_llm_response(user_prompt, system_prompt, use_mock)
state["draft"] = draft
state["messages"].append(f"✍️ 作家完成{'修订' if feedback else '初稿'}撰写")
return state
class EditorAgent:
"""编辑 Agent - 负责审核和优化"""
def __call__(self, state: BlogState) -> BlogState:
draft = state["draft"]
revision_count = state.get("revision_count", 0)
use_mock = state.get("use_mock", True)
# 质量检查
issues = []
if len(draft) < 500:
issues.append("内容长度不足,需要扩充至至少500字")
if "```" not in draft and ("代码" in state["topic"] or "编程" in state["topic"]):
issues.append("技术文章缺少代码示例")
if "总结" not in draft and "结论" not in draft:
issues.append("缺少总结或结论部分")
if draft.count("#") < 3:
issues.append("文章结构层次不够清晰,建议增加小节")
# 决定是否需要修订
if issues and revision_count < 2:
feedback_text = f"""编辑审核反馈(第{revision_count + 1}次):
需要改进的地方:
{chr(10).join(f'{i+1}. {issue}' for i, issue in enumerate(issues))}
请针对以上问题进行修改,提升文章质量。"""
state["feedback"] = feedback_text
state["revision_count"] = revision_count + 1
state["messages"].append(f"📝 编辑提出修改意见(第{revision_count + 1}次)")
return state
else:
# 批准发布
state["final_blog"] = draft
state["feedback"] = ""
if issues:
state["messages"].append(f"✅ 编辑批准发布(达到最大修订次数,存在{len(issues)}个小问题)")
else:
state["messages"].append("✅ 编辑批准发布(质量优秀)")
return state
# ============= 路由逻辑 =============
def should_continue(state: BlogState) -> str:
"""决定工作流下一步"""
if state.get("final_blog"):
return "end"
elif state.get("feedback"):
return "revise"
else:
return "review"
# ============= 构建工作流 =============
@st.cache_resource
def create_blog_workflow():
"""创建博客生成工作流(缓存)"""
workflow = StateGraph(BlogState)
# 添加节点
workflow.add_node("researcher", ResearcherAgent())
workflow.add_node("writer", WriterAgent())
workflow.add_node("editor", EditorAgent())
# 定义流程
workflow.set_entry_point("researcher")
workflow.add_edge("researcher", "writer")
workflow.add_edge("writer", "editor")
# 条件路由
workflow.add_conditional_edges(
"editor",
should_continue,
{
"end": END,
"revise": "writer",
"review": "editor"
}
)
return workflow.compile()
# ============= Streamlit 界面 =============
def main():
st.set_page_config(
page_title="AI 博客助手",
page_icon="✍️",
layout="wide"
)
# 初始化 session state
if "openai_key" not in st.session_state:
st.session_state.openai_key = ""
st.title("✍️ AI 博客助手")
st.markdown("### 🤖 多 Agent 协作的智能博客生成系统")
# 侧边栏配置
with st.sidebar:
st.header("⚙️ 系统配置")
# API 配置
with st.expander("🔑 API 配置", expanded=False):
api_key = st.text_input(
"OpenAI API Key",
type="password",
value=st.session_state.openai_key,
help="如果不提供,将使用模拟模式"
)
st.session_state.openai_key = api_key
if api_key:
st.success("✅ API Key 已配置")
else:
st.info("💡 未配置 API Key,将使用模拟模式")
st.divider()
# 系统架构说明
st.markdown("""
### 🏗️ 系统架构
**1. 研究员 Agent** 🔍
- 收集主题相关信息
- 分析目标受众
- 提供写作建议
**2. 作家 Agent** ✍️
- 撰写博客内容
- 根据反馈修订
- 保持风格一致
**3. 编辑 Agent** 📝
- 审核内容质量
- 检查结构完整
- 提供改进建议
""")
st.divider()
max_revisions = st.slider(
"最大修订次数",
min_value=0,
max_value=5,
value=2,
help="编辑最多要求修订的次数"
)
st.divider()
# 示例主题
st.markdown("""
### 💡 示例主题
- Python 异步编程
- Docker 容器化
- 微服务架构
- React Hooks
- 机器学习入门
""")
# 主界面
col1, col2 = st.columns([3, 2])
with col1:
st.subheader("📝 输入博客主题")
topic = st.text_input(
"请输入主题",
placeholder="例如:Python装饰器详解、Kubernetes入门指南等",
help="输入您想要撰写的博客主题",
label_visibility="collapsed"
)
# ✅ 多选版本
quick_topics = st.multiselect(
"快速选择(可多选):",
["Python异步编程", "React Hooks实战", "Docker容器化", "机器学习基础", "API设计"]
)
if quick_topics:
topic = quick_topics
with col2:
st.subheader("📊 工作流程")
st.code("""
研究员 → 作家 → 编辑
↓ ↓ ↓
调研 撰写 审核
↺ 修订循环
""", language="")
# 生成按钮
generate_btn = st.button(
"🚀 开始生成博客",
type="primary",
use_container_width=True,
disabled=not topic
)
# 生成博客
if generate_btn and topic:
# 确定是否使用模拟模式
use_mock = not bool(st.session_state.openai_key)
if use_mock:
st.info("💡 使用模拟模式生成(未配置 API Key)")
# 初始化状态
initial_state = BlogState(
topic=topic,
research_notes="",
draft="",
final_blog="",
feedback="",
messages=[],
revision_count=0,
use_mock=use_mock
)
# 创建容器
progress_container = st.container()
message_container = st.container()
result_container = st.container()
with progress_container:
progress_bar = st.progress(0, text="初始化...")
status_placeholder = st.empty()
try:
# 创建工作流
app = create_blog_workflow()
# 执行工作流
result = None
step_count = 0
total_steps = 6 # 估计步骤数
with message_container:
st.subheader("📋 执行日志")
log_placeholder = st.empty()
logs = []
for step_result in app.stream(initial_state):
step_count += 1
progress = min(int((step_count / total_steps) * 100), 95)
progress_bar.progress(progress, text=f"执行中... {progress}%")
# 获取当前状态
current_state = list(step_result.values())[0]
# 显示消息
if "messages" in current_state:
new_messages = current_state["messages"]
if new_messages:
logs.extend(new_messages)
log_placeholder.text_area(
"日志",
"\n".join(f"• {msg}" for msg in logs),
height=200,
label_visibility="collapsed"
)
result = current_state
# 完成
progress_bar.progress(100, text="✅ 完成!")
status_placeholder.success("博客生成完成!")
# 显示结果
with result_container:
st.divider()
# 统计信息
col1, col2, col3 = st.columns(3)
with col1:
st.metric("修订次数", result.get("revision_count", 0))
with col2:
st.metric("内容长度", f"{len(result.get('final_blog', ''))} 字符")
with col3:
st.metric("执行步骤", step_count)
# 过程详情
with st.expander("📋 研究笔记", expanded=False):
st.markdown(result.get("research_notes", ""))
if result.get("draft") != result.get("final_blog"):
with st.expander("📄 草稿历史", expanded=False):
st.markdown(result.get("draft", ""))
# 最终博客
st.divider()
st.subheader("📰 最终博客")
final_blog = result.get("final_blog", "")
if final_blog:
# 显示博客
st.markdown(final_blog)
st.divider()
# 操作按钮
col1, col2, col3, col4 = st.columns(4)
if isinstance(topic, list):
topic_str = "_".join(str(x).strip().replace(' ', '_') for x in topic if x)
else:
topic_str = str(topic)
with col1:
st.download_button(
label="📥 下载 MD",
data=final_blog,
file_name=f"{topic_str.replace(' ', '_')}.md",
mime="text/markdown",
use_container_width=True
)
with col2:
export_data = {
"topic": topic,
"content": final_blog,
"metadata": {
"revision_count": result.get("revision_count", 0),
"created_at": datetime.now().isoformat(),
"mode": "mock" if use_mock else "llm"
}
}
st.download_button(
label="📥 下载 JSON",
data=json.dumps(export_data, ensure_ascii=False, indent=2),
file_name=f"{topic_str.replace(' ', '_')}.json",
mime="application/json",
use_container_width=True
)
with col3:
if st.button("📋 复制内容", use_container_width=True):
st.code(final_blog, language="markdown")
with col4:
if st.button("🔄 重新生成", use_container_width=True):
st.rerun()
else:
st.error("生成失败,请重试")
except Exception as e:
st.error(f"❌ 生成过程出错: {str(e)}")
with st.expander("查看错误详情"):
st.exception(e)
elif generate_btn:
st.warning("⚠️ 请先输入博客主题")
# 底部信息
st.divider()
col1, col2 = st.columns(2)
with col1:
st.markdown("""
### 💡 使用提示
- 输入明确具体的主题
- 配置 API Key 获得更好效果
- 查看执行日志了解过程
- 支持多种格式下载
""")
with col2:
st.markdown("""
### 🔧 技术栈
- **LangGraph**: Agent 协作框架
- **Streamlit**: 交互界面
- **OpenAI**: 大语言模型
- **Python**: 核心开发语言
""")
if __name__ == "__main__":
main() |