案例

Chat-甄嬛

分类:SFT;训练Token 0.6M

介绍

Chat-甄嬛 (作者:KMnO4-zx) 是利用《甄嬛传》剧本中所有关于甄嬛的台词和语句,基于大模型进行 LoRA 微调得到的模仿甄嬛语气的聊天语言模型。

Chat-甄嬛实现了以《甄嬛传》为切入点,打造一套基于小说、剧本的个性化 AI 微调大模型完整流程,通过提供任一小说、剧本,指定人物角色,运行本项目完整流程,让每一位用户都基于心仪的小说、剧本打造一个属于自己的、契合角色人设、具备高度智能的个性化 AI。

甄嬛,小说《后宫·甄嬛传》和电视剧《甄嬛传》中的女一号,核心女主角。原名甄玉嬛,嫌玉字俗气而改名甄嬛,为汉人甄远道之女,后被雍正赐姓钮祜禄氏,抬旗为满洲上三旗,获名“钮祜禄·甄嬛”。同沈眉庄、安陵容参加选秀,因容貌酷似纯元皇后而被选中。入宫后面对华妃的步步紧逼,沈眉庄被冤、安陵容变心,从偏安一隅的青涩少女变成了能引起血雨腥风的宫斗老手。雍正发现年氏一族的野心后令其父甄远道剪除,甄嬛也于后宫中用她的连环巧计帮皇帝解决政敌,故而深得雍正爱待。几经周折,终于斗垮了嚣张跋扈的华妃。甄嬛封妃时遭皇后宜修暗算,被皇上嫌弃,生下女儿胧月后心灰意冷,自请出宫为尼。然得果郡王爱慕,二人相爱,得知果郡王死讯后立刻设计与雍正再遇,风光回宫。此后甄父冤案平反、甄氏复起,她也生下双生子,在滴血验亲等各种阴谋中躲过宜修的暗害,最后以牺牲自己亲生胎儿的方式扳倒了幕后黑手的皇后。但雍正又逼甄嬛毒杀允礼,以测试甄嬛真心,并让已经生产过孩子的甄嬛去准格尔和亲。甄嬛遂视皇帝为最该毁灭的对象,大结局道尽“人类的一切争斗,皆因统治者的不公不义而起”,并毒杀雍正。四阿哥弘历登基为乾隆,甄嬛被尊为圣母皇太后,权倾朝野,在如懿传中安度晚年。

环境

在任意一台可联网的 CPU 机器上,安装环境:

pip install pytrio transformers modelscope tqdm swanlab numpy

其中 swanlab 用于观测训练曲线,需要先在本地登录后使用,详情参考:快速开始 - SwanLab

数据集

将数据集下载到训练项目的 dataset/ 目录下,命名为 huanhuan.json 即可。

下载链接:Github

代码

完成训练和评估大约需要消耗 0.6M 训练 Token。下面的示例使用 Qwen/Qwen3.5-4B 作为基座模型,并通过 SwanLab 记录训练曲线。

示例中的 DATASET_PATH = Path("dataset/huanhuan.json") 假设脚本位于训练项目根目录,数据集位于同一目录下的 dataset/huanhuan.json。如果你的脚本位置不同,请按实际目录调整 DATASET_PATH

执行下面任一版本的代码,即可开始训练:

同步版(方便理解)

import json
import os
from pathlib import Path
import time

import numpy as np
import pytrio as trio
import swanlab
from tqdm import tqdm

# 基础训练配置:按需替换模型、数据集和 LoRA 权重名称。
BASE_MODEL = "Qwen/Qwen3.5-4B"
DATASET_PATH = Path("dataset/huanhuan.json")
NUM_EPOCHS = 3
BATCH_SIZE = 16
LORA_RANK = 32
LEARNING_RATE = 1e-4
MAX_LENGTH = 1024
SYSTEM_PROMPT = "现在你要扮演皇帝身边的女人--甄嬛"

# SwanLab 配置支持通过环境变量覆盖,方便复用同一份脚本跑多组实验。
SWANLAB_PROJECT = os.getenv("SWANLAB_PROJECT", "trio-case")
SWANLAB_EXPERIMENT_NAME = os.getenv("SWANLAB_EXPERIMENT_NAME", "chat-huanhuan-qwen35-4b")
WEIGHTS_NAME = os.getenv("TRIO_WEIGHTS_NAME", SWANLAB_EXPERIMENT_NAME)


# 加载数据集
def load_examples(dataset_path: Path) -> list[dict[str, str]]:
    # 数据集是 JSON 数组,每条样本包含 instruction/input/output 三个字段。
    raw_examples = json.loads(dataset_path.read_text(encoding="utf-8"))
    examples: list[dict[str, str]] = []

    for item in raw_examples:
        instruction = item.get("instruction", "").strip()
        input_text = item.get("input", "").strip()
        output_text = item.get("output", "").strip()

        if not instruction or not output_text:
            continue

        # input 为空时只使用 instruction;否则把 instruction 和 input 合并成用户输入。
        user_text = instruction if not input_text else f"{instruction}\n{input_text}"
        examples.append({"user": user_text, "assistant": output_text})

    if not examples:
        raise ValueError(f"No valid training examples found in {dataset_path}")

    return examples


def build_datum(example: dict[str, str], tokenizer) -> trio.Datum:
    # system prompt 用于固定角色设定,user 内容来自数据集里的 instruction/input。
    messages = [
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user", "content": example["user"]},
    ]
    prompt_text = tokenizer.apply_chat_template(
        messages,
        tokenize=False,
        add_generation_prompt=True,
        enable_thinking=False,
    )

    # prompt 部分不参与 loss,等价于常见 SFT 代码里 labels 使用 -100。
    prompt_tokens = tokenizer.encode(prompt_text, add_special_tokens=False)
    prompt_weights = [0] * len(prompt_tokens)

    # assistant 回复才是模型需要学习的目标,因此 loss 权重为 1。
    completion_tokens = tokenizer.encode(example["assistant"], add_special_tokens=False)
    completion_weights = [1] * len(completion_tokens)

    # 显式补上 EOS,让模型学习在回答结束处停止。
    eos_token_id = tokenizer.eos_token_id
    if eos_token_id is not None:
        completion_tokens = completion_tokens + [eos_token_id]
        completion_weights = completion_weights + [1]

    tokens = prompt_tokens + completion_tokens
    weights = prompt_weights + completion_weights
    if len(tokens) > MAX_LENGTH:
        # 超长样本直接截断,保持 tokens 和 weights 对齐。
        tokens = tokens[:MAX_LENGTH]
        weights = weights[:MAX_LENGTH]

    # 自回归训练需要右移一位:input 预测 target,loss_weights 对齐 target。
    input_tokens = tokens[:-1]
    target_tokens = tokens[1:]
    loss_weights = weights[1:]

    return trio.Datum(
        model_input=trio.ModelInput.from_ints(tokens=input_tokens),
        loss_fn_inputs={
            "weights": np.asarray(loss_weights, dtype=np.float32),
            "target_tokens": np.asarray(target_tokens, dtype=np.int32),
        },
    )


def evaluate_client(client, tokenizer, prompts: list[str], title: str) -> None:
    # 训练前后都用同一组 prompt 测试,便于观察 LoRA 微调带来的变化。
    print(f"\n{title}")
    stop_tokens = [tokenizer.eos_token] if tokenizer.eos_token else ["<|im_end|>"]
    params = trio.SamplingParams(max_tokens=80, temperature=0.0, stop=stop_tokens)

    for prompt in prompts:
        # 推理时也保留同一个 system prompt,保证训练和测试输入格式一致。
        messages = [
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": prompt},
        ]
        prompt_text = tokenizer.apply_chat_template(
            messages,
            tokenize=False,
            add_generation_prompt=True,
            enable_thinking=False,
        )
        prompt_ids = tokenizer.encode(prompt_text, add_special_tokens=False)
        future = client.sample(
            prompt=trio.ModelInput.from_ints(prompt_ids),
            sampling_params=params,
            num_samples=1,
        )
        result = future.result()
        print(f"User: {prompt}")
        print(f"Assistant: {result.sequences[0].text.strip()}\n")


def main() -> None:
    # 使用脚本所在目录拼接数据路径,避免从其他工作目录运行时找不到数据集。
    dataset_path = Path(__file__).resolve().parent / DATASET_PATH
    examples = load_examples(dataset_path)
    print(f"Loaded {len(examples)} training examples from {dataset_path}")

    # 创建 PyTrio 服务客户端,并基于指定基座模型创建 LoRA 训练客户端。
    service_client = trio.ServiceClient()
    training_client = service_client.create_lora_training_client(
        base_model=BASE_MODEL,
        rank=LORA_RANK,
    )

    print("Loading tokenizer...")
    tokenizer = training_client.get_tokenizer()
    print("Tokenizer ready")

    # 预先把原始文本样本转换成 PyTrio 训练所需的 Datum。
    processed_examples = [build_datum(example, tokenizer) for example in examples]

    print("Start training")
    # 计算每个 epoch 的训练步数和总步数,便于进度条显示和 SwanLab 日志记录。
    steps_per_epoch = (len(processed_examples) + BATCH_SIZE - 1) // BATCH_SIZE
    total_steps = NUM_EPOCHS * steps_per_epoch

    # 把关键超参数写入 SwanLab,便于后续复现实验。
    swanlab_init_kwargs = {
        "project": SWANLAB_PROJECT,
        "experiment_name": SWANLAB_EXPERIMENT_NAME,
        "config": {
            "base_model": BASE_MODEL,
            "dataset_path": str(DATASET_PATH),
            "weights_name": WEIGHTS_NAME,
            "num_epochs": NUM_EPOCHS,
            "batch_size": BATCH_SIZE,
            "lora_rank": LORA_RANK,
            "learning_rate": LEARNING_RATE,
            "max_length": MAX_LENGTH,
            "system_prompt": SYSTEM_PROMPT,
            "num_examples": len(processed_examples),
            "steps_per_epoch": steps_per_epoch,
            "total_steps": total_steps,
        },
    }

    swanlab_run = swanlab.init(**swanlab_init_kwargs)

    progress_bar = tqdm(total=total_steps, desc="SFT Training", unit="batch")
    for epoch in range(NUM_EPOCHS):
        for start in range(0, len(processed_examples), BATCH_SIZE):
            batch = processed_examples[start:start + BATCH_SIZE]
            batch_index = start // BATCH_SIZE
            global_step = epoch * steps_per_epoch + batch_index

            # 提交训练任务,进行前向和反向传播,并更新优化器参数。
            fwdbwd_future = training_client.forward_backward(batch, "cross_entropy")
            optim_future = training_client.optim_step(trio.AdamParams(learning_rate=LEARNING_RATE))
            fwdbwd_result = fwdbwd_future.result()
            optim_future.result()

            # PyTrio 返回每个 token 的 logprob,这里按 loss 权重求加权平均 loss。
            logprobs = np.concatenate(
                [output["logprobs"].tolist() for output in fwdbwd_result.loss_fn_outputs]
            )
            weights = np.concatenate(
                [example.loss_fn_inputs["weights"].tolist() for example in batch]
            )
            loss = -np.dot(logprobs, weights) / weights.sum()
            swanlab.log(
                {
                    "loss": float(loss),
                    "epoch": epoch + 1,
                    "batch": batch_index + 1,
                },
                step=global_step,
            )
            progress_bar.update(1)
            progress_bar.set_postfix(epoch=f"{epoch + 1}/{NUM_EPOCHS}", loss=f"{loss:.4f}")

    progress_bar.close()
    print("Saving LoRA weights...")

    # 保存 LoRA 权重,并拿到带 LoRA 权重的采样客户端用于效果测试。
    sft_weights_future = training_client.save_weights_for_sampler(name=WEIGHTS_NAME)
    sft_weights = sft_weights_future.result()
    # 未训练前的基座模型采样客户端,用于对比训练前后的效果。
    base_sampling_client = service_client.create_sampling_client(base_model=BASE_MODEL)
    # 训练后带 LoRA 权重的采样客户端,用于对比训练前后的效果。
    tuned_sampling_client = service_client.create_sampling_client(
        base_model=BASE_MODEL,
        model_path=sft_weights.path,
    )
    # 测试 prompt 列表,便于观察 LoRA 微调带来的变化。
    test_prompts = [
        "你是谁?",
        "介绍一下你自己。",
        "朕今天偶感风寒,你觉得我该如何调养身体?",
    ]

    # 训练前后都用同一组 prompt 测试,便于观察 LoRA 微调带来的变化。
    evaluate_client(base_sampling_client, tokenizer, test_prompts, title="Base model responses")
    evaluate_client(tuned_sampling_client, tokenizer, test_prompts, title="Fine-tuned model responses")

    print(f"Saved weights name: {WEIGHTS_NAME},Weights path: {sft_weights.path}")
    swanlab_run.finish()


if __name__ == "__main__":
    start_main_time = time.time()
    main()
    end_main_time = time.time()
    print("#" * 50)
    print("# all done")
    print(f"# train cost {end_main_time - start_main_time:.2f}s")
    print("#" * 50)

异步版(速度 x1.5)

import asyncio
import json
import os
from pathlib import Path
import time

import numpy as np
import pytrio as trio
import swanlab
from tqdm import tqdm

# 基础训练配置:按需替换模型、数据集和 LoRA 权重名称。
BASE_MODEL = "Qwen/Qwen3.5-4B"
DATASET_PATH = Path("dataset/huanhuan.json")
NUM_EPOCHS = 3
BATCH_SIZE = 16
LORA_RANK = 32
LEARNING_RATE = 1e-4
MAX_LENGTH = 1024
SYSTEM_PROMPT = "现在你要扮演皇帝身边的女人--甄嬛"

# SwanLab 配置支持通过环境变量覆盖,方便复用同一份脚本跑多组实验。
SWANLAB_PROJECT = os.getenv("SWANLAB_PROJECT", "trio-case")
SWANLAB_EXPERIMENT_NAME = os.getenv("SWANLAB_EXPERIMENT_NAME", "chat-huanhuan-qwen35-4b-async")
WEIGHTS_NAME = os.getenv("TRIO_WEIGHTS_NAME", SWANLAB_EXPERIMENT_NAME)


def load_examples(dataset_path: Path) -> list[dict[str, str]]:
    # 数据集是 JSON 数组,每条样本包含 instruction/input/output 三个字段。
    raw_examples = json.loads(dataset_path.read_text(encoding="utf-8"))
    examples: list[dict[str, str]] = []

    for item in raw_examples:
        instruction = item.get("instruction", "").strip()
        input_text = item.get("input", "").strip()
        output_text = item.get("output", "").strip()

        if not instruction or not output_text:
            continue

        # input 为空时只使用 instruction;否则把 instruction 和 input 合并成用户输入。
        user_text = instruction if not input_text else f"{instruction}\n{input_text}"
        examples.append({"user": user_text, "assistant": output_text})

    if not examples:
        raise ValueError(f"No valid training examples found in {dataset_path}")

    return examples


def build_datum(example: dict[str, str], tokenizer) -> trio.Datum:
    # system prompt 用于固定角色设定,user 内容来自数据集里的 instruction/input。
    messages = [
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user", "content": example["user"]},
    ]
    prompt_text = tokenizer.apply_chat_template(
        messages,
        tokenize=False,
        add_generation_prompt=True,
        enable_thinking=False,
    )

    # prompt 部分不参与 loss,等价于常见 SFT 代码里 labels 使用 -100。
    prompt_tokens = tokenizer.encode(prompt_text, add_special_tokens=False)
    prompt_weights = [0] * len(prompt_tokens)

    # assistant 回复才是模型需要学习的目标,因此 loss 权重为 1。
    completion_tokens = tokenizer.encode(example["assistant"], add_special_tokens=False)
    completion_weights = [1] * len(completion_tokens)

    # 显式补上 EOS,让模型学习在回答结束处停止。
    eos_token_id = tokenizer.eos_token_id
    if eos_token_id is not None:
        completion_tokens = completion_tokens + [eos_token_id]
        completion_weights = completion_weights + [1]

    tokens = prompt_tokens + completion_tokens
    weights = prompt_weights + completion_weights
    if len(tokens) > MAX_LENGTH:
        # 超长样本直接截断,保持 tokens 和 weights 对齐。
        tokens = tokens[:MAX_LENGTH]
        weights = weights[:MAX_LENGTH]

    # 自回归训练需要右移一位:input 预测 target,loss_weights 对齐 target。
    input_tokens = tokens[:-1]
    target_tokens = tokens[1:]
    loss_weights = weights[1:]

    return trio.Datum(
        model_input=trio.ModelInput.from_ints(tokens=input_tokens),
        loss_fn_inputs={
            "weights": np.asarray(loss_weights, dtype=np.float32),
            "target_tokens": np.asarray(target_tokens, dtype=np.int32),
        },
    )


async def evaluate_client(client, tokenizer, prompts: list[str], title: str) -> None:
    # 训练前后都用同一组 prompt 测试,便于观察 LoRA 微调带来的变化。
    print(f"\n{title}")
    stop_tokens = [tokenizer.eos_token] if tokenizer.eos_token else ["<|im_end|>"]
    params = trio.SamplingParams(max_tokens=80, temperature=0.0, stop=stop_tokens)

    for prompt in prompts:
        # 推理时也保留同一个 system prompt,保证训练和测试输入格式一致。
        messages = [
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": prompt},
        ]
        prompt_text = tokenizer.apply_chat_template(
            messages,
            tokenize=False,
            add_generation_prompt=True,
            enable_thinking=False,
        )
        prompt_ids = tokenizer.encode(prompt_text, add_special_tokens=False)
        future = await client.sample_async(
            prompt=trio.ModelInput.from_ints(prompt_ids),
            sampling_params=params,
            num_samples=1,
        )
        result = await future
        print(f"User: {prompt}")
        print(f"Assistant: {result.sequences[0].text.strip()}\n")


async def main() -> None:
    # 使用脚本所在目录拼接数据路径,避免从其他工作目录运行时找不到数据集。
    dataset_path = Path(__file__).resolve().parent / DATASET_PATH
    examples = load_examples(dataset_path)
    print(f"Loaded {len(examples)} training examples from {dataset_path}")

    # 创建 PyTrio 服务客户端,并基于指定基座模型创建 LoRA 训练客户端。
    service_client = trio.ServiceClient()
    training_client = await service_client.create_lora_training_client_async(
        base_model=BASE_MODEL,
        rank=LORA_RANK,
    )

    print("Loading tokenizer...")
    tokenizer = training_client.get_tokenizer()
    print("Tokenizer ready")

    # 预先把原始文本样本转换成 PyTrio 训练所需的 Datum。
    processed_examples = [build_datum(example, tokenizer) for example in examples]

    print("Start async training")
    steps_per_epoch = (len(processed_examples) + BATCH_SIZE - 1) // BATCH_SIZE
    total_steps = NUM_EPOCHS * steps_per_epoch

    # 把关键超参数写入 SwanLab,便于后续复现实验。
    swanlab_init_kwargs = {
        "project": SWANLAB_PROJECT,
        "experiment_name": SWANLAB_EXPERIMENT_NAME,
        "config": {
            "base_model": BASE_MODEL,
            "dataset_path": str(DATASET_PATH),
            "weights_name": WEIGHTS_NAME,
            "num_epochs": NUM_EPOCHS,
            "batch_size": BATCH_SIZE,
            "lora_rank": LORA_RANK,
            "learning_rate": LEARNING_RATE,
            "max_length": MAX_LENGTH,
            "system_prompt": SYSTEM_PROMPT,
            "num_examples": len(processed_examples),
            "steps_per_epoch": steps_per_epoch,
            "total_steps": total_steps,
        },
    }
    swanlab_run = swanlab.init(**swanlab_init_kwargs)

    progress_bar = tqdm(total=total_steps, desc="Async training", unit="batch")
    for epoch in range(NUM_EPOCHS):
        print_queue = []
        submit_bar = tqdm(
            total=steps_per_epoch,
            desc=f"Epoch {epoch + 1}/{NUM_EPOCHS} submit",
            unit="batch",
            leave=False,
        )

        for start in range(0, len(processed_examples), BATCH_SIZE):
            batch = processed_examples[start:start + BATCH_SIZE]
            batch_index = start // BATCH_SIZE
            global_step = epoch * steps_per_epoch + batch_index

            # 异步提交 forward/backward 和 optimizer step,本地先拿到 future。
            fwdbwd_future = await training_client.forward_backward_async(batch, "cross_entropy")
            optim_future = await training_client.optim_step_async(trio.AdamParams(learning_rate=LEARNING_RATE))
            submit_bar.update(1)

            # 把 loss 计算和上报也写成异步,避免阻塞后续 batch 提交。
            async def print_loss(fwdbwd_future, optim_future, batch, epoch, batch_index, global_step):
                fwdbwd_result = await fwdbwd_future
                await optim_future

                # PyTrio 返回每个 token 的 logprob,这里按 loss 权重求加权平均 loss。
                logprobs = np.concatenate(
                    [output["logprobs"].tolist() for output in fwdbwd_result.loss_fn_outputs]
                )
                weights = np.concatenate(
                    [example.loss_fn_inputs["weights"].tolist() for example in batch]
                )
                loss = -np.dot(logprobs, weights) / weights.sum()
                swanlab.log(
                    {
                        "loss": float(loss),
                        "epoch": epoch + 1,
                        "batch": batch_index + 1,
                    },
                    step=global_step,
                )
                progress_bar.update(1)
                progress_bar.set_postfix(epoch=f"{epoch + 1}/{NUM_EPOCHS}", loss=f"{loss:.4f}")

            task = asyncio.create_task(
                print_loss(fwdbwd_future, optim_future, batch, epoch, batch_index, global_step)
            )
            print_queue.append(task)
        submit_bar.close()

        # 每个 epoch 结束前等待该 epoch 的所有后台日志任务完成。
        await asyncio.gather(*print_queue)

    progress_bar.close()
    print("Saving LoRA weights...")

    # 保存 LoRA 权重,并拿到带 LoRA 权重的采样客户端用于效果测试。
    sft_weights_future = await training_client.save_weights_for_sampler_async(name=WEIGHTS_NAME)
    sft_weights = await sft_weights_future

    # 创建训练前的基座模型采样客户端,用于对比训练前后的效果。
    base_sampling_client = await service_client.create_sampling_client_async(base_model=BASE_MODEL)
    # 创建训练后带 LoRA 权重的采样客户端,用于对比训练前后的效果。
    tuned_sampling_client = await service_client.create_sampling_client_async(
        base_model=BASE_MODEL,
        model_path=sft_weights.path,
    )

    test_prompts = [
        "你是谁?",
        "介绍一下你自己。",
        "朕今天偶感风寒,你觉得我该如何调养身体?",
    ]

    # 训练前后都用同一组 prompt 测试,便于观察 LoRA 微调带来的变化。
    await evaluate_client(base_sampling_client, tokenizer, test_prompts, title="Base model responses")
    await evaluate_client(tuned_sampling_client, tokenizer, test_prompts, title="Fine-tuned model responses")

    print(f"Saved weights name: {WEIGHTS_NAME},Weights path: {sft_weights.path}")
    swanlab_run.finish()


if __name__ == "__main__":
    start_main_time = time.time()
    asyncio.run(main())
    end_main_time = time.time()
    print("#" * 50)
    print("# all done")
    print(f"# train cost {end_main_time - start_main_time:.2f}s")
    print("#" * 50)

训练结果

经过 3 个 epoch 的训练,SwanLab 会记录每个 batch 的 loss 曲线。对比原始模型的回复(下面的 Base model responses),SFT 后的模型会更稳定地回答自己是甄嬛,并使用甄嬛相关的人设和语气进行对话。

运行结束后会看到类似输出:

Async training: 100%|████████████████████████████████████| 702/702 [..:.., ..batch/s, epoch=3/3, loss=...]
Saving LoRA weights...

Base model responses
User: 你是谁?
Assistant: (轻抚鬓边珠钗,眼波流转间似有千言万语,却只化作一声轻叹)

殿下,您问臣妾是谁?
臣妾本是江南贡女,入宫前只知采莲、浣纱、听曲。可自入了这紫禁城,便成了这深宫里的“甄嬛”。

User: 介绍一下你自己。
Assistant: (轻抚鬓边珠钗,眸光流转,似笑非笑地看向你)

公子且慢。在这深宫之中,身份如浮萍,人心似鬼蜮,谁又能说得清自己究竟是谁呢?

世人皆道本宫是宁嫔,是皇后,是端妃,是熹贵妃……这些不过是前朝旧事

User: 朕今天偶感风寒,你觉得我该如何调养身体?
Assistant: (轻移莲步至御案前,微微福身,神色间带着几分关切与小心翼翼)

陛下,您身子骨向来是金贵,怎的今日便受了风寒?这宫里的风,虽说是暖阁,可若是夜里漏风,或是夜里寒气重了些,都容易让人着凉。

臣妾以为,陛下此刻不宜再操劳政务


Fine-tuned model responses
User: 你是谁?
Assistant: 我是甄嬛,家父是大理寺少卿甄远道。

User: 介绍一下你自己。
Assistant: 我是甄嬛,家父是大理寺少卿甄远道。

User: 朕今天偶感风寒,你觉得我该如何调养身体?
Assistant: 风寒不宜用重药,皇上若觉得难受,可让太医送些安宫牛黄丸和紫雪丹在殿中,以化冰凌的方式融化,让药力慢慢挥发,皇上即可免受风寒之苦了。

本页目录