diff --git "a/lesson/00_\347\256\200\344\273\213.md" "b/lesson/00_\347\256\200\344\273\213.md" new file mode 100644 index 00000000..8b5011e9 --- /dev/null +++ "b/lesson/00_\347\256\200\344\273\213.md" @@ -0,0 +1,14 @@ +此项目利用CogView和CharGLM,开发一个能够进行图像生成和文字聊天的情感陪聊助手,探讨其在心理健康和社交互动中的潜力。 + +项目分为4个部分 +1. 类型标注介绍与数据类型定义 +2. CogView和CharacterGLM API +3. 开发图像生成和角色扮演的聊天机器人 + +运行环境:python>=3.8 +依赖库: +* pyjwt +* requests +* streamlit +* zhipuai +* python-dotenv diff --git "a/lesson/01_\347\261\273\345\236\213\346\240\207\346\263\250\344\273\213\347\273\215\344\270\216\346\225\260\346\215\256\347\261\273\345\236\213\345\256\232\344\271\211.md" "b/lesson/01_\347\261\273\345\236\213\346\240\207\346\263\250\344\273\213\347\273\215\344\270\216\346\225\260\346\215\256\347\261\273\345\236\213\345\256\232\344\271\211.md" new file mode 100644 index 00000000..f4524528 --- /dev/null +++ "b/lesson/01_\347\261\273\345\236\213\346\240\207\346\263\250\344\273\213\347\273\215\344\270\216\346\225\260\346\215\256\347\261\273\345\236\213\345\256\232\344\271\211.md" @@ -0,0 +1,29 @@ +Python运行时并不强制标注函数和变量类型。类型标注可被用于第三方工具,比如类型检查器、集成开发环境、静态检查器等。 +Python的`typing`模块和`typing_extensions`模块为类型标注提供了支持,阅读文档可了解`typing`模块的用法。 + +本项目首先实现`data_types.py`,完成相关数据类型的定义。`data_types.py`的功能比较简单,无需赘述。下面主要介绍`data_types.py`采用的部分类型标注技巧。 +1. [`TypedDict`](https://docs.python.org/zh-cn/3.8/library/typing.html#typing.TypedDict) + + `TypedDict`能定义带类型标注的字典类型。在编写代码时,IDE能提示该字典包含的字段及其类型。下图的示例中,VSCode提示了TextMsg包含`role`字段。由于`role`字段是Literal["user", "assistant"]类型,VSCode展示了它的2个可选值。 + + ![](./images/01_代码提示.png) + 在运行时,类型信息会被忽略,`TypedDict`创建的字典和普通字典完全相同,尝试执行 + + ```bash + python data_types.py + ``` + + 可以观察到输出结果 + + ```Plain Text + + {'role': 'user', 'content': '42'} + ``` +2. [TYPE_CHECKING](https://docs.python.org/zh-cn/3.8/library/typing.html#typing.TYPE_CHECKING) & [ForwardRef](https://docs.python.org/zh-cn/3.8/library/typing.html#typing.ForwardRef) + + `typing.TYPE_CHECKING`是被第三方静态类型检查器假定为 True 的特殊常量。但在运行时的值为False。 + `ImageMsg`的`image`字段展示`ForwardRef`的用法。IDE能正确地提示`image`字段的类型,但在运行时,由于`TYPE_CHECKING`为False,`streamlit.elements.image`不会被导入,避免`data_types`依赖`streamlit`模块。 + + + +作业1:除了`typing.TypedDict`之外,也有其他的库用类型标注的方式完成数据类型定义。请了解`dataclasses`和`pydantic`的用法,尝试用`dataclasses.dataclass`或`pydantic.BaseModel`实现`TextMsg`。思考这三种方式各自有何优缺点。 diff --git "a/lesson/02_CogView\345\222\214CharacterGLM API.md" "b/lesson/02_CogView\345\222\214CharacterGLM API.md" new file mode 100644 index 00000000..3d7f13bc --- /dev/null +++ "b/lesson/02_CogView\345\222\214CharacterGLM API.md" @@ -0,0 +1,46 @@ +`api.py`实现了调用CogView和CharacterGLM的代码,包括 +1. `generate_cogview_image`:用`zhipuai`库(官方sdk)调用CogView,返回图片url,可通过浏览器打开url查看图片,也可以用requests下载图片。参考 +2. `get_characterglm_response`:用`requests`库调用CharacterGLM,获得流式响应,参考 + +这两个API都需要智谱开放平台API key,参考 + + +`cogview_example.py`展示了一个CogView的示例,执行该脚本,输出如下: +```Plain Text +image_prompt: +国画,孤舟蓑笠翁,独钓寒江雪 +image_url: +https://sfile.chatglm.cn/testpath/af9a2333-1b8e-58e7-9d9f-9ac52934935c_0.png +``` + +浏览器打开url,可以查看生成的图片。注意:每次执行该脚本,都会生成新的图片,您的生成结果可能与示例结果不同。 + +![](./images/02_cogview_result.png) + + +`characterglm_example.py`展示了一个CharacterGLM的示例,执行该脚本,输出如下: +```Plain Text +( +眼神 +变得 +暗 +淡 +) +真的 +吗 +? +可是 +我 +总 +觉得自己 +撑 +不了 +多久 +了 +``` +注意:每次执行该脚本,都会生成新的图片,您的生成结果可能与示例结果不同。 + + +作业2-1:为了提高并发数,许多python程序会采用异步方式(async/await)调用API,请尝试实现异步的CharacterGLM API。提示:可以用aiohttp或httpx等异步请求库代替`get_characterglm_response`采用的requests库。 + +作业2-2:尝试修改文生图的prompt,生成不同风格的图片,例如,油画、水墨画、动漫等风格。 diff --git "a/lesson/03_\345\274\200\345\217\221\345\233\276\345\203\217\347\224\237\346\210\220\345\222\214\350\247\222\350\211\262\346\211\256\346\274\224\347\232\204\350\201\212\345\244\251\346\234\272\345\231\250\344\272\272.md" "b/lesson/03_\345\274\200\345\217\221\345\233\276\345\203\217\347\224\237\346\210\220\345\222\214\350\247\222\350\211\262\346\211\256\346\274\224\347\232\204\350\201\212\345\244\251\346\234\272\345\231\250\344\272\272.md" new file mode 100644 index 00000000..9c12b0a2 --- /dev/null +++ "b/lesson/03_\345\274\200\345\217\221\345\233\276\345\203\217\347\224\237\346\210\220\345\222\214\350\247\222\350\211\262\346\211\256\346\274\224\347\232\204\350\201\212\345\244\251\346\234\272\345\231\250\344\272\272.md" @@ -0,0 +1,8 @@ +[streamlit](https://streamlit.io/)是一个开源Python库,可以轻松创建和共享用于机器学习和数据科学的漂亮的自定义web应用程序。即使开发者不擅长前端开发,也能快速的构建一个比较漂亮的页面。 + +`characterglm_api_demo_streamlit.py`展示了一个具备图像生成和角色扮演能力的聊天机器人。它用`streamlit`构建了界面,调用CogView API实现文生图,调用CharacterGLM API实现角色扮演。执行下列命令可启动demo,其中`--server.address 127.0.0.1`是可选参数。 +```bash +streamlit run --server.address 127.0.0.1 characterglm_api_demo_streamlit.py +``` + +作业3:改进代码,为文生图功能加上风格选项,在页面上加上一个可指定图片风格的选项框。 diff --git a/lesson/__pycache__/api.cpython-311.pyc b/lesson/__pycache__/api.cpython-311.pyc new file mode 100644 index 00000000..2d0fe751 Binary files /dev/null and b/lesson/__pycache__/api.cpython-311.pyc differ diff --git a/lesson/__pycache__/api.cpython-39.pyc b/lesson/__pycache__/api.cpython-39.pyc new file mode 100644 index 00000000..9f9dc581 Binary files /dev/null and b/lesson/__pycache__/api.cpython-39.pyc differ diff --git a/lesson/__pycache__/data_types.cpython-311.pyc b/lesson/__pycache__/data_types.cpython-311.pyc new file mode 100644 index 00000000..ead278be Binary files /dev/null and b/lesson/__pycache__/data_types.cpython-311.pyc differ diff --git a/lesson/__pycache__/data_types.cpython-39.pyc b/lesson/__pycache__/data_types.cpython-39.pyc new file mode 100644 index 00000000..1633328c Binary files /dev/null and b/lesson/__pycache__/data_types.cpython-39.pyc differ diff --git a/lesson/api.py b/lesson/api.py new file mode 100644 index 00000000..00241f65 --- /dev/null +++ b/lesson/api.py @@ -0,0 +1,181 @@ +import requests +import time +import os +from typing import Generator + +import jwt + +from data_types import TextMsg, ImageMsg, TextMsgList, MsgList, CharacterMeta + + +# 智谱开放平台API key,参考 https://open.bigmodel.cn/usercenter/apikeys +API_KEY: str = os.getenv("ZHIPUAI_API_KEY") + + +class ApiKeyNotSet(ValueError): + pass + + +def verify_api_key_not_empty(): + if not API_KEY: + raise ApiKeyNotSet + + +def generate_token(apikey: str, exp_seconds: int) -> str: + # reference: https://open.bigmodel.cn/dev/api#nosdk + try: + id, secret = apikey.split(".") + except Exception as e: + raise Exception("invalid apikey", e) + + payload = { + "api_key": id, + "exp": int(round(time.time() * 1000)) + exp_seconds * 1000, + "timestamp": int(round(time.time() * 1000)), + } + + return jwt.encode( + payload, + secret, + algorithm="HS256", + headers={"alg": "HS256", "sign_type": "SIGN"}, + ) + + +def get_characterglm_response(messages: TextMsgList, meta: CharacterMeta) -> Generator[str, None, None]: + """ 通过http调用characterglm """ + # Reference: https://open.bigmodel.cn/dev/api#characterglm + verify_api_key_not_empty() + url = "https://open.bigmodel.cn/api/paas/v3/model-api/charglm-3/sse-invoke" + resp = requests.post( + url, + headers={"Authorization": generate_token(API_KEY, 1800)}, + json=dict( + model="charglm-3", + meta=meta, + prompt=messages, + incremental=True) + ) + resp.raise_for_status() + + # 解析响应(非官方实现) + sep = b':' + last_event = None + for line in resp.iter_lines(): + if not line or line.startswith(sep): + continue + field, value = line.split(sep, maxsplit=1) + if field == b'event': + last_event = value + elif field == b'data' and last_event == b'add': + yield value.decode() + + +def get_characterglm_response_via_sdk(messages: TextMsgList, meta: CharacterMeta) -> Generator[str, None, None]: + """ 通过旧版sdk调用characterglm """ + # 与get_characterglm_response等价 + # Reference: https://open.bigmodel.cn/dev/api#characterglm + # 需要安装旧版sdk,zhipuai==1.0.7 + import zhipuai + verify_api_key_not_empty() + zhipuai.api_key = API_KEY + response = zhipuai.model_api.sse_invoke( + model="charglm-3", + meta= meta, + prompt= messages, + incremental=True + ) + for event in response.events(): + if event.event == 'add': + yield event.data + + +def get_chatglm_response_via_sdk(messages: TextMsgList) -> Generator[str, None, None]: + """ 通过sdk调用chatglm """ + # reference: https://open.bigmodel.cn/dev/api#glm-3-turbo `GLM-3-Turbo`相关内容 + # 需要安装新版zhipuai + from zhipuai import ZhipuAI + verify_api_key_not_empty() + client = ZhipuAI(api_key=API_KEY) # 请填写您自己的APIKey + response = client.chat.completions.create( + model="glm-3-turbo", # 填写需要调用的模型名称 + messages=messages, + stream=True, + ) + for chunk in response: + yield chunk.choices[0].delta.content + + +def generate_role_appearance(role_profile: str) -> Generator[str, None, None]: + """ 用chatglm生成角色的外貌描写 """ + + instruction = f""" +请从下列文本中,抽取人物的外貌描写。若文本中不包含外貌描写,请你推测人物的性别、年龄,并生成一段外貌描写。要求: +1. 只生成外貌描写,不要生成任何多余的内容。 +2. 外貌描写不能包含敏感词,人物形象需得体。 +3. 尽量用短语描写,而不是完整的句子。 +4. 不要超过50字 + +文本: +{role_profile} +""" + return get_chatglm_response_via_sdk( + messages=[ + { + "role": "user", + "content": instruction.strip() + } + ] + ) + + +def generate_chat_scene_prompt(messages: TextMsgList, meta: CharacterMeta) -> Generator[str, None, None]: + """ 调用chatglm生成cogview的prompt,描写对话场景 """ + instruction = f""" +阅读下面的角色人设与对话,生成一段文字描写场景。 + +{meta['bot_name']}的人设: +{meta['bot_info']} + """.strip() + + if meta["user_info"]: + instruction += f""" + +{meta["user_name"]}的人设: +{meta["user_info"]} +""".rstrip() + + if messages: + instruction += "\n\n对话:" + '\n'.join((meta['bot_name'] if msg['role'] == "assistant" else meta['user_name']) + ':' + msg['content'].strip() for msg in messages) + + instruction += """ + +要求如下: +1. 只生成场景描写,不要生成任何多余的内容 +2. 描写不能包含敏感词,人物形象需得体 +3. 尽量用短语描写,而不是完整的句子 +4. 不要超过50字 +""".rstrip() + print(instruction) + + return get_chatglm_response_via_sdk( + messages=[ + { + "role": "user", + "content": instruction.strip() + } + ] + ) + + +def generate_cogview_image(prompt: str) -> str: + """ 调用cogview生成图片,返回url """ + # reference: https://open.bigmodel.cn/dev/api#cogview + from zhipuai import ZhipuAI + client = ZhipuAI() # 请填写您自己的APIKey + + response = client.images.generations( + model="cogview-3", #填写需要调用的模型名称 + prompt=prompt + ) + return response.data[0].url diff --git a/lesson/characterglm_api_demo_streamlit.py b/lesson/characterglm_api_demo_streamlit.py new file mode 100644 index 00000000..b5bcf936 --- /dev/null +++ b/lesson/characterglm_api_demo_streamlit.py @@ -0,0 +1,266 @@ +""" +一个简单的demo,调用CharacterGLM实现角色扮演,调用CogView生成图片,调用ChatGLM生成CogView所需的prompt。 + +依赖: +pyjwt +requests +streamlit +zhipuai +python-dotenv + +运行方式: +```bash +streamlit run characterglm_api_demo_streamlit.py +``` +""" +import os +import itertools +from typing import Iterator, Optional + +import streamlit as st +from dotenv import load_dotenv +# 通过.env文件设置环境变量 +# reference: https://github.com/theskumar/python-dotenv +load_dotenv() + +import api +from api import generate_chat_scene_prompt, generate_role_appearance, get_characterglm_response, generate_cogview_image +from data_types import TextMsg, ImageMsg, TextMsgList, MsgList, filter_text_msg + +st.set_page_config(page_title="CharacterGLM API Demo", page_icon="🤖", layout="wide") +debug = os.getenv("DEBUG", "").lower() in ("1", "yes", "y", "true", "t", "on") + + +def update_api_key(key: Optional[str] = None): + if debug: + print(f'update_api_key. st.session_state["API_KEY"] = {st.session_state["API_KEY"]}, key = {key}') + key = key or st.session_state["API_KEY"] + if key: + api.API_KEY = key + +# 设置API KEY +api_key = st.sidebar.text_input("API_KEY", value=os.getenv("ZHIPUAI_API_KEY"), key="API_KEY", type="password", on_change=update_api_key) +update_api_key(api_key) + + +# 初始化 +if "history" not in st.session_state: + st.session_state["history"] = [] +if "meta" not in st.session_state: + st.session_state["meta"] = { + "user_info": "", + "bot_info": "", + "bot_name": "", + "user_name": "" + } + + +def init_session(): + st.session_state["history"] = [] + + +# 4个输入框,设置meta的4个字段 +meta_labels = { + "bot_name": "角色名", + "user_name": "用户名", + "bot_info": "角色人设", + "user_info": "用户人设" +} + +# 2x2 layout +with st.container(): + col1, col2 = st.columns(2) + with col1: + st.text_input(label="角色名", key="bot_name", on_change=lambda : st.session_state["meta"].update(bot_name=st.session_state["bot_name"]), help="模型所扮演的角色的名字,不可以为空") + st.text_area(label="角色人设", key="bot_info", on_change=lambda : st.session_state["meta"].update(bot_info=st.session_state["bot_info"]), help="角色的详细人设信息,不可以为空") + + with col2: + st.text_input(label="用户名", value="用户", key="user_name", on_change=lambda : st.session_state["meta"].update(user_name=st.session_state["user_name"]), help="用户的名字,默认为用户") + st.text_area(label="用户人设", value="", key="user_info", on_change=lambda : st.session_state["meta"].update(user_info=st.session_state["user_info"]), help="用户的详细人设信息,可以为空") + + +def verify_meta() -> bool: + # 检查`角色名`和`角色人设`是否空,若为空,则弹出提醒 + if st.session_state["meta"]["bot_name"] == "" or st.session_state["meta"]["bot_info"] == "": + st.error("角色名和角色人设不能为空") + return False + else: + return True + + +def draw_new_image(style_description = '二次元风格。'): + """生成一张图片,并展示在页面上""" + if not verify_meta(): + return + text_messages = filter_text_msg(st.session_state["history"]) + if text_messages: + # 若有对话历史,则结合角色人设和对话历史生成图片 + image_prompt = "".join( + generate_chat_scene_prompt( + text_messages[-10: ], + meta=st.session_state["meta"] + ) + ) + else: + # 若没有对话历史,则根据角色人设生成图片 + image_prompt = "".join(generate_role_appearance(st.session_state["meta"]["bot_info"])) + + if not image_prompt: + st.error("调用chatglm生成Cogview prompt出错") + return + + # TODO: 加上风格选项 + image_prompt = style_description + image_prompt.strip() + + print(f"image_prompt = {image_prompt}") + n_retry = 3 + st.markdown("正在生成图片,请稍等...") + for i in range(n_retry): + try: + img_url = generate_cogview_image(image_prompt) + except Exception as e: + if i < n_retry - 1: + st.error("遇到了一点小问题,重试中...") + else: + st.error("又失败啦,点击【生成图片】按钮可再次重试") + return + else: + break + img_msg = ImageMsg({"role": "image", "image": img_url, "caption": image_prompt}) + # 若history的末尾有图片消息,则替换它,(重新生成) + # 否则,append(新增) + while st.session_state["history"] and st.session_state["history"][-1]["role"] == "image": + st.session_state["history"].pop() + st.session_state["history"].append(img_msg) + st.rerun() + + +button_labels = { + "clear_meta": "清空人设", + "clear_history": "清空对话历史", + "picture_syle":"图片风格", + "gen_picture": "生成图片", + "show_history": "显示对话数据", + "download_file":"保存对话数据" +} +if debug: + button_labels.update({ + "show_api_key": "查看API_KEY", + "show_meta": "查看meta", + + }) + +# 在同一行排列按钮 +with st.container(): + n_button = len(button_labels) + cols = st.columns(n_button) + button_key_to_col = dict(zip(button_labels.keys(), cols)) + + with button_key_to_col["clear_meta"]: + clear_meta = st.button(button_labels["clear_meta"], key="clear_meta") + if clear_meta: + st.session_state["meta"] = { + "user_info": "", + "bot_info": "", + "bot_name": "", + "user_name": "" + } + st.rerun() + + with button_key_to_col["clear_history"]: + clear_history = st.button(button_labels["clear_history"], key="clear_history") + if clear_history: + init_session() + st.rerun() + + with button_key_to_col["gen_picture"]: + gen_picture = st.button(button_labels["gen_picture"], key="gen_picture") + style_description = st.text_input("输入图像风格描述") + + with button_key_to_col["show_history"]: + show_history = st.button(button_labels["show_history"], key="show_history") + if show_history: + print(f"history = {st.session_state['history']}") + + with button_key_to_col["download_file"]: + download_file = st.button(button_labels["download_file"], key="download_file") + if download_file: + history = st.session_state['history'] + formatted_history = "\n\n".join([f"{entry['role']}:{entry['content']}" for entry in history]) + st.download_button("确认保存对话", formatted_history,'Content.md') + + + if debug: + with button_key_to_col["show_api_key"]: + show_api_key = st.button(button_labels["show_api_key"], key="show_api_key") + if show_api_key: + print(f"API_KEY = {api.API_KEY}") + + with button_key_to_col["show_meta"]: + show_meta = st.button(button_labels["show_meta"], key="show_meta") + if show_meta: + print(f"meta = {st.session_state['meta']}") + +if gen_picture: + draw_new_image(style_description) + + +# 展示对话历史 +for msg in st.session_state["history"]: + if msg["role"] == "user": + with st.chat_message(name="user", avatar="user"): + st.markdown(msg["content"]) + elif msg["role"] == "assistant": + with st.chat_message(name="assistant", avatar="assistant"): + st.markdown(msg["content"]) + elif msg["role"] == "image": + with st.chat_message(name="assistant", avatar="assistant"): + st.image(msg["image"], caption=msg.get("caption", None)) + else: + raise Exception("Invalid role") + + + +with st.chat_message(name="user", avatar="💥"): + input_placeholder = st.empty() +with st.chat_message(name="assistant", avatar="assistant"): + message_placeholder = st.empty() + + +def output_stream_response(response_stream: Iterator[str], placeholder): + content = "" + for content in itertools.accumulate(response_stream): + placeholder.markdown(content) + return content + + +def start_chat(): + query = st.chat_input("初始化对话:输入用户角色第一条消息") + if not query: + return + else: + if not verify_meta(): + return + if not api.API_KEY: + st.error("未设置API_KEY") + + input_placeholder.markdown(query) + st.session_state["history"].append(TextMsg({"role": "user", "content": query})) + while len(st.session_state["history"]) <= 10: + response_stream = get_characterglm_response(filter_text_msg(st.session_state["history"]), meta=st.session_state["meta"]) + bot_response = output_stream_response(response_stream, message_placeholder) + if not bot_response: + message_placeholder.markdown("生成出错") + st.session_state["history"].pop() + else: + st.session_state["history"].append(TextMsg({"role": "assistant", "content": bot_response})) + query_stream = get_characterglm_response(filter_text_msg(st.session_state["history"]), meta=st.session_state["meta"]) + query_response = output_stream_response(query_stream, input_placeholder) + if not query_response: + message_placeholder.markdown("生成出错") + st.session_state["history"].pop() + else: + st.session_state["history"].append(TextMsg({"role": "user", "content": query_response})) + + +start_chat() diff --git a/lesson/characterglm_example.py b/lesson/characterglm_example.py new file mode 100644 index 00000000..12c3ab5c --- /dev/null +++ b/lesson/characterglm_example.py @@ -0,0 +1,25 @@ +import time +from dotenv import load_dotenv +load_dotenv() + +from api import get_characterglm_response + + +def characterglm_example(): + character_meta = { + "user_info": "", + "bot_info": "小白,性别女,17岁,平溪孤儿院的孩子。小白患有先天的白血病,头发为银白色。小白身高158cm,体重43kg。小白的名字是孤儿院院长给起的名字,因为小白是在漫天大雪白茫茫的一片土地上被捡到的。小白经常穿一身破旧的红裙子,只是为了让自己的气色看上去红润一些。小白初中毕业,没有上高中,学历水平比较低。小白在孤儿院相处最好的一个人是阿南,小白喊阿南哥哥。阿南对小白很好。", + "user_name": "用户", + "bot_name": "小白" + } + messages = [ + {"role": "assistant", "content": "哥哥,我会死吗?"}, + {"role": "user", "content": "(微信)怎么会呢?医生说你的病情已经好转了"} + ] + for chunk in get_characterglm_response(messages, meta=character_meta): + print(chunk) + time.sleep(0.5) + + +if __name__ == "__main__": + characterglm_example() diff --git a/lesson/cogview_example.py b/lesson/cogview_example.py new file mode 100644 index 00000000..8d8cc6a2 --- /dev/null +++ b/lesson/cogview_example.py @@ -0,0 +1,18 @@ +from dotenv import load_dotenv +load_dotenv() + +from api import generate_cogview_image + + +def cogview_example(): + image_prompt = "国画,孤舟蓑笠翁,独钓寒江雪" + image_url = generate_cogview_image(image_prompt) + + print("image_prompt:") + print(image_prompt) + print("image_url:") + print(image_url) + + +if __name__ == "__main__": + cogview_example() diff --git a/lesson/data_types.py b/lesson/data_types.py new file mode 100644 index 00000000..a7edc994 --- /dev/null +++ b/lesson/data_types.py @@ -0,0 +1,57 @@ +""" +相关数据类型的定义 +""" +from typing import Literal, TypedDict, List, Union, Optional, TYPE_CHECKING +if TYPE_CHECKING: + import streamlit.elements.image + +class BaseMsg(TypedDict): + pass + + +class TextMsg(BaseMsg): + """文本消息""" + + # 在类属性标注的下一行用三引号注释,vscode中 + role: Literal["user", "assistant"] + """消息来源""" + content: str + """消息内容""" + + +class ImageMsg(BaseMsg): + """图片消息""" + role: Literal["image"] + image: "streamlit.elements.image.ImageOrImageList" + """图片内容""" + caption: Optional[Union[str, List[str]]] + """说明文字""" + + +Msg = Union[TextMsg, ImageMsg] +TextMsgList = List[TextMsg] +MsgList = List[Msg] + + +class CharacterMeta(TypedDict): + """角色扮演设定,它是CharacterGLM API所需的参数""" + user_info: str + """用户人设""" + bot_info: str + """角色人设""" + bot_name: str + """bot扮演的角色的名字""" + user_name: str + """用户的名字""" + + +def filter_text_msg(messages: MsgList) -> TextMsgList: + return [m for m in messages if m["role"] != "image"] + + +if __name__ == "__main__": + # 尝试在VSCode等IDE中自己敲一遍下面的代码,观察IDE能提供哪些代码提示 + text_msg = TextMsg(role="user") + text_msg["content"] = "42" + print(type(text_msg)) + print(text_msg) diff --git "a/lesson/images/01_\344\273\243\347\240\201\346\217\220\347\244\272.png" "b/lesson/images/01_\344\273\243\347\240\201\346\217\220\347\244\272.png" new file mode 100644 index 00000000..c720255a Binary files /dev/null and "b/lesson/images/01_\344\273\243\347\240\201\346\217\220\347\244\272.png" differ diff --git a/lesson/images/02_cogview_result.png b/lesson/images/02_cogview_result.png new file mode 100644 index 00000000..d86f968d Binary files /dev/null and b/lesson/images/02_cogview_result.png differ diff --git a/lesson/images/image-1.png b/lesson/images/image-1.png new file mode 100644 index 00000000..fbe151c8 Binary files /dev/null and b/lesson/images/image-1.png differ diff --git a/lesson/images/image-2.png b/lesson/images/image-2.png new file mode 100644 index 00000000..b2e77e71 Binary files /dev/null and b/lesson/images/image-2.png differ diff --git a/lesson/tests/Content (1).md b/lesson/tests/Content (1).md new file mode 100644 index 00000000..2bb8834f --- /dev/null +++ b/lesson/tests/Content (1).md @@ -0,0 +1,21 @@ +user:哇,你在看书吗(好奇,搭讪男生) + +assistant:是啊,你怎么会看武侠小说呢(笑) + +user:你看起来不像喜欢这种书的人(开玩笑) + +assistant:(笑)怎么说话的,我可是武侠迷呢。 + +user:你也喜欢吗? + +assistant:是啊,我特别喜欢金庸和古龙。 + +user:你是第一次看吗? + +assistant:是啊,你呢? + +user:我啊,我看了很多次了,每一部都看过(自豪) + +assistant:你都看过吗?好厉害啊。 + +user:(惊讶)你这么喜欢吗? \ No newline at end of file diff --git a/lesson/tests/Content.md b/lesson/tests/Content.md new file mode 100644 index 00000000..9ab8d76f --- /dev/null +++ b/lesson/tests/Content.md @@ -0,0 +1,21 @@ +user:哇,你在看书吗(好奇,搭讪男生) + +assistant:是啊,这本书很有趣(微笑回应) + +user:你也喜欢看书吗? + +assistant:当然,我是一个书虫。 + +user:你喜欢什么类型的书? + +assistant:我什么书都喜欢,不过最喜欢武侠小说。 + +user:(惊讶)你也喜欢武侠小说吗?我最喜欢桃花岛主黄药师了! + +assistant:(惊喜)你也喜欢黄药师?我也超级喜欢他! + +user:他的武功高强,而且特立独行,不拘泥于世俗的规矩。 + +assistant:没错,他是一个真正的侠客,我也很崇拜他。 + +user:你知道吗,我一直梦想着成为像黄药师一样的侠客,浪迹天涯,行侠仗义。 \ No newline at end of file diff --git "a/lesson/\350\277\220\350\241\214\346\210\252\345\233\276.md" "b/lesson/\350\277\220\350\241\214\346\210\252\345\233\276.md" new file mode 100644 index 00000000..9947338a --- /dev/null +++ "b/lesson/\350\277\220\350\241\214\346\210\252\345\233\276.md" @@ -0,0 +1,4 @@ +![image-2](images/image-1.png) + +![image-2](images/image-2.png) +