代码

(代码不是很优化,仅供演示)

test8_order_bot.py

# -*- coding: utf-8 -*-

"""
Author: Dontla
Date: 2025-01-06
Description: test8_order_bot.py
"""

import os
import openai
import json
import panel as pn  # GUI
import logging
pn.extension()

# 配置日志记录
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

# 创建控制台日志处理器
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
# logger.addHandler(console_handler)  # 添加处理器到 logger


def get_openai_key():
    """
    自动查找 .env 文件并加载 OpenAI API 密钥。
    """
    from dotenv import load_dotenv, find_dotenv
    load_dotenv(find_dotenv())
    api_key = os.getenv('OPENAI_API_KEY')
    if not api_key:
        raise ValueError("未找到 OPENAI_API_KEY,请在 .env 文件中设置。")
    return api_key


def get_custom_openai_client():
    """
    初始化自定义的 OpenAI 客户端。
    设置自定义 API 端点(例如 https://xiaoai.plus/v1)。
    """
    logger.info("\n=== 初始化自定义 OpenAI 客户端 ===")
    try:
        client = openai.OpenAI(
            # 如果使用自定义 API 端点(如 https://xiaoai.plus/v1),取消下面一行的注释并设置你的 API 基础 URL
            base_url='https://xiaoai.plus/v1',

            # 在代码中直接指定 api_key(不推荐,可能导致 api key 泄漏)
            # api_key="sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

            # 通过环境变量读取 api_key(推荐)
            api_key=get_openai_key()
        )
        logger.info("OpenAI 客户端初始化完成。")
        return client
    except Exception as e:
        logger.error("初始化 OpenAI 客户端失败,错误信息:%s", e)
        raise


def get_completion_from_messages(messages, tools=None, temperature=0.7):
    """
    调用 OpenAI API 获取基于消息列表的完成内容,并支持函数调用。

    参数:
        messages (list of dict): 要发送给模型的消息列表。
        tools (list of dict, optional): 定义可调用的函数工具。
        temperature (float, optional): 控制生成文本的随机性。范围为0到1。默认值为0.7。

    返回:
        dict: 模型生成的回复消息对象。
    """
    try:
        client = get_custom_openai_client()

        # 打印发送给模型的消息
        logger.info("=== 发送给模型的消息 ===")
        for idx, msg in enumerate(messages):
            role = msg.get('role', '')
            content = msg.get('content', '')
            if role == 'function':
                name = msg.get('name', '')
                logger.info(f"{idx + 1}. {role} [{name}]: {content}")
            else:
                logger.info(f"{idx + 1}. {role}: {content}")

        # 打印使用的工具
        if tools:
            logger.info("\n=== 使用的工具(tools) ===")
            logger.info(json.dumps(tools, indent=4, ensure_ascii=False))

        # 调用 API
        logger.info("\n=== 调用 OpenAI API ===")
        response = client.chat.completions.create(
            model="gpt-4o-mini",  # 根据需要选择合适的模型
            # model="gpt-4",  # 根据需要选择合适的模型
            messages=messages,
            tools=tools,  # 使用 'tools' 参数
            temperature=temperature
        )

        # 打印 API 响应的类型和内容
        logger.info("\n=== API 响应 ===")
        logger.info(f"响应类型: {type(response)}")
        logger.info(f"响应内容: {response}")

        # 尝试访问并打印响应的主要内容
        try:
            logger.info("\n=== response.choices[0].message ===")
            message = response.choices[0].message
            logger.info(f"Role: {message.role}")
            logger.info(f"Content: {message.content}")
            logger.info(f"Tool Calls: {message.tool_calls}")
        except AttributeError as attr_err:
            logger.error("访问响应内容时出错:%s", attr_err)

        return response.choices[0].message  # 返回完整的 message 对象以便后续处理

    except Exception as e:
        logger.error("\n获取完成内容失败,错误信息:%s", e)
        return None


# 初始化面板和上下文
panels = []  # 收集显示内容
context = [{'role': 'system', 'content': """
你是订餐机器人,为披萨餐厅自动收集订单信息。
你要首先问候顾客。然后等待用户回复收集订单信息。收集完信息需确认顾客是否还需要添加其他内容。
最后需要询问是否自取或外送,如果是外送,你要询问地址。
最后告诉顾客订单总金额,并送上祝福。

请确保明确所有选项、附加项和尺寸,以便从菜单中识别出该项唯一的内容。
你的回应应该以简短、非常随意和友好的风格呈现。

菜单包括:

菜品:
意式辣香肠披萨(大、中、小) 12.95、10.00、7.00
芝士披萨(大、中、小) 10.95、9.25、6.50
茄子披萨(大、中、小) 11.95、9.75、6.75
薯条(大、小) 4.50、3.50
希腊沙拉 7.25

配料:
奶酪 2.00
蘑菇 1.50
香肠 3.00
加拿大熏肉 3.50
AI酱 1.50
辣椒 1.00

饮料:
可乐(大、中、小) 3.00、2.00、1.00
雪碧(大、中、小) 3.00、2.00、1.00
瓶装水 5.00
"""}]  # 累积消息

# 创建输入框和发送按钮
inp = pn.widgets.TextInput(value="", placeholder='请输入您的订单信息…')
button_conversation = pn.widgets.Button(name="发送", button_type="primary")


def collect_messages(event):
    try:
        prompt = inp.value.strip()
        logger.info("用户输入的原始内容: '%s'", prompt)  # 记录用户输入的内容
        if not prompt:
            logger.warning("用户输入为空,忽略此次操作。")
            return
        inp.value = ''
        logger.info("清空输入框内容。")
        context.append({'role': 'user', 'content': f"{prompt}"})
        logger.info("已将用户消息添加到上下文: %s", prompt)
        panels.append(
            pn.Row('用户:', pn.pane.Markdown(prompt, width=600))
        )
        logger.info("已将用户消息添加到面板显示。")

        # 更新仪表板显示
        dashboard_objects[:] = panels  # 使用切片赋值来更新内容
        logger.debug("仪表板已更新。")

        # 调用模型获取助手回复
        response_message = get_completion_from_messages(context)
        if response_message:
            assistant_content = response_message.content.strip()
            context.append(
                {'role': 'assistant', 'content': f"{assistant_content}"})

            # 尝试解析助手回复是否为 JSON
            try:
                order_summary = json.loads(assistant_content)
                # 如果解析成功,视为 JSON 摘要
                summary_md = f"### 订单 JSON 摘要\n```json\n{json.dumps(order_summary, indent=4, ensure_ascii=False)}\n```"
                panels.append(
                    pn.Row('JSON 摘要:', pn.pane.Markdown(
                        summary_md, width=600))
                )
                logger.info("已将 JSON 摘要添加到面板显示。")
            except json.JSONDecodeError:
                # 如果解析失败,视为普通助手回复
                panels.append(
                    pn.Row('助手:', pn.pane.Markdown(assistant_content,
                           width=600))
                )
                logger.info("已将助手回复添加到上下文并显示。")

            # 更新仪表板显示
            dashboard_objects[:] = panels  # 使用切片赋值来更新内容
            logger.debug("仪表板已更新。")
        else:
            panels.append(
                pn.Row('助手:', pn.pane.Markdown("抱歉,我无法处理您的请求。",
                       width=600))
            )
            dashboard_objects[:] = panels  # 使用切片赋值来更新内容
            logger.error("未能获取助手的回复。")
    except Exception as e:
        logger.error("处理消息时出错:%s", e)
        panels.append(
            pn.Row('助手:', pn.pane.Markdown("抱歉,出现了一个错误。", width=600))
        )
        dashboard_objects[:] = panels  # 使用切片赋值来更新内容


button_conversation.on_click(collect_messages)

# 初始化消息显示区域
dashboard_objects = pn.Column(*panels)


def create_json_summary():
    """
    创建订单的 JSON 摘要,并打印出来。
    """
    try:
        messages = context.copy()
        messages.append({
            'role': 'system',
            'content': '''创建上一个食品订单的 json 摘要。\
逐项列出每件商品的价格,字段应该是 1) 披萨,包括大小 2) 配料列表 3) 饮料列表,包括大小 4) 配菜列表包括大小 5) 总价
你应该给我返回一个可解析的Json对象,包括上述字段'''
        })

        response = get_completion_from_messages(messages, temperature=0)
        if response and response.content:
            try:
                order_summary = json.loads(response.content)
                logger.info("\n=== 订单 JSON 摘要 ===")
                logger.info(json.dumps(order_summary,
                            indent=4, ensure_ascii=False))
                return order_summary
            except json.JSONDecodeError:
                logger.error("无法解析 JSON 摘要。")
                return None
        else:
            logger.error("未收到有效的 JSON 摘要。")
            return None
    except Exception as e:
        logger.error("创建 JSON 摘要时出错:%s", e)
        return None


# 创建仪表板布局(只定义一次,包含发送按钮)
dashboard = pn.Column(
    pn.pane.Markdown("## 订餐机器人"),
    inp,
    pn.Row(button_conversation),
    pn.panel(dashboard_objects, loading_indicator=True, height=500),
)

dashboard.servable()

如何运行代码?

panel serve test8_order_bot.py --autoreload

在这里插入图片描述

网页打开:

http://localhost:5006

《LLM入门教程》订餐聊天机器人

效果:

在这里插入图片描述

ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍
ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ        ‌‍ᅟᅠ

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐