From 9972fb7cedcd7af57e580252d3c3fd316b9f3565 Mon Sep 17 00:00:00 2001 From: chyroc Date: Tue, 24 Sep 2024 19:12:39 +0800 Subject: [PATCH 1/5] feat: add chat v3 api --- cozepy/__init__.py | 4 +- cozepy/chat.py | 140 +++++++++++++++++++++++++++++++++ cozepy/conversation.py | 113 +-------------------------- cozepy/coze.py | 10 +++ cozepy/model.py | 172 ++++++++++++++++++++++++++++++++++++++++- cozepy/request.py | 9 ++- tests/test_bot.py | 2 - 7 files changed, 331 insertions(+), 119 deletions(-) create mode 100644 cozepy/chat.py diff --git a/cozepy/__init__.py b/cozepy/__init__.py index fe3a6d2..47f3d69 100644 --- a/cozepy/__init__.py +++ b/cozepy/__init__.py @@ -2,15 +2,15 @@ from .config import COZE_COM_BASE_URL, COZE_CN_BASE_URL from .coze import Coze from .model import TokenPaged, NumberPaged -from .conversation import ( +from .model import ( MessageRole, MessageType, MessageContentType, MessageObjectStringType, MessageObjectString, Message, - Conversation, ) +from .conversation import Conversation __all__ = [ "ApplicationOAuth", diff --git a/cozepy/chat.py b/cozepy/chat.py new file mode 100644 index 0000000..372b651 --- /dev/null +++ b/cozepy/chat.py @@ -0,0 +1,140 @@ +import json +from enum import Enum +from typing import Dict, List, Iterator + +from .auth import Auth +from .model import Message, Chat, MessageResponse, CozeModel +from .request import Requester + + +class Event(str, Enum): + # Event for creating a conversation, indicating the start of the conversation. + # 创建对话的事件,表示对话开始。 + conversation_chat_created = "conversation.chat.created" + + # The server is processing the conversation. + # 服务端正在处理对话。 + conversation_chat_in_progress = "conversation.chat.in_progress" + + # Incremental message, usually an incremental message when type=answer. + # 增量消息,通常是 type=answer 时的增量消息。 + conversation_message_delta = "conversation.message.delta" + + # The message has been completely replied to. At this point, the streaming package contains the spliced results of all message.delta, and each message is in a completed state. + # message 已回复完成。此时流式包中带有所有 message.delta 的拼接结果,且每个消息均为 completed 状态。 + conversation_message_completed = "conversation.message.completed" + + # The conversation is completed. + # 对话完成。 + conversation_chat_completed = "conversation.chat.completed" + + # This event is used to mark a failed conversation. + # 此事件用于标识对话失败。 + conversation_chat_failed = "conversation.chat.failed" + + # The conversation is interrupted and requires the user to report the execution results of the tool. + # 对话中断,需要使用方上报工具的执行结果。 + conversation_chat_requires_action = "conversation.chat.requires_action" + + # Error events during the streaming response process. For detailed explanations of code and msg, please refer to Error codes. + # 流式响应过程中的错误事件。关于 code 和 msg 的详细说明,可参考错误码。 + error = "error" + + # The streaming response for this session ended normally. + # 本次会话的流式返回正常结束。 + done = "done" + + +class ChatEvent(CozeModel): + event: Event + chat: Chat = None + message: MessageResponse = None + + +class ChatIterator(object): + def __init__(self, iters: Iterator[bytes]): + self._iters = iters + + def __iter__(self): + return self + + def __next__(self) -> ChatEvent: + event = "" + data = "" + line = "" + times = 0 + + while times < 2: + line = next(self._iters).decode("utf-8") + if line == "": + continue + elif line.startswith("event:"): + if event == "": + event = line[6:] + else: + raise Exception(f"invalid event: {line}") + elif line.startswith("data:"): + if data == "": + data = line[5:] + else: + raise Exception(f"invalid event: {line}") + else: + raise Exception(f"invalid event: {line}") + + times += 1 + + if event == Event.done: + raise StopIteration + elif event == Event.error: + raise Exception(f"error event: {line}") + elif event in [Event.conversation_message_delta, Event.conversation_message_completed]: + return ChatEvent(event=event, message=MessageResponse.model_validate(json.loads(data))) + elif event in [ + Event.conversation_chat_created, + Event.conversation_chat_in_progress, + Event.conversation_chat_completed, + Event.conversation_chat_failed, + Event.conversation_chat_requires_action, + ]: + return ChatEvent(event=event, chat=Chat.model_validate(json.loads(data))) + else: + raise Exception(f"unknown event: {line}") + + +class ChatClient(object): + def __init__(self, base_url: str, auth: Auth, requester: Requester): + self._base_url = base_url + self._auth = auth + self._requester = requester + + def chat_v3( + self, + *, + bot_id: str, + user_id: str, + additional_messages: List[Message] = None, + stream: bool = False, + custom_variables: Dict[str, str] = None, + auto_save_history: bool = True, + meta_data: Dict[str, str] = None, + conversation_id: str = None, + ) -> Chat | ChatIterator: + """ + Create a conversation. + Conversation is an interaction between a bot and a user, including one or more messages. + """ + url = f"{self._base_url}/v3/chat" + body = { + "bot_id": bot_id, + "user_id": user_id, + "additional_messages": [i.model_dump() for i in additional_messages] if additional_messages else [], + "stream": stream, + "custom_variables": custom_variables, + "auto_save_history": auto_save_history, + "conversation_id": conversation_id if conversation_id else None, + "meta_data": meta_data, + } + if not stream: + return self._requester.request("post", url, Chat, body=body, stream=stream) + + return ChatIterator(self._requester.request("post", url, Chat, body=body, stream=stream)) diff --git a/cozepy/conversation.py b/cozepy/conversation.py index 91a7c1e..9dcbdad 100644 --- a/cozepy/conversation.py +++ b/cozepy/conversation.py @@ -1,119 +1,10 @@ -from enum import Enum -from typing import Dict, List, Optional +from typing import Dict, List from .auth import Auth -from .model import CozeModel +from .model import CozeModel, Message from .request import Requester -class MessageRole(str, Enum): - # Indicates that the content of the message is sent by the user. - user = "user" - # Indicates that the content of the message is sent by the bot. - assistant = "assistant" - - -class MessageType(str, Enum): - # User input content. - # 用户输入内容。 - question = "question" - # The message content returned by the Bot to the user, supporting incremental return. If the workflow is bound to a message node, there may be multiple answer scenarios, and the end flag of the streaming return can be used to determine that all answers are completed. - # Bot 返回给用户的消息内容,支持增量返回。如果工作流绑定了消息节点,可能会存在多 answer 场景,此时可以用流式返回的结束标志来判断所有 answer 完成。 - answer = "answer" - # Intermediate results of the function (function call) called during the Bot conversation process. - # Bot 对话过程中调用函数(function call)的中间结果。 - function_call = "function_call" - # Results returned after calling the tool (function call). - # 调用工具 (function call)后返回的结果。 - tool_output = "tool_output" - # Results returned after calling the tool (function call). - # 调用工具 (function call)后返回的结果。 - tool_response = "tool_response" - # If the user question suggestion switch is turned on in the Bot configuration, the reply content related to the recommended questions will be returned. - # 如果在 Bot 上配置打开了用户问题建议开关,则会返回推荐问题相关的回复内容。不支持在请求中作为入参。 - follow_up = "follow_up" - # In the scenario of multiple answers, the server will return a verbose package, and the corresponding content is in JSON format. content.msg_type = generate_answer_finish represents that all answers have been replied to. - # 多 answer 场景下,服务端会返回一个 verbose 包,对应的 content 为 JSON 格式,content.msg_type =generate_answer_finish 代表全部 answer 回复完成。不支持在请求中作为入参。 - verbose = "verbose" - - -class MessageContentType(str, Enum): - # Text. - # 文本。 - text = "text" - # Multimodal content, that is, a combination of text and files, or a combination of text and images. - # 多模态内容,即文本和文件的组合、文本和图片的组合。 - object_string = "object_string" - # message card. This enum value only appears in the interface response and is not supported as an input parameter. - # 卡片。此枚举值仅在接口响应中出现,不支持作为入参。 - card = "card" - - -class MessageObjectStringType(str, Enum): - """ - The content type of the multimodal message. - """ - - text = "text" - file = "file" - image = "image" - - -class MessageObjectString(CozeModel): - # The content type of the multimodal message. - # 多模态消息内容类型 - type: MessageObjectStringType - # Text content. Required when type is text. - # 文本内容。 - text: str - # The ID of the file or image content. - # 在 type 为 file 或 image 时,file_id 和 file_url 应至少指定一个。 - file_id: str - # The online address of the file or image content.
Must be a valid address that is publicly accessible. - # file_id or file_url must be specified when type is file or image. - # 文件或图片内容的在线地址。必须是可公共访问的有效地址。 - # 在 type 为 file 或 image 时,file_id 和 file_url 应至少指定一个。 - file_url: str - - -class Message(CozeModel): - # The entity that sent this message. - role: MessageRole - # The type of message. - # type: # MessageType - # The content of the message. It supports various types of content, including plain text, multimodal (a mix of text, images, and files), message cards, and more. - # 消息的内容,支持纯文本、多模态(文本、图片、文件混合输入)、卡片等多种类型的内容。 - content: str - # The type of message content. - # 消息内容的类型 - content_type: MessageContentType - # Additional information when creating a message, and this additional information will also be returned when retrieving messages. - # Custom key-value pairs should be specified in Map object format, with a length of 16 key-value pairs. The length of the key should be between 1 and 64 characters, and the length of the value should be between 1 and 512 characters. - # 创建消息时的附加消息,获取消息时也会返回此附加消息。 - # 自定义键值对,应指定为 Map 对象格式。长度为 16 对键值对,其中键(key)的长度范围为 1~64 个字符,值(value)的长度范围为 1~512 个字符。 - meta_data: Optional[Dict[str, str]] = None - - @staticmethod - def user_text_message(content: str, meta_data: Optional[Dict[str, str]] = None) -> "Message": - return Message( - role=MessageRole.user, - type=MessageType.question, - content=content, - content_type=MessageContentType.text, - meta_data=meta_data, - ) - - @staticmethod - def assistant_text_message(content: str, meta_data: Optional[Dict[str, str]] = None) -> "Message": - return Message( - role=MessageRole.assistant, - type=MessageType.answer, - content=content, - content_type=MessageContentType.text, - meta_data=meta_data, - ) - - class Conversation(CozeModel): id: str created_at: int diff --git a/cozepy/coze.py b/cozepy/coze.py index a4cbe44..2c9b0d0 100644 --- a/cozepy/coze.py +++ b/cozepy/coze.py @@ -8,6 +8,7 @@ from .bot import BotClient from .workspace import WorkspaceClient from .conversation import ConversationClient + from .chat import ChatClient class Coze(object): @@ -24,6 +25,7 @@ def __init__( self._bot = None self._workspace = None self._conversation = None + self._chat = None @property def bot(self) -> "BotClient": @@ -48,3 +50,11 @@ def conversation(self) -> "ConversationClient": self._conversation = ConversationClient(self._base_url, self._auth, self._requester) return self._conversation + + @property + def chat(self) -> "ChatClient": + if not self._chat: + from cozepy.chat import ChatClient + + self._chat = ChatClient(self._base_url, self._auth, self._requester) + return self._chat diff --git a/cozepy/model.py b/cozepy/model.py index 351e068..a7ddba5 100644 --- a/cozepy/model.py +++ b/cozepy/model.py @@ -1,4 +1,5 @@ -from typing import TypeVar, Generic, List +from enum import Enum +from typing import TypeVar, Generic, List, Optional, Dict from pydantic import BaseModel, ConfigDict @@ -49,3 +50,172 @@ def __repr__(self): return ( f"NumberPaged(items={self.items}, page_num={self.page_num}, page_size={self.page_size}, total={self.total})" ) + + +class MessageRole(str, Enum): + # Indicates that the content of the message is sent by the user. + user = "user" + # Indicates that the content of the message is sent by the bot. + assistant = "assistant" + + +class MessageType(str, Enum): + # User input content. + # 用户输入内容。 + question = "question" + # The message content returned by the Bot to the user, supporting incremental return. If the workflow is bound to a message node, there may be multiple answer scenarios, and the end flag of the streaming return can be used to determine that all answers are completed. + # Bot 返回给用户的消息内容,支持增量返回。如果工作流绑定了消息节点,可能会存在多 answer 场景,此时可以用流式返回的结束标志来判断所有 answer 完成。 + answer = "answer" + # Intermediate results of the function (function call) called during the Bot conversation process. + # Bot 对话过程中调用函数(function call)的中间结果。 + function_call = "function_call" + # Results returned after calling the tool (function call). + # 调用工具 (function call)后返回的结果。 + tool_output = "tool_output" + # Results returned after calling the tool (function call). + # 调用工具 (function call)后返回的结果。 + tool_response = "tool_response" + # If the user question suggestion switch is turned on in the Bot configuration, the reply content related to the recommended questions will be returned. + # 如果在 Bot 上配置打开了用户问题建议开关,则会返回推荐问题相关的回复内容。不支持在请求中作为入参。 + follow_up = "follow_up" + # In the scenario of multiple answers, the server will return a verbose package, and the corresponding content is in JSON format. content.msg_type = generate_answer_finish represents that all answers have been replied to. + # 多 answer 场景下,服务端会返回一个 verbose 包,对应的 content 为 JSON 格式,content.msg_type =generate_answer_finish 代表全部 answer 回复完成。不支持在请求中作为入参。 + verbose = "verbose" + + +class MessageContentType(str, Enum): + # Text. + # 文本。 + text = "text" + # Multimodal content, that is, a combination of text and files, or a combination of text and images. + # 多模态内容,即文本和文件的组合、文本和图片的组合。 + object_string = "object_string" + # message card. This enum value only appears in the interface response and is not supported as an input parameter. + # 卡片。此枚举值仅在接口响应中出现,不支持作为入参。 + card = "card" + + +class MessageObjectStringType(str, Enum): + """ + The content type of the multimodal message. + """ + + text = "text" + file = "file" + image = "image" + + +class MessageObjectString(CozeModel): + # The content type of the multimodal message. + # 多模态消息内容类型 + type: MessageObjectStringType + # Text content. Required when type is text. + # 文本内容。 + text: str + # The ID of the file or image content. + # 在 type 为 file 或 image 时,file_id 和 file_url 应至少指定一个。 + file_id: str + # The online address of the file or image content.
Must be a valid address that is publicly accessible. + # file_id or file_url must be specified when type is file or image. + # 文件或图片内容的在线地址。必须是可公共访问的有效地址。 + # 在 type 为 file 或 image 时,file_id 和 file_url 应至少指定一个。 + file_url: str + + +class Message(CozeModel): + # The entity that sent this message. + role: MessageRole + # The type of message. + type: MessageType + # The content of the message. It supports various types of content, including plain text, multimodal (a mix of text, images, and files), message cards, and more. + # 消息的内容,支持纯文本、多模态(文本、图片、文件混合输入)、卡片等多种类型的内容。 + content: str + # The type of message content. + # 消息内容的类型 + content_type: MessageContentType + # Additional information when creating a message, and this additional information will also be returned when retrieving messages. + # Custom key-value pairs should be specified in Map object format, with a length of 16 key-value pairs. The length of the key should be between 1 and 64 characters, and the length of the value should be between 1 and 512 characters. + # 创建消息时的附加消息,获取消息时也会返回此附加消息。 + # 自定义键值对,应指定为 Map 对象格式。长度为 16 对键值对,其中键(key)的长度范围为 1~64 个字符,值(value)的长度范围为 1~512 个字符。 + meta_data: Optional[Dict[str, str]] = None + + @staticmethod + def user_text_message(content: str, meta_data: Optional[Dict[str, str]] = None) -> "Message": + return Message( + role=MessageRole.user, + type=MessageType.question, + content=content, + content_type=MessageContentType.text, + meta_data=meta_data, + ) + + @staticmethod + def assistant_text_message(content: str, meta_data: Optional[Dict[str, str]] = None) -> "Message": + return Message( + role=MessageRole.assistant, + type=MessageType.answer, + content=content, + content_type=MessageContentType.text, + meta_data=meta_data, + ) + + +class MessageResponse(Message): + id: str + conversation_id: str + bot_id: str + chat_id: str + created_at: int = None + updated_at: int = None + + +class ChatStatus(str, Enum): + """ + The running status of the session + """ + + # The session has been created. + created = "created" + + # The Bot is processing. + in_progress = "in_progress" + + # The Bot has finished processing, and the session has ended. + completed = "completed" + + # The session has failed. + failed = "failed" + + # The session is interrupted and requires further processing. + requires_action = "requires_action" + + +class Chat(CozeModel): + # The ID of the chat. + id: str + # The ID of the conversation. + conversation_id: str + # The ID of the bot. + bot_id: str + # Indicates the create time of the chat. The value format is Unix timestamp in seconds. + created_at: Optional[int] = None + # Indicates the end time of the chat. The value format is Unix timestamp in seconds. + completed_at: Optional[int] = None + # Indicates the failure time of the chat. The value format is Unix timestamp in seconds. + failed_at: Optional[int] = None + # Additional information when creating a message, and this additional information will also be returned when retrieving messages. + # Custom key-value pairs should be specified in Map object format, with a length of 16 key-value pairs. The length of the key should be between 1 and 64 characters, and the length of the value should be between 1 and 512 characters. + meta_data: Optional[Dict[str, str]] = None + # When the chat encounters an exception, this field returns detailed error information, including: + # Code: The error code. An integer type. 0 indicates success, other values indicate failure. + # Msg: The error message. A string type. + + # The running status of the session. The values are: + # created: The session has been created. + # in_progress: The Bot is processing. + # completed: The Bot has finished processing, and the session has ended. + # failed: The session has failed. + # requires_action: The session is interrupted and requires further processing. + status: ChatStatus + + # Details of the information needed for execution. diff --git a/cozepy/request.py b/cozepy/request.py index c93a865..1de790c 100644 --- a/cozepy/request.py +++ b/cozepy/request.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Tuple, Optional +from typing import TYPE_CHECKING, Tuple, Optional, Iterator import requests from requests import Response @@ -36,7 +36,8 @@ def request( params: dict = None, headers: dict = None, body: dict = None, - ) -> T: + stream: bool = False, + ) -> T | Iterator[bytes]: """ Send a request to the server. """ @@ -44,7 +45,9 @@ def request( headers = {} if self._auth: self._auth.authentication(headers) - r = requests.request(method, url, params=params, headers=headers, json=body) + r = requests.request(method, url, params=params, headers=headers, json=body, stream=stream) + if stream: + return r.iter_lines() code, msg, data = self.__parse_requests_code_msg(r) diff --git a/tests/test_bot.py b/tests/test_bot.py index f6a2e03..4a3f404 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -8,8 +8,6 @@ class TestBotClient(TestCase): def test_list_published_bots_v1(self): space_id = os.getenv("SPACE_ID_1").strip() token = os.getenv("COZE_TOKEN").strip() - for i in token: - print("token", i) auth = TokenAuth(token) cli = Coze(auth=auth, base_url=COZE_CN_BASE_URL) From bb13da80059ed0b51759c74e12f4ac956b647ccf Mon Sep 17 00:00:00 2001 From: chyroc Date: Tue, 24 Sep 2024 19:14:58 +0800 Subject: [PATCH 2/5] fix tuple type --- cozepy/__init__.py | 8 +++++++- cozepy/chat.py | 4 +++- cozepy/request.py | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/cozepy/__init__.py b/cozepy/__init__.py index 47f3d69..3c265ec 100644 --- a/cozepy/__init__.py +++ b/cozepy/__init__.py @@ -1,8 +1,9 @@ from .auth import ApplicationOAuth, Auth, TokenAuth from .config import COZE_COM_BASE_URL, COZE_CN_BASE_URL from .coze import Coze -from .model import TokenPaged, NumberPaged from .model import ( + TokenPaged, + NumberPaged, MessageRole, MessageType, MessageContentType, @@ -11,6 +12,7 @@ Message, ) from .conversation import Conversation +from .chat import Chat, ChatEvent, ChatIterator, Event __all__ = [ "ApplicationOAuth", @@ -28,4 +30,8 @@ "MessageObjectString", "Message", "Conversation", + "Chat", + "ChatEvent", + "ChatIterator", + "Event", ] diff --git a/cozepy/chat.py b/cozepy/chat.py index 372b651..53bb178 100644 --- a/cozepy/chat.py +++ b/cozepy/chat.py @@ -2,6 +2,8 @@ from enum import Enum from typing import Dict, List, Iterator +from click import Tuple + from .auth import Auth from .model import Message, Chat, MessageResponse, CozeModel from .request import Requester @@ -118,7 +120,7 @@ def chat_v3( auto_save_history: bool = True, meta_data: Dict[str, str] = None, conversation_id: str = None, - ) -> Chat | ChatIterator: + ) -> Tuple[Chat, ChatIterator]: """ Create a conversation. Conversation is an interaction between a bot and a user, including one or more messages. diff --git a/cozepy/request.py b/cozepy/request.py index 1de790c..87ceabe 100644 --- a/cozepy/request.py +++ b/cozepy/request.py @@ -37,7 +37,7 @@ def request( headers: dict = None, body: dict = None, stream: bool = False, - ) -> T | Iterator[bytes]: + ) -> Tuple[T, Iterator[bytes]]: """ Send a request to the server. """ From 94bea8030996388607099f5e19416112de5d075f Mon Sep 17 00:00:00 2001 From: chyroc Date: Tue, 24 Sep 2024 19:16:31 +0800 Subject: [PATCH 3/5] fix tuple type --- cozepy/chat.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cozepy/chat.py b/cozepy/chat.py index 53bb178..702df21 100644 --- a/cozepy/chat.py +++ b/cozepy/chat.py @@ -1,8 +1,6 @@ import json from enum import Enum -from typing import Dict, List, Iterator - -from click import Tuple +from typing import Dict, List, Iterator, Tuple from .auth import Auth from .model import Message, Chat, MessageResponse, CozeModel From b694a89eb400859187a3b000dd74dad8431c2356 Mon Sep 17 00:00:00 2001 From: chyroc Date: Tue, 24 Sep 2024 19:28:11 +0800 Subject: [PATCH 4/5] add ci test --- .github/workflows/ci.yml | 1 + cozepy/chat.py | 4 ++-- cozepy/request.py | 4 ++-- tests/test_chat.py | 41 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 46 insertions(+), 4 deletions(-) create mode 100644 tests/test_chat.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0efd7e8..e39bd53 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,6 +43,7 @@ jobs: COZE_JWT_AUTH_CLIENT_ID: ${{ secrets.COZE_JWT_AUTH_CLIENT_ID }} COZE_JWT_AUTH_PRIVATE_KEY: ${{ secrets.COZE_JWT_AUTH_PRIVATE_KEY }} COZE_JWT_AUTH_KEY_ID: ${{ secrets.COZE_JWT_AUTH_KEY_ID }} + COZE_BOT_ID_TRANSLATE: ${{ secrets.COZE_BOT_ID_TRANSLATE }} - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 diff --git a/cozepy/chat.py b/cozepy/chat.py index 702df21..085f308 100644 --- a/cozepy/chat.py +++ b/cozepy/chat.py @@ -1,6 +1,6 @@ import json from enum import Enum -from typing import Dict, List, Iterator, Tuple +from typing import Dict, List, Iterator, Tuple, Union from .auth import Auth from .model import Message, Chat, MessageResponse, CozeModel @@ -118,7 +118,7 @@ def chat_v3( auto_save_history: bool = True, meta_data: Dict[str, str] = None, conversation_id: str = None, - ) -> Tuple[Chat, ChatIterator]: + ) -> Union[Chat, ChatIterator]: """ Create a conversation. Conversation is an interaction between a bot and a user, including one or more messages. diff --git a/cozepy/request.py b/cozepy/request.py index 87ceabe..eedad80 100644 --- a/cozepy/request.py +++ b/cozepy/request.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Tuple, Optional, Iterator +from typing import TYPE_CHECKING, Tuple, Optional, Iterator, Union import requests from requests import Response @@ -37,7 +37,7 @@ def request( headers: dict = None, body: dict = None, stream: bool = False, - ) -> Tuple[T, Iterator[bytes]]: + ) -> Union[T, Iterator[bytes]]: """ Send a request to the server. """ diff --git a/tests/test_chat.py b/tests/test_chat.py new file mode 100644 index 0000000..9227d25 --- /dev/null +++ b/tests/test_chat.py @@ -0,0 +1,41 @@ +import os + +from cozepy import TokenAuth, Coze, COZE_CN_BASE_URL, Message, ChatIterator, Event +from cozepy.auth import _random_hex + + +def test_chat_v3_not_stream(): + token = os.getenv("COZE_TOKEN").strip() + bot_id = os.getenv("COZE_BOT_ID_TRANSLATE").strip() + + auth = TokenAuth(token) + cli = Coze(auth=auth, base_url=COZE_CN_BASE_URL) + + chat = cli.chat.chat_v3( + bot_id=bot_id, + user_id=_random_hex(10), + additional_messages=[Message.user_text_message("Hi, how are you?")], + stream=False, + ) + assert chat is not None + assert chat.id != "" + + +def test_chat_v3_stream(): + token = os.getenv("COZE_TOKEN").strip() + bot_id = os.getenv("COZE_BOT_ID_TRANSLATE").strip() + + auth = TokenAuth(token) + cli = Coze(auth=auth, base_url=COZE_CN_BASE_URL) + + chat_iter: ChatIterator = cli.chat.chat_v3( + bot_id=bot_id, + user_id=_random_hex(10), + additional_messages=[Message.user_text_message("Hi, how are you?")], + stream=True, + ) + for item in chat_iter: + assert item is not None + assert item.event != "" + if item.event == Event.conversation_message_delta: + assert item.message.content != "" From 923d60b4d8849eedb89e08ab004920b23db67b88 Mon Sep 17 00:00:00 2001 From: chyroc Date: Tue, 24 Sep 2024 19:33:01 +0800 Subject: [PATCH 5/5] remove unused class --- cozepy/chat.py | 8 ++++---- cozepy/model.py | 16 +++++++--------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/cozepy/chat.py b/cozepy/chat.py index 085f308..eb84d9a 100644 --- a/cozepy/chat.py +++ b/cozepy/chat.py @@ -1,9 +1,9 @@ import json from enum import Enum -from typing import Dict, List, Iterator, Tuple, Union +from typing import Dict, List, Iterator, Union from .auth import Auth -from .model import Message, Chat, MessageResponse, CozeModel +from .model import Message, Chat, CozeModel from .request import Requester @@ -48,7 +48,7 @@ class Event(str, Enum): class ChatEvent(CozeModel): event: Event chat: Chat = None - message: MessageResponse = None + message: Message = None class ChatIterator(object): @@ -88,7 +88,7 @@ def __next__(self) -> ChatEvent: elif event == Event.error: raise Exception(f"error event: {line}") elif event in [Event.conversation_message_delta, Event.conversation_message_completed]: - return ChatEvent(event=event, message=MessageResponse.model_validate(json.loads(data))) + return ChatEvent(event=event, message=Message.model_validate(json.loads(data))) elif event in [ Event.conversation_chat_created, Event.conversation_chat_in_progress, diff --git a/cozepy/model.py b/cozepy/model.py index a7ddba5..509a6ec 100644 --- a/cozepy/model.py +++ b/cozepy/model.py @@ -139,6 +139,13 @@ class Message(CozeModel): # 自定义键值对,应指定为 Map 对象格式。长度为 16 对键值对,其中键(key)的长度范围为 1~64 个字符,值(value)的长度范围为 1~512 个字符。 meta_data: Optional[Dict[str, str]] = None + id: str = None + conversation_id: str = None + bot_id: str = None + chat_id: str = None + created_at: int = None + updated_at: int = None + @staticmethod def user_text_message(content: str, meta_data: Optional[Dict[str, str]] = None) -> "Message": return Message( @@ -160,15 +167,6 @@ def assistant_text_message(content: str, meta_data: Optional[Dict[str, str]] = N ) -class MessageResponse(Message): - id: str - conversation_id: str - bot_id: str - chat_id: str - created_at: int = None - updated_at: int = None - - class ChatStatus(str, Enum): """ The running status of the session