文档:https://microsoft.github.io/autogen/stable/user-guide/agentchat-user-guide/quickstart.html
项目:https://github.com/microsoft/autogen
pip install -U "autogen-agentchat" "autogen-ext[openai]"
pip install -U "autogenstudio"
使用自定义模型方法(需兼容openai api)
python
from autogen_ext.models.openai import OpenAIChatCompletionClient
from autogen_core.models import ModelFamily, ModelInfo
# 创建自定义模型信息
qwen_model_info = ModelInfo(
vision=False,
function_calling=True,
json_output=True,
family=ModelFamily.UNKNOWN
)
model_client = OpenAIChatCompletionClient(
model="Qwen/Qwen2.5-7B-Instruct-1M",
model_info=qwen_model_info,
context_window=8192, # 根据实际模型上下文窗口大小调整
temperature=0.5,
top_p=0.7
)
使用ollama模型
uv pip install "autogen-ext[ollama]"
python
model_info = ModelInfo(
vision=False,
function_calling=True,
json_output=True,
structured_output=True,
family=ModelFamily.UNKNOWN
)
# 创建模型客户端
model_client = OpenAIChatCompletionClient(
model="qwen3:32b",
temperature=0.4,
model_info=model_info,
top_p=0.7,
)
多个agent一轮任务示范使用openai
python
import asyncio # 引入asyncio模块,用来运行异步函数
from autogen_agentchat.agents import AssistantAgent
from autogen_agentchat.conditions import TextMentionTermination
from autogen_agentchat.teams import RoundRobinGroupChat
from autogen_agentchat.ui import Console
from autogen_ext.models.openai import OpenAIChatCompletionClient
import os
# 设置OpenAI API密钥,这是访问OpenAI服务的必要凭证
os.environ["OPENAI_API_KEY"] = "sk-6LsZB5NLIfnIfrb30tPCRUBOh61sdRkiFRH500ukWf9hEl6w"
# 设置自定义的API基础URL,将请求重定向到指定服务器
os.environ["OPENAI_BASE_URL"] = "https://newapi.ipzu.com/v1"
async def main():
# 创建模型客户端
model_client = OpenAIChatCompletionClient(model="gpt-4o-mini")
# 创建规划助手
planner_agent = AssistantAgent(
"planner_agent",
model_client=model_client,
description="一个能够帮助用户制定旅行计划的助手。",
system_message="你是一个乐于助人的助手,可以根据用户的要求制定旅行计划。",
)
# 创建本地活动推荐助手
local_agent = AssistantAgent(
"local_agent",
model_client=model_client,
description="一个可以为用户推荐当地活动或旅游景点的助手。",
system_message="你是一个乐于助人的助手,可以根据提供的背景信息,为用户推荐地道且有趣的当地活动或旅游景点。",
)
# 创建语言提示助手
language_agent = AssistantAgent(
"language_agent",
model_client=model_client,
description="一个能够为特定目的地提供语言沟通建议的助手。",
system_message="你是一个乐于助人的助手,能够审阅旅行计划,并提供与目的地语言或沟通挑战相关的重要提示。如果计划中已经包含了语言提示,可以说明计划已妥当,并给出理由。",
)
# 创建旅行总结助手
travel_summary_agent = AssistantAgent(
"travel_summary_agent",
model_client=model_client,
description="一个能够整合所有建议,生成完整旅行计划的助手。",
system_message="你是一个乐于助人的助手,能够综合所有其他助手的建议,给出详细、完整的最终旅行计划。你的最终回复必须是完整的计划。当计划整合完毕后,请以 TERMINATE 作为结束。",
)
# 设置终止条件,当任一回复中提到 TERMINATE 时结束对话
termination = TextMentionTermination("TERMINATE")
# 创建轮流对话组,依次由各助手交互
group_chat = RoundRobinGroupChat(
[planner_agent, local_agent, language_agent, travel_summary_agent],
termination_condition=termination
)
# 在控制台运行交互,任务是制定尼泊尔3日游的计划
await Console(group_chat.run_stream(task="制定一个尼泊尔的3日旅行计划。"))
# 关闭模型客户端
await model_client.close()
# 运行异步主函数
asyncio.run(main())
2轮公屏对话示范
python
"""
基于AutoGen的聊天直播多角色对话模拟
场景:在聊天直播中模拟多个角色对话,所有人的对话都会出现在公屏上大家都能看到
然后某些角色根据自身判断是否响应发送信息
"""
import asyncio
import os
from autogen_agentchat.agents import AssistantAgent
from autogen_ext.models.openai import OpenAIChatCompletionClient
# 设置OpenAI API密钥,这是访问OpenAI服务的必要凭证
os.environ["OPENAI_API_KEY"] = "sk-6LsZB5NLIfnIfrb30tPCRUBOh61sdRkiFRH500ukWf9hEl6w"
# 设置自定义的API基础URL,将请求重定向到指定服务器
os.environ["OPENAI_BASE_URL"] = "https://newapi.ipzu.com/v1"
# 辅助函数:从TaskResult对象中提取文本内容
def extract_message_content(task_result):
"""从TaskResult对象中提取实际的消息内容"""
# 如果已经是字符串,直接返回
if isinstance(task_result, str):
return task_result
try:
# 尝试获取最后一条消息的内容
if hasattr(task_result, 'messages') and task_result.messages:
last_message = task_result.messages[-1]
if hasattr(last_message, 'content'):
return last_message.content
# 如果上面的方法失败,尝试将整个对象转为字符串并提取有用部分
result_str = str(task_result)
if 'content=' in result_str:
parts = result_str.split('content=')
if len(parts) > 1:
content_part = parts[-1].strip("'").strip('"')
# 如果内容后面还有其他属性,只取到下一个属性之前
if ', ' in content_part:
content_part = content_part.split(', ')[0]
return content_part.strip("'").strip('"')
except Exception as e:
print(f"提取消息内容时出错: {str(e)}")
# 如果无法提取,返回原始对象的字符串表示
return f"(无法解析的消息: {str(task_result)})"
# 定义一个简单的多角色聊天模拟器
async def simulate_chat():
# 定义所有角色的模型客户端
model_client = OpenAIChatCompletionClient(
model="gpt-4o-mini",
api_key=os.environ.get("OPENAI_API_KEY"),
)
# 定义角色名称的映射(API名称 -> 显示名称)
role_names = {
"game_host": "游戏主播",
"game_expert": "游戏专家",
"funny_viewer": "搞笑观众",
"game_critic": "游戏评论家"
}
# 创建角色 - 使用英文名称作为API名称
host = AssistantAgent(
name="game_host",
system_message="""你是一名受欢迎的游戏主播,性格开朗活泼,语气充满活力。
你负责引导话题,回应观众和嘉宾的发言。
使用emoji表情和网络流行语,但不要过度。
你的回应简短有趣,通常在50字以内。
只在聊天内容与你相关或需要你回应时发言,不要回应每一条消息。""",
model_client=model_client,
)
expert = AssistantAgent(
name="game_expert",
system_message="""你是一位资深游戏专家,只在谈论游戏机制、攻略或游戏历史时发言。
你的语气冷静专业,喜欢分析游戏设计和玩法细节。
不参与与游戏无关的闲聊。
回应应简洁但内容丰富,通常在100字以内。
只在讨论游戏技术内容时发言,不要回应每一条消息。""",
model_client=model_client,
)
funny = AssistantAgent(
name="funny_viewer",
system_message="""你是直播间的搞笑观众,总是发表幽默的评论和笑话。
你会对任何严肃的讨论进行调侃,经常使用夸张的表达和自嘲。
你的回应非常简短,通常在30字以内,充满网络流行语和表情符号。
你不会回应每条消息,只在有机会插入笑点时发言。""",
model_client=model_client,
)
critic = AssistantAgent(
name="game_critic",
system_message="""你是一位尖锐的游戏评论家,只在讨论游戏评价、行业问题或游戏缺点时发言。
你的语气犀利直接,喜欢指出问题和提出批评。
不参与纯粹的娱乐性闲聊。
回应应简洁明了,通常在80字以内。
只在讨论游戏问题或有批评机会时发言,不要回应每一条消息。""",
model_client=model_client,
)
# 角色列表
roles = {
"game_host": host,
"game_expert": expert,
"funny_viewer": funny,
"game_critic": critic
}
# 主持人发起的初始消息
initial_message = """
大家好!欢迎来到今天的游戏直播间!今天我们将一起讨论最近非常火爆的《艾尔登法环》游戏。
这款游戏有着极高的难度和开放世界设计,有人说它是近年来最好的RPG游戏,也有人觉得它太难了根本不适合普通玩家。
大家对这款游戏有什么看法呢?来聊聊吧!
"""
print("主持人:", initial_message)
# 模拟每个角色的回应
try:
# 存储所有回应
responses = {}
# 第一轮对话:所有角色对初始消息的回应
for role_id, agent in roles.items():
display_name = role_names[role_id]
try:
task_result = await agent.run(task=f"根据这条消息,作为{display_name}回应: {initial_message}")
# 提取消息内容
message_content = extract_message_content(task_result)
print(f"{display_name}: {message_content}")
responses[role_id] = message_content
except Exception as e:
print(f"角色 {display_name} 回应时出错: {str(e)}")
responses[role_id] = "(无法回应)"
# 模拟第二轮对话
second_prompt = """
关于《艾尔登法环》的难度问题,有人说这种高难度是FromSoftware工作室一贯的风格,也是他们游戏的魅力所在。
但也有玩家表示,游戏缺乏难度选项让很多休闲玩家感到挫折。大家觉得游戏应该坚持自己的特色还是应该照顾更多玩家?
"""
print("\n主持人:", second_prompt)
# 构建所有对话历史
all_messages = f"""
主持人: {initial_message}
{role_names['game_host']}: {responses['game_host']}
{role_names['game_expert']}: {responses['game_expert']}
{role_names['funny_viewer']}: {responses['funny_viewer']}
{role_names['game_critic']}: {responses['game_critic']}
主持人: {second_prompt}
"""
# 第二轮对话:所有角色对第二个主持人提问的回应
for role_id, agent in roles.items():
display_name = role_names[role_id]
try:
task_result = await agent.run(task=f"根据这些消息记录,作为{display_name}回应最后一条主持人的提问: {all_messages}")
# 提取消息内容
message_content = extract_message_content(task_result)
print(f"{display_name}: {message_content}")
except Exception as e:
print(f"角色 {display_name} 回应时出错: {str(e)}")
except Exception as e:
print(f"发生错误: {str(e)}")
finally:
# 关闭模型客户端
await model_client.close()
# 主函数
async def main():
await simulate_chat()
if __name__ == "__main__":
asyncio.run(main())
根据某话题一直讨论下去
python
import asyncio
import os
from autogen_agentchat.agents import AssistantAgent
from autogen_agentchat.conditions import TextMentionTermination
from autogen_agentchat.teams import RoundRobinGroupChat
from autogen_agentchat.ui import Console
from autogen_ext.models.openai import OpenAIChatCompletionClient
# 设置OpenAI API密钥,这是访问OpenAI服务的必要凭证
os.environ["OPENAI_API_KEY"] = "sk-6LsZB5NLIfnIfrb30tPCRUBOh61sdRkiFRH500ukWf9hEl6w"
# 设置自定义的API基础URL,将请求重定向到指定服务器
os.environ["OPENAI_BASE_URL"] = "https://newapi.ipzu.com/v1"
async def main():
# 创建模型客户端
model_client = OpenAIChatCompletionClient(model="gpt-4o-mini")
# 创建游戏主播角色
host_agent = AssistantAgent(
name="game_host",
model_client=model_client,
description="游戏直播间的主播,开朗活泼,语气充满活力",
system_message="""你是一名受欢迎的游戏主播,性格开朗活泼,语气充满活力。
你负责引导话题,回应观众和嘉宾的发言。
使用emoji表情和网络流行语,但不要过度。
你的回应简短有趣,通常在50字以内。
只在聊天内容与你相关或需要你回应时发言。""",
)
# 创建游戏专家角色
expert_agent = AssistantAgent(
name="game_expert",
model_client=model_client,
description="游戏资深专家,冷静专业,擅长分析游戏机制",
system_message="""你是一位资深游戏专家,只在谈论游戏机制、攻略或游戏历史时发言。
你的语气冷静专业,喜欢分析游戏设计和玩法细节。
不参与与游戏无关的闲聊。
回应应简洁但内容丰富,通常在100字以内。
只在讨论游戏技术内容时发言。""",
)
# 创建搞笑观众角色
funny_agent = AssistantAgent(
name="funny_viewer",
model_client=model_client,
description="直播间的搞笑观众,总是发表幽默的评论和笑话",
system_message="""你是直播间的搞笑观众,总是发表幽默的评论和笑话。
你会对任何严肃的讨论进行调侃,经常使用夸张的表达和自嘲。
你的回应非常简短,通常在30字以内,充满网络流行语和表情符号。
你不会回应每条消息,只在有机会插入笑点时发言。""",
)
# 创建游戏评论家角色
critic_agent = AssistantAgent(
name="game_critic",
model_client=model_client,
description="尖锐的游戏评论家,喜欢指出问题和提出批评",
system_message="""你是一位尖锐的游戏评论家,只在讨论游戏评价、行业问题或游戏缺点时发言。
你的语气犀利直接,喜欢指出问题和提出批评。
不参与纯粹的娱乐性闲聊。
回应应简洁明了,通常在80字以内。
只在讨论游戏问题或有批评机会时发言。""",
)
# 创建主持人角色,用于引导讨论
moderator_agent = AssistantAgent(
name="moderator",
model_client=model_client,
description="聊天直播间的主持人,负责引导讨论并提出话题",
system_message="""你是聊天直播间的主持人,负责引导讨论和提出问题。
你的语气专业而友好,能够引导话题的进行并鼓励其他角色参与讨论。
提问应该简短明了,每次只提一个问题。
你的回应一般在50字以内。
在聊天持续一段时间后,你可以发送包含TERMINATE的消息结束讨论。""",
)
# 设置终止条件,当任一回复中提到TERMINATE时结束对话
termination = TextMentionTermination("TERMINATE")
# 创建轮流对话组,所有角色轮流发言
group_chat = RoundRobinGroupChat(
[moderator_agent, host_agent, expert_agent, funny_agent, critic_agent],
termination_condition=termination
)
# 游戏讨论主题
initial_task = """
大家好!欢迎来到今天的游戏直播间!今天我们将一起讨论最近非常火爆的《艾尔登法环》游戏。
这款游戏有着极高的难度和开放世界设计,有人说它是近年来最好的RPG游戏,也有人觉得它太难了根本不适合普通玩家。
大家对这款游戏有什么看法呢?来聊聊吧!
我们可以从游戏的难度系统谈起,你们觉得高难度是这类游戏的魅力所在,还是应该提供更多难度选项?
"""
# 在控制台运行交互,实时显示生成过程
await Console(group_chat.run_stream(task=initial_task))
# 关闭模型客户端
await model_client.close()
# 运行异步主函数
if __name__ == "__main__":
asyncio.run(main())
模拟聊天室简易demo
templates/chatroom.html前端:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI聊天直播间</title>
<style>
body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
#chatbox { height: 500px; overflow-y: scroll; border: 1px solid #ddd; padding: 10px; margin-bottom: 15px; background-color: #f9f9f9; }
#prompt-box { width: 100%; padding: 10px; border: 1px solid #ddd; margin-bottom: 10px; }
.message { margin: 8px 0; padding: 8px; border-radius: 4px; }
/* 系统消息样式 */
.system { color: #2ecc71; padding: 5px; font-size: 0.8em;}
/* 通知消息样式 */
.notification { color: #7f8c8d; padding: 5px; font-size: 0.8em;}
/* 气泡样式 */
.chat-bubble {
position: relative;
padding: 10px 15px;
border-radius: 15px;
margin: 10px 0;
max-width: 80%;
box-shadow: 0 1px 2px rgba(0,0,0,0.1);
background-color: rgb(230, 230, 230);
}
/* 不同角色的气泡颜色 */
.game-host { color: white; background-color: #e74c3c; margin-left: auto; }
.game-expert { color: white; background-color: #3498db; margin-left: auto; }
.funny-viewer { color: white; background-color: #f39c12; margin-left: auto; }
.game-critic { color: white; background-color: #9b59b6; margin-left: auto; }
.user { color: white; background-color: #7f8c8d; margin-right: auto; }
.control-panel { margin-bottom: 15px; }
.btn { padding: 10px 20px; margin-right: 10px; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; }
.btn-start { background-color: #2ecc71; color: white; }
.btn-stop { background-color: #e74c3c; color: white; }
h2 { color: #34495e; }
.label { font-weight: bold; margin-bottom: 5px; display: block; }
/* 发送者名称 */
.sender-name {
font-weight: bold;
margin-bottom: 5px;
font-size: 0.9em;
}
</style>
</head>
<body>
<h2>AI聊天直播间 - 游戏讨论</h2>
<div class="control-panel">
<button id="start-btn" class="btn btn-start" onclick="startChat()">开始聊天</button>
<button id="stop-btn" class="btn btn-stop" onclick="stopChat()" disabled>停止聊天</button>
</div>
<div id="chatbox"></div>
<div>
<label for="prompt-box" class="label">管理员系统提示 (引导聊天方向):</label>
<textarea id="prompt-box" rows="3" placeholder="输入系统提示来引导话题方向..."></textarea>
<button class="btn btn-start" onclick="sendSystemPrompt()">发送系统提示</button>
</div>
<script>
// 获取当前主机和端口
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl = `${protocol}//${window.location.host}/ws`;
const ws = new WebSocket(wsUrl);
// 添加回车键发送功能
document.getElementById("prompt-box").addEventListener("keydown", function(event) {
// 检查是否按下的是回车键且没有按住Shift键
if (event.key === "Enter" && !event.shiftKey) {
event.preventDefault(); // 阻止默认的换行行为
sendSystemPrompt(); // 发送消息
}
});
ws.onmessage = function(event) {
const data = JSON.parse(event.data);
const chatbox = document.getElementById("chatbox");
const message = document.createElement("div");
// 根据消息类型设置不同的样式
if (data.type === "system") {
// 系统消息,使用居中浅绿色文本
message.className = "system";
message.textContent = `系统提示: ${data.content}`;
} else if (data.type === "notification") {
// 通知消息,使用灰色文本
message.className = "notification";
message.textContent = data.content;
} else if (data.type === "chat") {
// 聊天消息,使用气泡样式
message.className = "chat-bubble";
// 为不同角色添加不同的样式
if (data.sender) {
const senderClass = data.sender.replace(/ /g, "-").toLowerCase();
message.classList.add(senderClass);
// 添加发送者名称
const senderName = document.createElement("div");
senderName.className = "sender-name";
senderName.textContent = data.sender;
message.appendChild(senderName);
} else {
message.classList.add("user");
}
// 添加消息内容
const contentElem = document.createElement("div");
contentElem.textContent = data.content;
message.appendChild(contentElem);
} else {
// 其他类型的消息
message.className = "message";
message.textContent = data.content;
}
chatbox.appendChild(message);
chatbox.scrollTop = chatbox.scrollHeight;
};
function startChat() {
ws.send(JSON.stringify({command: "start"}));
document.getElementById("start-btn").disabled = true;
document.getElementById("stop-btn").disabled = false;
}
function stopChat() {
ws.send(JSON.stringify({command: "stop"}));
document.getElementById("start-btn").disabled = false;
document.getElementById("stop-btn").disabled = true;
}
function sendSystemPrompt() {
const promptBox = document.getElementById("prompt-box");
const prompt = promptBox.value.trim();
if (prompt) {
ws.send(JSON.stringify({
command: "system",
content: prompt
}));
promptBox.value = "";
}
}
</script>
</body>
</html>
python
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse
import traceback
import asyncio
import json
import os
import re
import random
from autogen_agentchat.agents import AssistantAgent
from autogen_ext.models.openai import OpenAIChatCompletionClient
from autogen_core.models import ModelFamily, ModelInfo
# 设置OpenAI API密钥和基础URL
os.environ["OPENAI_API_KEY"] = "sk-6LsZB5NLIfnIfrb30tPCRUBOh61sdRkiFRH500ukWf9hEl6w"
# os.environ["OPENAI_BASE_URL"] = "https://newapi.ipzu.com/v1"
os.environ["OPENAI_BASE_URL"] = "http://192.168.1.163:8000/v1"
# 创建FastAPI实例
app = FastAPI()
# WebSocket连接列表
connections: list[WebSocket] = []
# 聊天状态
chat_active = False
chat_task = None
# 聊天历史记录,这是前端UI的显示,实际的上下文在current_context中
chat_history: list[dict[str, any]] = []
# 创建自定义模型信息
qwen_model_info = ModelInfo(
vision=False, function_calling=True, json_output=True, family=ModelFamily.UNKNOWN
)
# 创建模型客户端
model_client = OpenAIChatCompletionClient(
model="Qwen/Qwen2.5-7B-Instruct-1M",
model_info=qwen_model_info,
context_window=8192, # 根据实际模型上下文窗口大小调整
temperature=0.4,
top_p=0.7,
)
# 创建一个核验模型是否抽风的函数
def check_model_stability(text: str) -> bool:
"""
检查模型是否抽风,如果通过测试则返回True,否则返回False
"""
if not text:
print("模型产生空文本")
return False # 空文本不通过
# 1. 检测连续空白或特殊空白字符过长
if re.search(r"[\s︊]{%d,}" % 100, text):
print(f"模型产生连续空白或特殊空白字符过长:{text}")
return False
# 正则表达式,检查是否存在重复6轮的相同内容
pattern = re.compile(r"(.{1,10}?)(?:\1){" + str(6) + r",}")
if pattern.search(text):
# 如果存在,则认为模型抽风了
print(f"模型抽风循环:{text}")
return False
# 2. 整体空白比例过高
blank_count = sum(1 for c in text if c.isspace() or c == "︊")
if blank_count / len(text) > 0.3:
print(f"模型产生大量空白:{text}")
return False
# 如果以上都没有问题,则认为模型正常
return True
# 创建角色
def create_agents():
# 创建游戏主播角色
host_agent = AssistantAgent(
name="game_host",
model_client=model_client,
description="游戏直播间的主播,开朗活泼,语气充满活力",
system_message="""你是一名受欢迎的游戏主播,性格开朗活泼,语气充满活力。
你负责引导话题,回应观众和嘉宾的发言。
使用emoji表情和网络流行语,但不要过度。
你的回应简短有趣,通常在50字以内。
只在聊天内容与你相关或需要你回应时发言。""",
)
# 创建游戏专家角色
expert_agent = AssistantAgent(
name="game_expert",
model_client=model_client,
description="游戏资深专家,冷静专业,擅长分析游戏机制",
system_message="""你是一位资深游戏专家,只在谈论游戏机制、攻略或游戏历史时发言。
你的语气冷静专业,喜欢分析游戏设计和玩法细节。
不参与与游戏无关的闲聊。
回应应简洁但内容丰富,通常在100字以内。
只在讨论游戏技术内容时发言。""",
)
# 创建搞笑观众角色
funny_agent = AssistantAgent(
name="funny_viewer",
model_client=model_client,
description="直播间的搞笑观众,总是发表幽默的评论和笑话",
system_message="""你是直播间的搞笑观众,总是发表幽默的评论和笑话。
你会对任何严肃的讨论进行调侃,经常使用夸张的表达和自嘲。
你的回应非常简短,通常在30字以内,充满网络流行语和表情符号。
你不会回应每条消息,只在有机会插入笑点时发言。""",
)
# 创建游戏评论家角色
critic_agent = AssistantAgent(
name="game_critic",
model_client=model_client,
description="尖锐的游戏评论家,喜欢指出问题和提出批评",
system_message="""你是一位尖锐的游戏评论家,只在讨论游戏评价、行业问题或游戏缺点时发言。
你的语气犀利直接,喜欢指出问题和提出批评。
不参与纯粹的娱乐性闲聊。
回应应简洁明了,通常在80字以内。
只在讨论游戏问题或有批评机会时发言。""",
)
return {
"game_host": host_agent,
"game_expert": expert_agent,
"funny_viewer": funny_agent,
"game_critic": critic_agent,
}
# 创建角色字典
agents = create_agents()
# 初始话题
default_topic = """
大家好!欢迎来到今天的游戏直播间!今天我们将一起讨论最近非常火爆的《德州扑克》游戏。
大家对这款游戏有什么看法呢?来聊聊吧!
"""
# 当前系统提示
current_system_prompt = default_topic
# 向所有连接的客户端广播消息
async def broadcast_message(message: dict[str, any]):
if not connections:
return
for connection in connections:
try:
await connection.send_text(json.dumps(message))
except Exception as e:
print(f"Error broadcasting message: {e}")
# 根路径返回聊天室页面
@app.get("/")
async def read_root():
return FileResponse("templates/chatroom.html")
# 处理WebSocket连接
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
connections.append(websocket)
# 发送历史消息记录
for message in chat_history:
await websocket.send_text(json.dumps(message))
try:
while True:
# 接收客户端消息
data = await websocket.receive_text()
try:
# 解析JSON数据
command_data = json.loads(data)
command = command_data.get("command", "")
if command == "start":
# 开始聊天
await start_chat()
elif command == "stop":
# 停止聊天
await stop_chat()
elif command == "system":
# 设置系统提示
content = command_data.get("content", "")
if content:
await set_system_prompt(content)
else:
# 未知命令,当作普通消息处理
user_message = {"type": "chat", "sender": "User", "content": data}
chat_history.append(user_message)
await broadcast_message(user_message)
except json.JSONDecodeError:
# 不是JSON格式,当作普通消息处理
user_message = {"type": "chat", "sender": "User", "content": data}
chat_history.append(user_message)
await broadcast_message(user_message)
except WebSocketDisconnect:
connections.remove(websocket)
# 设置系统提示
async def set_system_prompt(prompt: str):
global current_system_prompt, chat_history
current_system_prompt = prompt
# 移除之前的所有系统消息
chat_history = [msg for msg in chat_history if msg.get("type") != "system"]
# 创建系统提示消息
system_message = {"type": "system", "content": prompt}
# 添加到历史记录并广播
chat_history.append(system_message)
await broadcast_message(system_message)
# 如果聊天已经激活,则静默停止当前聊天并用新的系统提示重新开始
if chat_active:
await silent_stop_chat()
await asyncio.sleep(1) # 等待一秒,确保聊天已完全停止
await silent_start_chat()
# 启动聊天(带通知)
async def start_chat():
global chat_active, chat_task, chat_history
if chat_active:
return
chat_active = True
# 移除之前的所有系统消息
chat_history = [msg for msg in chat_history if msg.get("type") != "system"]
# 发送初始系统提示到聊天界面
system_message = {"type": "system", "content": current_system_prompt}
chat_history.append(system_message)
await broadcast_message(system_message)
# 创建异步任务
chat_task = asyncio.create_task(run_chat_loop())
# 发送通知
notification = {"type": "notification", "content": "AI开始..."}
await broadcast_message(notification)
# 静默启动聊天(不发送通知)
async def silent_start_chat():
global chat_active, chat_task, chat_history
if chat_active:
return
chat_active = True
# 创建异步任务
chat_task = asyncio.create_task(run_chat_loop())
# 停止聊天(带通知)
async def stop_chat():
global chat_active, chat_task
if not chat_active:
return
chat_active = False
if chat_task:
chat_task.cancel()
chat_task = None
# 发送通知
notification = {"type": "notification", "content": "AI停止"}
await broadcast_message(notification)
# 静默停止聊天(不发送通知)
async def silent_stop_chat():
global chat_active, chat_task
if not chat_active:
return
chat_active = False
if chat_task:
chat_task.cancel()
chat_task = None
# 角色回合制聊天循环
async def run_chat_loop():
agent_keys = list(agents.keys())
# 确保使用最新的系统提示
current_context = ""
try:
# 主循环
turn = 0
while chat_active:
# 角色轮流发言
for agent_key in agent_keys:
if not chat_active:
break
try:
# 让特定角色思考并回复
# 概率性回应
if should_respond():
print("开始请求一次ai:")
try:
# 显式提醒AI注意系统提示中的话题
agent_response = await agents[agent_key].run(
task=f"""根据以下系统提示和对话历史,作为{get_agent_display_name(agent_key)}回应:
系统提示: {current_system_prompt}
请严格遵循系统提示中指定的话题进行回应。只生成纯文本内容,不要超过60字符,不要使用任何Markdown、HTML或排版格式。
对话历史:
{current_context}"""
)
# 先提取文本内容
content = extract_message_content(agent_response)
# 对提取的文本内容进行稳定性检查
if check_model_stability(content):
# 随机等待2-4秒
await asyncio.sleep(random.uniform(2, 4))
await send_agent_message(agent_key, content)
current_context += f"\n\n{get_agent_display_name(agent_key)}: {content}"
# 如果超过3000字符,始终只取最后3000字符
current_context = current_context[-3000:]
except Exception as e:
print(f"出现错误: {agent_key}: {e}")
import traceback
print(f"详细错误信息: {traceback.format_exc()}")
except Exception as e:
print(f"出现错误 {agent_key}: {e}")
print(f"详细错误信息: {traceback.format_exc()}") # 打印完整堆栈跟踪
turn += 1
except asyncio.CancelledError:
print("循环聊天已取消")
except Exception as e:
print(f"聊天循环出错: {e}")
# 发送角色消息
async def send_agent_message(agent_key: str, content: str):
display_name = get_agent_display_name(agent_key)
message = {"type": "chat", "sender": display_name, "content": content}
chat_history.append(message)
await broadcast_message(message)
# 辅助函数:获取角色显示名称
def get_agent_display_name(agent_key: str) -> str:
display_names = {
"game_host": "游戏主播",
"game_expert": "游戏专家",
"funny_viewer": "搞笑观众",
"game_critic": "游戏评论家",
}
return display_names.get(agent_key, agent_key)
# 辅助函数:随机决定是否回应
def should_respond() -> bool:
import random
return random.random() < 0.1 # 10%的概率回应
# 辅助函数:从TaskResult对象中提取文本内容
def extract_message_content(task_result):
"""从TaskResult对象中提取实际的消息内容"""
# 如果已经是字符串,直接返回
if isinstance(task_result, str):
return task_result
try:
# 尝试获取最后一条消息的内容
if hasattr(task_result, "messages") and task_result.messages:
last_message = task_result.messages[-1]
if hasattr(last_message, "content"):
return last_message.content
# 如果上面的方法失败,尝试将整个对象转为字符串并提取有用部分
result_str = str(task_result)
if "content=" in result_str:
parts = result_str.split("content=")
if len(parts) > 1:
content_part = parts[-1].strip("'").strip('"')
# 如果内容后面还有其他属性,只取到下一个属性之前
if ", " in content_part:
content_part = content_part.split(", ")[0]
return content_part.strip("'").strip('"')
except Exception as e:
print(f"提取消息内容时出错: {str(e)}")
# 如果无法提取,返回原始对象的字符串表示
return f"(无法解析的消息: {str(task_result)})"
# 将静态文件挂载在/static路径上,而不是根路径
app.mount("/static", StaticFiles(directory="templates"), name="static")
# 启动服务器
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)