Skip to content

Deveplop/update character glm #103

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions lesson/00_简介.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
此项目利用CogView和CharGLM,开发一个能够进行图像生成和文字聊天的情感陪聊助手,探讨其在心理健康和社交互动中的潜力。

项目分为4个部分
1. 类型标注介绍与数据类型定义
2. CogView和CharacterGLM API
3. 开发图像生成和角色扮演的聊天机器人

运行环境:python>=3.8
依赖库:
* pyjwt
* requests
* streamlit
* zhipuai
* python-dotenv
29 changes: 29 additions & 0 deletions lesson/01_类型标注介绍与数据类型定义.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
Python运行时并不强制标注函数和变量类型。类型标注可被用于第三方工具,比如类型检查器、集成开发环境、静态检查器等。
Python的`typing`模块和`typing_extensions`模块为类型标注提供了支持,阅读文档<https://docs.python.org/zh-cn/3.8/library/typing.html>可了解`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
<class 'dict'>
{'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`。思考这三种方式各自有何优缺点。
46 changes: 46 additions & 0 deletions lesson/02_CogView和CharacterGLM API.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
`api.py`实现了调用CogView和CharacterGLM的代码,包括
1. `generate_cogview_image`:用`zhipuai`库(官方sdk)调用CogView,返回图片url,可通过浏览器打开url查看图片,也可以用requests下载图片。参考<https://open.bigmodel.cn/dev/api#cogview>
2. `get_characterglm_response`:用`requests`库调用CharacterGLM,获得流式响应,参考<https://open.bigmodel.cn/dev/api#characterglm>

这两个API都需要智谱开放平台API key,参考 <https://open.bigmodel.cn/usercenter/apikeys>


`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,生成不同风格的图片,例如,油画、水墨画、动漫等风格。
Original file line number Diff line number Diff line change
@@ -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:改进代码,为文生图功能加上风格选项,在页面上加上一个可指定图片风格的选项框。
Binary file added lesson/__pycache__/api.cpython-311.pyc
Binary file not shown.
Binary file added lesson/__pycache__/api.cpython-39.pyc
Binary file not shown.
Binary file added lesson/__pycache__/data_types.cpython-311.pyc
Binary file not shown.
Binary file added lesson/__pycache__/data_types.cpython-39.pyc
Binary file not shown.
181 changes: 181 additions & 0 deletions lesson/api.py
Original file line number Diff line number Diff line change
@@ -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
Loading