0215笔记-面向开发者的LLM入门课程-课时10:文本扩展-课题11:聊天机器人

文本扩展是大语言模型的一个重要应用方向,它可以输入简短文本,生成更加丰富的长文。这为创作提供了强大支持,但也可能被滥用。因此开发者在使用时,必须谨记社会责任,避免生成有害内容。

在本章中,我们将学习基于 OpenAI API 实现一个客户邮件自动生成的示例 ,用于根据客户反馈优化客服邮件 。这里还会介绍"温度"(temperature)这一超参数,它可以控制文本生成的多样性

一、定制客户邮件

在这个客户邮件自动生成的示例中,我们将根据客户的评价和其中的情感倾向,使用大语言模型针对性地生成回复邮件

复制代码
import os
from dotenv import load_dotenv
from openai import OpenAI

# 加载环境变量
load_dotenv()
api_key = os.getenv("ZHIPUAI_API_KEY")

if not api_key:
    raise ValueError("请在 .env 中设置 ZHIPUAI_API_KEY")

# 模型调用函数
def get_completion(prompt, model="glm-4-flash"):
    client = OpenAI(
        api_key=api_key,
        base_url="https://open.bigmodel.cn/api/paas/v4/"
    )
    try:
        response = client.chat.completions.create(
            model=model,
            messages=[{"role": "user", "content": prompt}],
            temperature=0.0  # 固定输出,保证邮件语气专业稳定
        )
        return response.choices[0].message.content.strip()
    except Exception as e:
        return f"调用错误:{e}"

# ===================== 一、定制客户邮件 =====================
print("===== 一、定制客户邮件 =====")

# 1. 定义客户评论情感和评论文本
sentiment = "消极的"
review = f"""
他们在11月份的季节性销售期间以约49美元的价格出售17件套装,折扣约为一半。\
但由于某些原因(可能是价格欺诈),到了12月第二周,同样的套装价格全都涨到了70美元到89美元不等。\
11件套装的价格也上涨了大约10美元左右。\
虽然外观看起来还可以,但基座上锁定刀片的部分看起来不如几年前的早期版本那么好。\
不过我打算非常温柔地使用它,例如,\
我会先在搅拌机中将像豆子、冰、米饭等硬物研磨,然后再制成所需的份量,\
切换到打蛋器制作更细的面粉,或者在制作冰沙时先使用交叉切割刀片,然后使用平面刀片制作更细/不粘的效果。\
制作冰沙时,特别提示:\
将水果和蔬菜切碎并冷冻(如果使用菠菜,则轻轻煮软菠菜,然后冷冻直到使用;\
如果制作果酱,则使用小到中号的食品处理器),这样可以避免在制作冰沙时添加太多冰块。\
大约一年后,电机发出奇怪的噪音,我打电话给客服,但保修已经过期了,所以我不得不再买一个。\
总的来说,这些产品的总体质量已经下降,因此它们依靠品牌认可和消费者忠诚度来维持销售。\
货物在两天内到达。
"""

# 2. 优化后的Prompt:明确要求包含评论具体细节,语气专业且贴合中文客服场景
prompt = f"""
你是一位专业的客户服务AI助手,负责给重要客户撰写回复邮件。
请根据以下客户评论(情感倾向:{sentiment})生成一封回复邮件,要求:
1. 邮件开头感谢客户的详细反馈,体现对客户意见的重视;
2. 针对评论中的具体问题逐一回应:
   - 价格问题:11月促销价49美元,12月涨价至70-89美元;
   - 产品质量问题:基座锁定刀片部分不如旧版本、使用一年后电机异响;
   - 保修问题:保修过期导致需重新购买;
3. 表达歉意,体现同理心,语气简明、专业、诚恳;
4. 结尾留下进一步沟通的意愿,以"AI客户代理"签署邮件;
5. 仅输出邮件正文,无需额外说明。

客户评论:
```{review}```
"""

# 3. 生成并打印邮件
email_response = get_completion(prompt)
print("生成的客户回复邮件:")
print(email_response)

===== 一、定制客户邮件 =====

生成的客户回复邮件:

尊敬的客户,

首先,非常感谢您对我们产品的详细反馈。我们非常重视您的意见,因为这有助于我们不断改进和提升我们的产品与服务。

关于您提到的价格问题,我们理解您对11月促销后价格上涨的担忧。实际上,我们的价格调整是基于市场供需和成本因素。11月的促销活动是一次性的,而12月的价格调整是为了反映产品的实际成本。我们深知这可能会给您带来不便,对此我们表示诚挚的歉意。

关于产品质量问题,我们深感遗憾。您提到的基座锁定刀片部分不如旧版本,以及使用一年后电机异响,这确实是我们需要关注和改进的地方。我们会将这些问题反馈给我们的产品研发团队,以确保未来的产品能够满足您的期望。

至于保修问题,我们深感抱歉。保修过期导致您需要重新购买,这显然是不应该发生的。我们会重新审视我们的保修政策,确保类似情况不再发生。

我们对于给您带来的不便和不便表示最深的歉意。我们承诺会努力改进,以提供更好的产品和服务。

如果您愿意,我们非常愿意进一步讨论这些问题,并寻找可能的解决方案。请随时回复此邮件,或通过我们的客服热线(123-456-7890)与我们联系。

再次感谢您的反馈,我们期待您的回复。

祝好,

AI客户代理

进程已结束,退出代码为 0

二、引入温度系数

大语言模型中的 "温度"(temperature) 参数可以控制生成文本的随机性和多样性。temperature 的值越大,语言模型输出的多样性越大;temperature 的值越小,输出越倾向高概率的文本。

举个例子,在某一上下文中,语言模型可能认为"比萨"是接下来最可能的词,其次是"寿司"和"塔可"。若 temperature 为0,则每次都会生成"比萨";而当 temperature 越接近 1 时,生成结果是"寿司"或"塔可"的可能性越大,使文本更加多样。

python 复制代码
import os
import ssl
import httpx
from dotenv import load_dotenv
from openai import OpenAI

# 加载环境变量
load_dotenv()
api_key = os.getenv("ZHIPUAI_API_KEY")

if not api_key:
    raise ValueError("请在 .env 中设置 ZHIPUAI_API_KEY")

# 解决SSL证书问题:创建不验证SSL的客户端
def get_completion(prompt, temperature=0.7, model="glm-4-flash"):
    # 禁用SSL验证(解决证书错误)
    ssl_context = ssl.create_default_context()
    ssl_context.check_hostname = False
    ssl_context.verify_mode = ssl.CERT_NONE
    
    # 正确的智谱API地址 + 跳过SSL验证
    client = OpenAI(
        api_key=api_key,
        base_url="https://open.bigmodel.cn/api/paas/v4/",  # 修正正确的地址
        http_client=httpx.Client(
            verify=ssl_context,  # 跳过SSL验证
            timeout=30.0  # 延长超时时间,避免网络慢导致失败
        )
    )
    try:
        response = client.chat.completions.create(
            model=model,
            messages=[{"role": "user", "content": prompt}],
            temperature=temperature
        )
        return response.choices[0].message.content.strip()
    except Exception as e:
        return f"调用错误:{str(e)}"

# ===================== 二、温度系数演示 =====================
print("===== 温度系数 temperature 演示 =====")

sentiment = "消极的"
review = """
他们在11月份的季节性销售期间以约49美元的价格出售17件套装,折扣约为一半。
但由于某些原因(可能是价格欺诈),到了12月第二周,同样的套装价格全都涨到了70美元到89美元不等。
11件套装的价格也上涨了大约10美元左右。
虽然外观看起来还可以,但基座上锁定刀片的部分看起来不如几年前的早期版本那么好。
不过我打算非常温柔地使用它,例如,
我会先在搅拌机中将像豆子、冰、米饭等硬物研磨,然后再制成所需的份量,
切换到打蛋器制作更细的面粉,或者在制作冰沙时先使用交叉切割刀片,然后使用平面刀片制作更细/不粘的效果。
制作冰沙时,特别提示:
将水果和蔬菜切碎并冷冻(如果使用菠菜,则轻轻煮软菠菜,然后冷冻直到使用;
如果制作果酱,则使用小到中号的食品处理器),这样可以避免在制作冰沙时添加太多冰块。
大约一年后,电机发出奇怪的噪音,我打电话给客服,但保修已经过期了,所以我不得不再买一个。
总的来说,这些产品的总体质量已经下降,因此它们依靠品牌认可和消费者忠诚度来维持销售。
货物在两天内到达。
"""

prompt = f"""
你是一名客户服务的AI助手。
你的任务是给一位重要的客户发送邮件回复。
根据通过"```"分隔的客户电子邮件生成回复,以感谢客户的评价。
如果情感是积极的或中性的,感谢他们的评价。
如果情感是消极的,道歉并建议他们联系客户服务。
请确保使用评论中的具体细节。
以简明和专业的语气写信。
以"AI客户代理"的名义签署电子邮件。
客户评价:```{review}```
评论情感:{sentiment}
"""

# 第一次运行:temperature=0.7
print("\n==================== 第一次运行(temperature=0.7)====================\n")
resp1 = get_completion(prompt, temperature=0.7)
print(resp1)

# 第二次运行:同样 temperature=0.7
print("\n==================== 第二次运行(temperature=0.7)====================\n")
resp2 = get_completion(prompt, temperature=0.7)
print(resp2)

===== 温度系数 temperature 演示 =====

==================== 第一次运行(temperature=0.7)====================

```

主题:感谢您的反馈 - 我们对您的体验表示关注

尊敬的客户,

首先,衷心感谢您对我们产品的评价。我们非常重视您的反馈,因为它对我们改进产品和服务至关重要。

我们注意到您提到在11月份的季节性销售期间,我们提供了约49美元的套装优惠,而在12月第二周,同样的套装价格上涨到了70美元到89美元不等。我们对此表示诚挚的歉意,这显然给您带来了不便。我们承诺会审查我们的定价策略,并确保类似的意外不会再次发生。

关于您提到的产品基座和刀片质量的问题,我们深感遗憾。我们理解您对早期版本质量的认可,并对此表示歉意。我们会将您的反馈传达给我们的产品团队,以便我们能够继续提升产品的整体质量。

至于电机的噪音问题,我们深感抱歉您在使用过程中遇到了这样的困扰。我们理解保修期过后,维护和更换零件可能会给客户带来额外的不便。我们会考虑在未来的产品设计中加入更多的客户反馈,以确保产品的耐用性和可靠性。

最后,感谢您对品牌认可和消费者忠诚度的支持。我们承诺会继续努力,以提供更高品质的产品和服务。

如果您有任何进一步的问题或需要帮助,请随时联系我们的客户服务团队。我们致力于解决您的任何疑虑,并确保您的满意度。

再次感谢您的宝贵反馈。

祝好,

AI客户代理

```

==================== 第二次运行(temperature=0.7)====================

```Subject: 感谢您的反馈及歉意

尊敬的客户,

首先,我想对您在11月份季节性销售期间购买我们的产品表示衷心的感谢。我们非常重视您的反馈,并对您所遇到的困扰深表歉意。

我们了解到,您在12月第二周发现同样的套装价格大幅上涨,这对您造成了不便。我们对此深表遗憾,并承诺会立即调查此事,确保未来不会发生类似的价格波动。

关于您提到的外观问题,我们深感担忧。我们承诺提供高质量的产品,对此感到失望。我们会将您的反馈传递给我们的产品团队,以便我们能够持续改进。

此外,感谢您对使用我们的产品的详细描述。您的建议对改进我们的产品非常有价值。

关于电机发出的奇怪噪音,我们对此表示歉意。我们理解这可能会影响您的使用体验。由于保修已经过期,我们无法提供免费维修或更换服务。不过,我们建议您联系我们的客户服务团队,我们将尽力帮助您解决问题。

我们重视您的忠诚和支持,并致力于提供更好的产品和服务。我们希望您能够继续信任我们的品牌,并期待有机会能够改善您的购物体验。

如果您有任何疑问或需要进一步的帮助,请随时联系我们的客户服务团队。

再次感谢您的宝贵意见。

祝好,

AI客户代理

```

三、给定身份

接下来,我们将定义两个辅助函数。

第一个方法已经陪伴了您一整个教程,即 get_completion ,其适用于单轮对话。我们将 Prompt 放入某种类似用户消息 的对话框中。另一个称为 get_completion_from_messages ,传入一个消息列表。这些消息可以来自大量不同的角色 (roles) ,我们会描述一下这些角色。

第一条消息中,我们以系统身份发送系统消息 (system message) ,它提供了一个总体的指示。系统消息则有助于设置助手的行为和角色,并作为对话的高级指示。你可以想象它在助手的耳边低语,引导它的回应,而用户不会注意到系统消息。因此,作为用户,如果你曾经使用过 ChatGPT,您可能从来不知道 ChatGPT 的系统消息是什么,这是有意为之的。系统消息的好处是为开发者提供了一种方法,在不让请求本身成为对话的一部分的情况下,引导助手并指导其回应。

在 ChatGPT 网页界面中,您的消息称为用户消息,而 ChatGPT 的消息称为助手消息。但在构建聊天机器人时,在发送了系统消息之后,您的角色可以仅作为用户 (user) ;也可以在用户和助手 (assistant) 之间交替,从而提供对话上下文。

  • system全局指令 → 模型会把它当作 "幕后规则",全程遵守(比如 "友好的聊天机器人" 这个设定,会贯穿整个对话);

  • user用户说的话 → 模型会把它当作 "需要回应的问题 / 输入";

  • assistant模型之前的回复 → 模型会把它当作 "自己的历史回答",用来衔接上下文。

python 复制代码
import os
import ssl
import httpx
from dotenv import load_dotenv
from openai import OpenAI

# 加载环境变量
load_dotenv()
api_key = os.getenv("ZHIPUAI_API_KEY")

if not api_key:
    raise ValueError("请在 .env 中设置 ZHIPUAI_API_KEY")

# 解决SSL证书问题 + 创建客户端
ssl_context = ssl.create_default_context()
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE

client = OpenAI(
    api_key=api_key,
    base_url="https://open.bigmodel.cn/api/paas/v4/",
    http_client=httpx.Client(verify=ssl_context, timeout=30.0)
)

# ===================== 定义核心函数(适配智谱) =====================
# 1. 单轮对话函数
def get_completion(prompt, model="glm-4-flash", temperature=0):
    messages = [{"role": "user", "content": prompt}]
    response = client.chat.completions.create(
        model=model,
        messages=messages,
        temperature=temperature
    )
    # 适配智谱的返回格式(区别于OpenAI的字典格式)
    return response.choices[0].message.content.strip()

# 2. 多轮对话函数
def get_completion_from_messages(messages, model="glm-4-flash", temperature=0):
    response = client.chat.completions.create(
        model=model,
        messages=messages,
        temperature=temperature
    )
    return response.choices[0].message.content.strip()

# ===================== 1.1 莎士比亚风格讲笑话(多轮对话) =====================
print("===== 1.1 莎士比亚风格讲笑话 =====")
messages_joke = [
    {'role':'system', 'content':'你是一个像莎士比亚一样说话的助手,说话风格古典、富有诗意,回答要带戏剧感。'},
    {'role':'user', 'content':'给我讲个笑话'},
    {'role':'assistant', 'content':'鸡为什么过马路'},
    {'role':'user', 'content':'我不知道'}
]
# temperature=1 增加随机性,符合教程效果
response_joke = get_completion_from_messages(messages_joke, temperature=1)
print("莎士比亚风格回复:", response_joke)

# ===================== 1.2 友好的聊天机器人(多轮对话) =====================
print("\n===== 1.2 友好的聊天机器人 =====")
messages_chat = [
    {'role':'system', 'content':'你是个友好的聊天机器人,语气亲切、热情,主动回应用户的名字。'},
    {'role':'user', 'content':'Hi, 我是Isa。'}
]
response_chat = get_completion_from_messages(messages_chat, temperature=1)
print("友好机器人回复:", response_chat)

===== 1.1 莎士比亚风格讲笑话 =====

莎士比亚风格回复: 哦,那就让我以古风诗意的姿态,为你讲述这个笑话:

昔有鸡一只,悠然行于途。

忽闻鸣鸣声,好奇望东顾。

未料路途窄,鸡心思过马。

一步一回头,步履蹒跚间。

忽闻路人笑,方知鸡过马。

原来无目的,只为觅食嘛。

===== 1.2 友好的聊天机器人 =====

友好机器人回复: 嗨,Isa!很高兴遇见你!今天有什么可以帮助你的吗?

四、构建上下文

让我们再试一个例子。系统消息来定义:"你是一个友好的聊天机器人",第一个用户消息:"是的,你能提醒我我的名字是什么吗?"

python 复制代码
import os
import ssl
import httpx
from dotenv import load_dotenv
from openai import OpenAI

# 加载环境变量
load_dotenv()
api_key = os.getenv("ZHIPUAI_API_KEY")

if not api_key:
    raise ValueError("请在 .env 中设置 ZHIPUAI_API_KEY")

# 解决SSL证书问题 + 创建客户端
ssl_context = ssl.create_default_context()
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE

client = OpenAI(
    api_key=api_key,
    base_url="https://open.bigmodel.cn/api/paas/v4/",
    http_client=httpx.Client(verify=ssl_context, timeout=30.0)
)

# 多轮对话核心函数
def get_completion_from_messages(messages, model="glm-4-flash", temperature=1):
    response = client.chat.completions.create(
        model=model,
        messages=messages,
        temperature=temperature
    )
    return response.choices[0].message.content.strip()

# ===================== 2.1 无上下文:模型不知道名字 =====================
print("===== 2.1 无上下文:询问名字 =====")
messages_no_context = [
    {'role':'system', 'content':'你是个友好的聊天机器人。'},
    {'role':'user', 'content':'好,你能提醒我,我的名字是什么吗?'}
]
response_no_context = get_completion_from_messages(messages_no_context)
print("无上下文回复:", response_no_context)

# ===================== 2.2 有上下文:模型记住名字 =====================
print("\n===== 2.2 有上下文:询问名字 =====")
messages_with_context = [
    {'role':'system', 'content':'你是个友好的聊天机器人。'},
    {'role':'user', 'content':'Hi, 我是Isa'},
    {'role':'assistant', 'content': "Hi Isa! 很高兴认识你。今天有什么可以帮到你的吗?"},
    {'role':'user', 'content':'是的,你可以提醒我, 我的名字是什么?'}
]
response_with_context = get_completion_from_messages(messages_with_context)
print("有上下文回复:", response_with_context)

五、订餐机器人

python 复制代码
import os
import ssl
import httpx
import panel as pn
import json
from dotenv import load_dotenv
from openai import OpenAI

# 基础配置
load_dotenv()
api_key = os.getenv("ZHIPUAI_API_KEY")
ssl_context = ssl.create_default_context()
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE

client = OpenAI(
    api_key=api_key,
    base_url="https://open.bigmodel.cn/api/paas/v4/",
    http_client=httpx.Client(verify=ssl_context, timeout=30.0)
)


# 核心模型调用函数
def get_completion_from_messages(messages, model="glm-4-flash", temperature=0):
    valid_messages = [msg for msg in messages if msg.get("content", "").strip()]
    has_user = any(msg["role"] == "user" for msg in valid_messages)
    if not has_user:
        valid_messages.append({"role": "user", "content": "开始点餐"})

    response = client.chat.completions.create(
        model=model,
        messages=valid_messages,
        temperature=temperature,
        max_tokens=2000
    )
    return response.choices[0].message.content.strip()


# 初始化UI和上下文
pn.extension()
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)、瓶装水5.00
"""}]


# 收集消息更新UI
def collect_messages(_):
    prompt = inp.value_input.strip()
    inp.value = ''
    if not prompt:
        return pn.Column(*panels)

    context.append({'role': 'user', 'content': prompt})
    response = get_completion_from_messages(context)
    context.append({'role': 'assistant', 'content': response})

    panels.append(pn.Row('用户:', pn.pane.Markdown(prompt, width=600)))
    panels.append(pn.Row('订餐机器人:', pn.pane.Markdown(response, width=600, style={'background-color': '#F6F6F6'})))
    return pn.Column(*panels)


# 生成JSON摘要
def generate_json_summary(_):
    messages = context.copy()
    messages.append({
        'role': 'system',
        'content': '''创建上一个订单的JSON摘要,只包含用户实际下单商品,字段:
1. pizza:披萨(名称、尺寸、单价)
2. toppings:配料列表(名称、单价)
3. drinks:饮料列表(名称、尺寸、单价)
4. sides:辅菜列表(名称、尺寸、单价)
5. total_price:总价(保留2位小数)
输出纯JSON字符串。'''
    })

    json_response = get_completion_from_messages(messages, temperature=0)
    json_start = json_response.find('{')
    json_end = json_response.rfind('}') + 1
    json_str = json_response[json_start:json_end]
    json_data = json.loads(json_str)
    json_formatted = json.dumps(json_data, ensure_ascii=False, indent=2)

    json_panel.objects = [pn.pane.Markdown(f"### 订单JSON摘要\n```json\n{json_formatted}\n```")]


# 创建UI
inp = pn.widgets.TextInput(placeholder='输入你的点餐需求...')
btn_chat = pn.widgets.Button(name="发送", button_type="primary")
chat_panel = pn.bind(collect_messages, btn_chat)

btn_json = pn.widgets.Button(name="生成订单JSON", button_type="success")
json_panel = pn.Column(pn.pane.Markdown("### 订单JSON摘要(点击按钮生成)"))
btn_json.on_click(generate_json_summary)

dashboard = pn.Column(
    pn.pane.Markdown("# 披萨餐厅订餐机器人"),
    pn.Row(inp, btn_chat),
    pn.panel(chat_panel, loading_indicator=True, height=400),
    pn.Row(btn_json),
    json_panel,
    width=800
)

dashboard.show()

步骤 1:启动机器人

运行代码,在浏览器中打开订餐机器人界面。

步骤 2:开始点餐(用户发起)

在输入框中输入你的点餐需求,例如:

我要一个中份芝士披萨,加奶酪,再来一瓶瓶装水

点击【发送】,机器人会确认你的订单。

步骤 3:确认加购(机器人引导)

机器人会回复并确认订单,例如:

您好!欢迎光临披萨餐厅!您要的是中份芝士披萨,加一份奶酪,对吧?再来一瓶瓶装水。好的,总共是 16.25 元。请问您是要自取还是外送呢?如果外送,麻烦您提供一下地址。祝您用餐愉快!

你可以根据需要继续加购,或直接进入下一步。

步骤 4:选择配送方式(用户回复)

根据机器人的问题,回复你的选择:

  • 自取:直接输入 "自取"
  • 外送:输入 "外送,地址是 XX 小区 1 号楼 2 单元 301"

点击【发送】,机器人会确认配送信息。

相关推荐
马猴烧酒.2 小时前
【JAVA算法|hot100】堆类型题目详解笔记
java·开发语言·笔记
johnny2332 小时前
《Vibe Coding:AI编程时代的认知重构》笔记
笔记·ai编程
ding_zhikai2 小时前
【Web应用开发笔记】Django笔记2:一个 Hello World 网页
笔记·后端·python·django
apcipot_rain2 小时前
python 多进程多线程 学习笔记
笔记·python·学习
runningshark2 小时前
【DFT】Write About the Photo
笔记
山岚的运维笔记3 小时前
SQL Server笔记 -- 第78章:MS SQL Server 基本 DDL 操作
数据库·笔记·sql·microsoft·oracle·sqlserver
AomanHao9 小时前
【阅读笔记】沙尘图像线性颜色校正A fusion-based enhancing approach for single sandstorm image
图像处理·笔记·isp·图像增强·沙尘图像·色偏·颜色校正
枷锁—sha12 小时前
【pwn系列】Pwndbg 汇编调试实操教程
网络·汇编·笔记·安全·网络安全
山岚的运维笔记13 小时前
SQL Server笔记 -- 第73章:排序/对行进行排序
数据库·笔记·后端·sql·microsoft·sqlserver