Coze智能体实现人生模拟器

在上篇文章:

一系列带你学会Coze------Coze智能体开发https://blog.csdn.net/sniper_fandc/article/details/154959322?fromshare=blogdetail&sharetype=blogdetail&sharerId=154959322&sharerefer=PC&sharesource=sniper_fandc&sharefrom=from_link 我们已经学习了Coze智能体的开发和相关API、SDK介绍,本文将使用Python的Coze SDK实现一个人生模拟器。

1 智能体开发

创建智能体,添加系统提示词:

复制代码
# 角色
你是"人生三万天",一个人生模拟器,能让用户体验不同的人生经历。你可以生动且富有代入感地为用户描述各种人生场景、挑战和机遇,用通俗易懂且富有感染力的语言让用户仿佛置身其中。

## 技能
### 技能 1: 开启人生模拟
1. 当用户要求开启人生模拟时,先询问用户希望模拟的人生主题方向,例如职场、情感、冒险等。如果用户已经指定主题,则直接进入下一步。
2. 根据用户选择的主题方向,开始模拟人生经历。描述人生的起点场景,包括人物设定、所处环境等基本信息。
===回复示例===
你开启了一段职场人生模拟。你叫[名字],22 岁,刚刚大学毕业,所学专业是[专业名称],怀揣着对未来的憧憬,你来到了[城市名称],准备在这个竞争激烈的职场中闯出一片天地。今天是你去[公司名称]面试的日子......
===示例结束===

### 技能 2: 推进人生模拟
1. 当用户要求推进模拟人生时,根据当前模拟人生的进展和场景,合理生成新的事件和发展。考虑到不同人生主题可能面临的各种情况,如职场中的晋升挑战、情感上的起伏、冒险中的意外等。
2. 在描述新事件时,要提供足够的细节,让用户感受到真实的人生体验。
===回复示例===
在你努力工作了几个月后,公司迎来了一个重要项目。领导决定让你负责其中一个关键部分,但时间紧迫,资源有限,你需要在一周内完成[具体任务]。这是一个提升自己的好机会,但同时也充满了挑战......
===示例结束===

### 技能 3: 提供人生选择
1. 在模拟人生过程中,遇到关键节点时,为用户提供不同的选择,并简要说明每个选择可能带来的后果。
2. 根据用户选择的方向,继续推进模拟人生。
===回复示例===
面对这个项目挑战,你有两个选择:
- 选择 A:向领导申请增加人手和资源,但可能会被认为能力不足,依赖他人。如果选择 A,接下来团队成员加入,项目进度可能加快,但人际关系方面可能出现一些小摩擦。
- 选择 B:独自承担,凭借自己的能力加班完成。如果选择 B,你可能会压力巨大,但成功完成后会得到领导的高度认可,未来晋升机会大增。
请做出你的选择。
===示例结束===

## 限制:
- 只讨论与人生模拟相关的内容,拒绝回答与人生模拟无关的话题。
- 所输出的内容必须按照给定的格式进行组织,不能偏离框架要求。
- 描述人生场景和事件时尽量简洁明了,避免过于冗长复杂的表述。
- 回答需基于合理的逻辑和对不同人生场景的理解,不能出现不合理的情节。 
- 整个游戏流程不超过10轮,可以出现选择某个选项后因为一些原因而导致人生体验结束。

模型配置如下:

注意:人生模拟器采用多轮对话的形式,每轮对话内容都与上轮选择相关,为了保证所有轮数的对话保持高相关性,建议将上下文轮数调多一点。

添加开场白:

发布智能体,注意勾选API和SDK:

保存智能体的id:url的bot/后的数字。

2 Python调用SDK

python 复制代码
import os
from cozepy import Coze, TokenAuth, ChatStatus, COZE_CN_BASE_URL, Message
from dotenv import load_dotenv
from flask import Flask, request, jsonify, send_file
from flask_cors import CORS
import uuid

# 将.env文件的变量加载到环境变量中
load_dotenv()

# 创建flask应用实例(web应用)
app = Flask(__name__)

# 允许跨域请求
CORS(app, resources={r"/*": {"origins": "*"}})

# 用户会话id - 使用字典存储会话
user_sessions = {}


class CozeService:
    def __init__(self):
        self.api_token = os.getenv("COZE_API_TOKEN")
        self.bot_id = os.getenv("BOT_ID", "7539845644169986098")
        self.coze = Coze(
            auth=TokenAuth(token=self.api_token),
            base_url=COZE_CN_BASE_URL
        )

    def get_ai_response(self, user_message, user_identifier):
        """获取智能体响应"""
        try:
            # 构建消息
            messages = [
                Message(
                    role="user",
                    content=user_message,
                    content_type="text",
                    type="question"
                )
            ]

            # 用户唯一标识在user_identifier用户会话id中: 使用该会话id继续聊天
            if user_identifier in user_sessions:
                session_data = user_sessions[user_identifier]
                conversation_id = session_data["conversation_id"]
                user_id = session_data["user_id"]

                # 使用会话id进行对话
                chat = self.coze.chat.create(
                    bot_id=self.bot_id,
                    user_id=user_id,
                    conversation_id=conversation_id,
                    additional_messages=messages,
                    auto_save_history=True
                )
            # 用户唯一标识不在user_identifier用户会话id中: 创建新会话
            else:
                # 生成唯一用户ID
                new_user_id = f"user_{uuid.uuid4().hex[:8]}"

                # 创建新对话
                chat = self.coze.chat.create(
                    bot_id=self.bot_id,
                    user_id=new_user_id,
                    additional_messages=messages,
                    auto_save_history=True
                )
                # 缓存用户会话
                user_sessions[user_identifier] = {
                    "conversation_id": chat.conversation_id,
                    "user_id": new_user_id,
                    "chat_id": chat.id
                }

            # 轮询等待完成
            while chat.status == ChatStatus.IN_PROGRESS:
                chat = self.coze.chat.retrieve(
                    conversation_id=chat.conversation_id,
                    chat_id=chat.id
                )

            # 获取结果
            if chat.status == ChatStatus.COMPLETED:
                messages = self.coze.chat.messages.list(
                    conversation_id=chat.conversation_id,
                    chat_id=chat.id
                )
                for msg in messages:
                    if msg.role == "assistant":
                        return {"status": "success", "content": msg.content}

            return {"status": "failed", "content": "对话未完成"}

        except Exception as e:
            return {"status": "error", "content": str(e)}


coze_service = CozeService()


# 根路径指向index.html 页面
@app.route("/")
def index():
    restart_session()
    return send_file('index.html')


# 重启会话接口
@app.route("/restart", methods=['POST'])
def restart_session():
    # 获取客户端标识
    user_identifier = request.remote_addr

    # 删除现有会话
    if user_identifier in user_sessions:
        del user_sessions[user_identifier]

    return jsonify({"status": "success", "message": "会话已重置"})


# 对话路由的接口
@app.route("/chat", methods=['POST'])
def chat():
    # 获取请求参数
    data = request.json
    user_message = data["message"]

    # 用户唯一标识使用客户端IP
    user_identifier = request.remote_addr

    result = coze_service.get_ai_response(user_message, user_identifier)
    return jsonify(result)


if __name__ == "__main__":
    app.run(debug=True, port=5000)

上述SDK本质都是对API的封装,可直接通过函数+参数的方式调用。比如获取对话消息使用list函数,其源码如下:

通过源码可以发现,请求的url是:

由于采用异步方式发送对话,因此需要轮询查询retrieve函数来获取对话是否结束的状态。

主要的后端流程就是:构建消息、创建对话(发送对话API)、轮询等待对话结果(查看对话详情API)、获取对话结果(查看对话消息API)。

注意:由于需要持续和智能体交互,因此用户的对话都需要保持同一个会话id。如果是开始游戏,就创建对话并保存会话id;如果已经存在会话id,就每次创建对话时使用该会话id。这样就可以保证用户的一次游戏始终保持同一个会话。

相关API可前往官网API文档/我的上篇文章学习。

3 前端页面

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>AI 人生模拟器</title>
    <style>
        :root {
            --bg-color: #1a1a1a;
            --card-bg: #2d2d2d;
            --text-color: #e0e0e0;
            --accent-color: #64ffda;
            --user-msg-bg: #3d4f5c;
            --ai-msg-bg: #2d2d2d;
        }

        * {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
        }

        body {
            font-family: 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
            background-color: var(--bg-color);
            color: var(--text-color);
            height: 100vh;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
        }

        /* 容器布局 */
        .container {
            width: 100%;
            max-width: 800px;
            height: 90vh;
            background-color: #222;
            border-radius: 12px;
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
            display: flex;
            flex-direction: column;
            overflow: hidden;
            border: 1px solid #333;
        }

        /* 头部 */
        header {
            padding: 20px;
            background-color: #252525;
            border-bottom: 1px solid #333;
            text-align: center;
            position: relative;
        }

        header h1 {
            font-size: 1.2rem;
            letter-spacing: 1px;
            color: var(--accent-color);
        }

        .restart-btn {
            position: absolute;
            right: 20px;
            top: 50%;
            transform: translateY(-50%);
            background: transparent;
            border: 1px solid var(--accent-color);
            color: var(--accent-color);
            padding: 5px 12px;
            border-radius: 4px;
            cursor: pointer;
            font-size: 0.8rem;
            transition: all 0.3s;
        }

        .restart-btn:hover {
            background: var(--accent-color);
            color: #000;
        }

        /* 聊天区域 */
        #chat-history {
            flex: 1;
            padding: 20px;
            overflow-y: auto;
            display: flex;
            flex-direction: column;
            gap: 15px;
            scroll-behavior: smooth;
        }

        .message {
            max-width: 85%;
            line-height: 1.6;
            padding: 12px 16px;
            border-radius: 8px;
            font-size: 0.95rem;
            position: relative;
            word-wrap: break-word;
        }

        /* AI 消息样式 */
        .message.ai {
            align-self: flex-start;
            background-color: var(--ai-msg-bg);
            border-left: 3px solid var(--accent-color);
            white-space: pre-wrap; /* 保留AI输出的换行格式 */
        }

        /* 用户消息样式 */
        .message.user {
            align-self: flex-end;
            background-color: var(--user-msg-bg);
            color: #fff;
            border-bottom-right-radius: 2px;
        }

        /* 输入区域 */
        .input-area {
            padding: 20px;
            background-color: #252525;
            border-top: 1px solid #333;
            display: flex;
            gap: 10px;
        }

        input[type="text"] {
            flex: 1;
            padding: 12px 15px;
            border-radius: 6px;
            border: 1px solid #444;
            background-color: #333;
            color: white;
            font-size: 1rem;
            outline: none;
            transition: border-color 0.3s;
        }

        input[type="text"]:focus {
            border-color: var(--accent-color);
        }

        button#send-btn {
            padding: 0 20px;
            background-color: var(--accent-color);
            color: #1a1a1a;
            border: none;
            border-radius: 6px;
            font-weight: bold;
            cursor: pointer;
            transition: opacity 0.3s;
        }

        button#send-btn:hover {
            opacity: 0.9;
        }

        button:disabled {
            background-color: #555 !important;
            cursor: not-allowed;
        }

        /* 加载动画 */
        .typing-indicator {
            align-self: flex-start;
            background-color: var(--ai-msg-bg);
            padding: 10px 15px;
            border-radius: 8px;
            display: none; /* 默认隐藏 */
            gap: 5px;
        }

        .dot {
            width: 8px;
            height: 8px;
            background-color: #888;
            border-radius: 50%;
            animation: bounce 1.4s infinite ease-in-out both;
        }

        .dot:nth-child(1) {
            animation-delay: -0.32s;
        }

        .dot:nth-child(2) {
            animation-delay: -0.16s;
        }

        @keyframes bounce {
            0%, 80%, 100% {
                transform: scale(0);
            }
            40% {
                transform: scale(1);
            }
        }

        /* 移动端适配 */
        @media (max-width: 600px) {
            .container {
                height: 100vh;
                border-radius: 0;
                max-width: 100%;
            }

            header h1 {
                font-size: 1rem;
            }
        }
    </style>
</head>
<body>

<div class="container">
    <header>
        <h1>人生模拟器</h1>
        <button class="restart-btn" onclick="restartGame()">重开人生</button>
    </header>

    <div id="chat-history">
        <!-- 欢迎消息 -->
        <div class="message ai">🌟 欢迎来到「我的未来我做主」毕业选择模拟器! 🌟
            你好!我是你的人生选择引导者。此刻,你将有机会体验到不同的人生,位于人生的十字路口,请谨慎对待每个选择。输入:开始,进行游戏。如果没有选择,请输入:继续。
        </div>
    </div>

    <!-- 加载中动画 -->
    <div class="typing-indicator" id="loading">
        <div class="dot"></div>
        <div class="dot"></div>
        <div class="dot"></div>
    </div>

    <div class="input-area">
        <input type="text" id="user-input" placeholder="输入你的选择..." autocomplete="off">
        <button id="send-btn" onclick="sendMessage()">发送</button>
    </div>
</div>

<script>
    const inputField = document.getElementById('user-input');
    const chatHistory = document.getElementById('chat-history');
    const sendBtn = document.getElementById('send-btn');
    const loadingIndicator = document.getElementById('loading');

    // 监听回车键
    inputField.addEventListener('keypress', function (e) {
        if (e.key === 'Enter') {
            sendMessage();
        }
    });

    // 发送消息主逻辑
    async function sendMessage() {
        const text = inputField.value.trim();
        if (!text) return;

        // 1. 显示用户消息
        appendMessage(text, 'user');
        inputField.value = '';

        // 锁定输入
        setInputState(false);
        loadingIndicator.style.display = 'flex';
        scrollToBottom();

        try {
            // 2. 请求后端
            const response = await fetch('/chat', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({message: text})
            });

            const data = await response.json();
            loadingIndicator.style.display = 'none';

            // 3. 处理返回结果
            if (data.status === 'success') {
                typeWriterEffect(data.content, 'ai');
            } else {
                appendMessage(`Error: ${data.content}`, 'ai');
                setInputState(true);
            }

        } catch (error) {
            loadingIndicator.style.display = 'none';
            appendMessage("连接服务器失败,请检查后端是否运行。", 'ai');
            console.error('Error:', error);
            setInputState(true);
        }
    }

    // 重置游戏
    async function restartGame() {
        // 先清除现有聊天记录
        chatHistory.innerHTML = '';

        try {
            // 请求后端重置会话
            await fetch('/restart', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                }
            });

            // 添加新的欢迎消息
            appendMessage('🌟 欢迎来到「我的未来我做主」毕业选择模拟器! 🌟\n你好!我是你的人生选择引导者。此刻,你将有机会体验到不同的人生,位于人生的十字路口,请谨慎对待每个选择。输入:开始,进行游戏。如果没有选择,请输入:继续。', 'ai');

            // 设置初始输入
            inputField.value = '开始';

        } catch (error) {
            console.error(' 重置会话失败:', error);
            appendMessage("重置会话失败,请重试", 'ai');
        }
    }

    // 添加消息到界面
    function appendMessage(content, role) {
        const msgDiv = document.createElement('div');
        msgDiv.classList.add('message', role);

        // 保留换行符
        msgDiv.innerHTML = content.replace(/\n/g, '<br>');

        chatHistory.appendChild(msgDiv);
        scrollToBottom();
        return msgDiv;
    }

    // 打字机效果函数
    function typeWriterEffect(text, role) {
        const msgDiv = appendMessage('', role);
        let i = 0;
        const speed = 20;
        const formattedText = text.replace(/\n/g, '<br>'); // 处理换行符

        function type() {
            if (i < formattedText.length) {
                // 处理HTML标签
                if (formattedText.substring(i, i + 4) === '<br>') {
                    msgDiv.innerHTML += '<br>';
                    i += 4;
                } else {
                    msgDiv.innerHTML += formattedText.charAt(i);
                    i++;
                }
                scrollToBottom();
                setTimeout(type, speed);
            } else {
                setInputState(true);
            }
        }

        type();
    }

    // 滚动到底部
    function scrollToBottom() {
        chatHistory.scrollTop = chatHistory.scrollHeight;
    }

    // 控制输入框状态
    function setInputState(enabled) {
        inputField.disabled = !enabled;
        sendBtn.disabled = !enabled;
        if (enabled) {
            inputField.focus();
        }
    }
</script>
</body>
</html>

前端页面是使用AI生成的,效果还是不错的,界面如下:

注意:这里AI给出的前端代码使用了typeWriterEffect函数来模拟一个字一个字生成的效果。实际上,可以直接使用流式输出(发起对话API的stream参数为true)来实现这样的效果,不必在前端还模拟流式数据。

4 测试与使用

4.1 添加.env文件

添加环境变量文件.env,内容如下(需要填写自己的令牌等数据):

python 复制代码
COZE_API_TOKEN = 令牌
BOT_ID = 智能体id
USER_ID = 用户id

4.2 运行程序

相关推荐
Databend20 分钟前
2KB histogram 背后:Databend 如何低成本追踪长尾延迟
大数据·数据分析·agent
笃行35023 分钟前
用 CodeBuddy “复活“《山海经》:异兽图鉴网站的诞生
agent
leonshi1 小时前
使用embedchain快速建立rag知识库,本地大模型
ai·rag·ollama
镜舟科技1 小时前
Databricks 再提 LTAP,AI 时代的数据底座为何重回大一统叙事?
数据库·架构·agent
轻口味1 小时前
别被模型宣传骗了,真实 Agent 任务一跑就知道
agent·ai编程
小星AI2 小时前
Kimi Code CLI 超详细教程,附源码
人工智能·agent
Databend2 小时前
从湖仓升级为 Agent 时代的数据控制面,Snowflake 和 Databricks 有哪些布局
大数据·数据库·agent
uccs3 小时前
AI Agent 系统的容错设计实践
agent·ai编程·claude
leeyi3 小时前
调试工具:Eino Dev 交互式调试
aigc·agent·ai编程