├── .github ├── release-drafter.yml └── workflows │ ├── ci.yml │ ├── pypi-release.yml │ └── required-labels.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .vscode └── settings.json ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── codecov.yml ├── cozepy ├── __init__.py ├── audio │ ├── __init__.py │ ├── rooms │ │ └── __init__.py │ ├── speech │ │ └── __init__.py │ ├── transcriptions │ │ └── __init__.py │ └── voices │ │ └── __init__.py ├── auth │ └── __init__.py ├── bots │ └── __init__.py ├── chat │ ├── __init__.py │ └── message │ │ └── __init__.py ├── config.py ├── conversations │ ├── __init__.py │ └── message │ │ └── __init__.py ├── coze.py ├── datasets │ ├── __init__.py │ ├── documents │ │ └── __init__.py │ └── images │ │ └── __init__.py ├── exception.py ├── files │ └── __init__.py ├── knowledge │ ├── __init__.py │ └── documents │ │ └── __init__.py ├── log.py ├── model.py ├── py.typed ├── request.py ├── templates │ └── __init__.py ├── users │ └── __init__.py ├── util.py ├── variables │ └── __init__.py ├── version.py ├── websockets │ ├── __init__.py │ ├── audio │ │ ├── __init__.py │ │ ├── speech │ │ │ └── __init__.py │ │ └── transcriptions │ │ │ └── __init__.py │ ├── chat │ │ └── __init__.py │ └── ws.py ├── workflows │ ├── __init__.py │ ├── chat │ │ └── __init__.py │ └── runs │ │ ├── __init__.py │ │ └── run_histories │ │ └── __init__.py └── workspaces │ └── __init__.py ├── examples ├── audio.py ├── auth_oauth_device.py ├── auth_oauth_jwt.py ├── auth_oauth_pkce.py ├── auth_oauth_web.py ├── auth_pat.py ├── benchmark_ark_text.py ├── benchmark_text_chat.py ├── benchmark_websockets_chat.py ├── bot_create.py ├── bot_publish.py ├── bot_retrieve.py ├── bot_update.py ├── chat_conversation_stream.py ├── chat_download_file.py ├── chat_local_plugin.py ├── chat_multimode_stream.py ├── chat_no_stream.py ├── chat_oneonone_audio.py ├── chat_simple_audio.py ├── chat_stream.py ├── cli.py ├── conversation.py ├── conversation_list.py ├── dataset_create.py ├── exception.py ├── files_upload.py ├── log.py ├── template_duplicate.py ├── timeout.py ├── users_me.py ├── variable_retrieve.py ├── variable_update.py ├── websockets_audio_speech.py ├── websockets_audio_transcriptions.py ├── websockets_chat.py ├── websockets_chat_realtime_gui.py ├── workflow_async.py ├── workflow_chat_multimode_stream.py ├── workflow_chat_stream.py ├── workflow_no_stream.py ├── workflow_run_history.py ├── workflow_stream.py └── workspace.py ├── poetry.lock ├── pyproject.toml └── tests ├── __init__.py ├── test_audio_rooms.py ├── test_audio_speech.py ├── test_audio_translations.py ├── test_audio_voices.py ├── test_auth.py ├── test_bots.py ├── test_chat.py ├── test_chat_messages.py ├── test_conversations.py ├── test_conversations_messages.py ├── test_datasets.py ├── test_datasets_documents.py ├── test_datasets_images.py ├── test_exception.py ├── test_file.py ├── test_knowledge_documents.py ├── test_log.py ├── test_model.py ├── test_request.py ├── test_template.py ├── test_users.py ├── test_util.py ├── test_version.py ├── test_workflow_run_history.py ├── test_workflows.py ├── test_workflows_chat.py ├── test_workspaces.py └── testdata ├── chat_audio_stream_resp.txt ├── chat_error_resp.txt ├── chat_failed_resp.txt ├── chat_invalid_resp.txt ├── chat_text_stream_resp.txt ├── private_key.pem ├── workflow_run_invalid_stream_resp.txt ├── workflow_run_stream_resp.txt └── workflows_chat_stream_resp.txt /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name-template: 'v$RESOLVED_VERSION' 2 | tag-template: 'v$RESOLVED_VERSION' 3 | categories: 4 | - title: '🚀 Features' 5 | labels: 6 | - 'feature' 7 | - 'enhancement' 8 | - title: '🐛 Bug Fixes' 9 | labels: 10 | - 'fix' 11 | - 'bugfix' 12 | - 'bug' 13 | - title: '🧰 Maintenance' 14 | labels: 15 | - 'chore' 16 | - 'documentation' 17 | autolabeler: 18 | - label: 'chore' 19 | files: 20 | - '*.md' 21 | branch: 22 | - '/docs{0,1}\/.+/' 23 | - label: 'bug' 24 | branch: 25 | - '/fix\/.+/' 26 | title: 27 | - '/fix/i' 28 | - label: 'enhancement' 29 | branch: 30 | - '/feature\/.+/' 31 | change-template: '- $TITLE @$AUTHOR (#$NUMBER)' 32 | change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks. 33 | version-resolver: 34 | major: 35 | labels: 36 | - 'major' 37 | minor: 38 | labels: 39 | - 'minor' 40 | patch: 41 | labels: 42 | - 'patch' 43 | default: patch 44 | template: | 45 | ## Changes 46 | 47 | $CHANGES 48 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | merge_group: 8 | 9 | permissions: write-all 10 | 11 | jobs: 12 | test: 13 | runs-on: ${{ matrix.os }} 14 | name: test (Python ${{ matrix.python-version }} on ${{ matrix.os-label }}) 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | python-version: [ "3.8", "3.9", "3.10", "3.11", "3.12" ] 19 | os: [ "ubuntu-latest" ] 20 | os-label: [ "Ubuntu" ] 21 | include: 22 | - { python-version: "3.8", os: "windows-latest", os-label: "Windows" } 23 | - { python-version: "3.8", os: "macos-latest", os-label: "macOS" } 24 | - { python-version: "3.7", os: "ubuntu-22.04", os-label: "Ubuntu" } 25 | steps: 26 | - uses: actions/checkout@v3 27 | - name: Set up Python 28 | uses: actions/setup-python@v4 29 | with: 30 | python-version: "${{ matrix.python-version }}" 31 | - name: Install Dependencies 32 | run: | 33 | pip install poetry 34 | poetry install 35 | - name: Build 36 | run: | 37 | poetry build 38 | - name: Check 39 | run: | 40 | poetry run ruff check cozepy 41 | poetry run ruff format --check 42 | poetry run mypy . 43 | - name: Run tests 44 | run: poetry run pytest --cov --cov-report=xml 45 | - name: Upload coverage to Codecov 46 | uses: codecov/codecov-action@v3 47 | with: 48 | token: ${{ secrets.CODECOV_TOKEN }} 49 | 50 | test_success: 51 | # this aggregates success state of all jobs listed in `needs` 52 | # this is the only required check to pass CI 53 | name: "Test success" 54 | if: always() 55 | runs-on: ubuntu-latest 56 | needs: [ test ] 57 | steps: 58 | - name: "Success" 59 | if: needs.test.result == 'success' 60 | run: true 61 | shell: bash 62 | - name: "Failure" 63 | if: needs.test.result != 'success' 64 | run: false 65 | shell: bash 66 | 67 | draft: 68 | runs-on: ubuntu-latest 69 | needs: test_success 70 | if: github.ref == 'refs/heads/main' 71 | steps: 72 | - uses: release-drafter/release-drafter@v5 73 | env: 74 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 75 | -------------------------------------------------------------------------------- /.github/workflows/pypi-release.yml: -------------------------------------------------------------------------------- 1 | name: Publish to PyPI 2 | on: 3 | push: 4 | tags: ["v*.*.*"] 5 | jobs: 6 | publish: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v3 10 | - name: Build and publish to pypi 11 | uses: JRubics/poetry-publish@v2.0 12 | with: 13 | pypi_token: ${{ secrets.PYPI_TOKEN }} 14 | -------------------------------------------------------------------------------- /.github/workflows/required-labels.yml: -------------------------------------------------------------------------------- 1 | name: Required Labels 2 | on: 3 | pull_request: 4 | types: [opened, labeled, unlabeled, synchronize] 5 | jobs: 6 | label: 7 | runs-on: ubuntu-latest 8 | permissions: 9 | issues: write 10 | pull-requests: write 11 | steps: 12 | - uses: mheap/github-action-required-labels@v5 13 | with: 14 | mode: minimum 15 | count: 1 16 | labels: | 17 | feature 18 | enhancement 19 | fix 20 | bugfix 21 | bug 22 | chore 23 | documentation 24 | add_comment: true 25 | message: "Requires label: feature, enhancement, fix, bugfix, bug, chore, documentation." -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .venv*/ 3 | .DS_Store 4 | __pycache__/ 5 | dist/ 6 | *debug* 7 | .env 8 | .env_private 9 | scripts/ 10 | .cache/ 11 | output.wav 12 | response.wav 13 | temp_response.pcm -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/astral-sh/ruff-pre-commit 3 | # Ruff version. 4 | rev: v0.6.7 5 | hooks: 6 | # Run the linter. 7 | - id: ruff 8 | types_or: [ python, pyi ] 9 | args: [ --fix ] 10 | # Run the formatter. 11 | - id: ruff-format 12 | types_or: [ python, pyi ] -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | // Git 4 | "**/.git": true, 5 | "**/.svn": true, 6 | // Python 7 | "**/.venv": true, 8 | "**/__pycache__": true, 9 | "**/.pytest_cache": true, 10 | "**/.mypy_cache": true, 11 | "**/*.pyc": true, 12 | "**/.coverage": true, 13 | "**/.eggs": true, 14 | "**/*.egg-info": true, 15 | "**/.ruff_cache": true, 16 | // Project 17 | "**/build": true, 18 | "**/dist": true, 19 | ".cache": true, 20 | // Editor 21 | "**/.idea": true, 22 | // System 23 | "**/.DS_Store": true 24 | }, 25 | "cSpell.words": [ 26 | "cozepy", 27 | "logid", 28 | "respx" 29 | ], 30 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Setting up the environment 2 | 3 | create a virtual environment: 4 | 5 | ```shell 6 | python -m venv ./.venv 7 | ``` 8 | 9 | active the virtual environment: 10 | 11 | ```shell 12 | source ./.venv/bin/activate 13 | ``` 14 | 15 | We use [Poetry](https://python-poetry.org/) to manage dependencies, you can install it by: 16 | 17 | ```shell 18 | python -m pip install poetry 19 | ``` 20 | 21 | And then install dependencies: 22 | 23 | ```shell 24 | poetry install 25 | ``` 26 | 27 | ## Pre Commit 28 | 29 | ```shell 30 | pre-commit install 31 | ``` -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Spring (SG) Pte. Ltd. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: #add everything under here, more options at https://docs.codecov.com/docs/commit-status 4 | default: 5 | target: 95% 6 | threshold: 95% 7 | base: auto 8 | paths: 9 | - cozepy/** 10 | comment: #this is a top-level key 11 | layout: "diff, flags, files" 12 | behavior: default 13 | require_changes: true # if true: only post the comment if coverage changes 14 | require_base: true # if true: must have a base report to post 15 | require_head: true # if true: must have a head report to post 16 | hide_project_coverage: false # [true :: only show coverage on the git diff aka patch coverage]] -------------------------------------------------------------------------------- /cozepy/audio/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, Optional 2 | 3 | from cozepy.request import Requester 4 | from cozepy.util import remove_url_trailing_slash 5 | 6 | if TYPE_CHECKING: 7 | from .rooms import AsyncRoomsClient, RoomsClient 8 | from .speech import AsyncSpeechClient, SpeechClient 9 | from .transcriptions import AsyncTranscriptionsClient, TranscriptionsClient 10 | from .voices import AsyncVoicesClient, VoicesClient 11 | 12 | 13 | class AudioClient(object): 14 | def __init__(self, base_url: str, requester: Requester): 15 | self._base_url = remove_url_trailing_slash(base_url) 16 | self._requester = requester 17 | 18 | self._rooms: Optional[RoomsClient] = None 19 | self._voices: Optional[VoicesClient] = None 20 | self._speech: Optional[SpeechClient] = None 21 | self._transcriptions: Optional[TranscriptionsClient] = None 22 | 23 | @property 24 | def rooms(self) -> "RoomsClient": 25 | if self._rooms is None: 26 | from .rooms import RoomsClient 27 | 28 | self._rooms = RoomsClient(base_url=self._base_url, requester=self._requester) 29 | return self._rooms 30 | 31 | @property 32 | def speech(self) -> "SpeechClient": 33 | if self._speech is None: 34 | from .speech import SpeechClient 35 | 36 | self._speech = SpeechClient(base_url=self._base_url, requester=self._requester) 37 | return self._speech 38 | 39 | @property 40 | def transcriptions(self) -> "TranscriptionsClient": 41 | if self._transcriptions is None: 42 | from .transcriptions import TranscriptionsClient 43 | 44 | self._transcriptions = TranscriptionsClient(base_url=self._base_url, requester=self._requester) 45 | return self._transcriptions 46 | 47 | @property 48 | def voices(self) -> "VoicesClient": 49 | if self._voices is None: 50 | from .voices import VoicesClient 51 | 52 | self._voices = VoicesClient(base_url=self._base_url, requester=self._requester) 53 | return self._voices 54 | 55 | 56 | class AsyncAudioClient(object): 57 | def __init__(self, base_url: str, requester: Requester): 58 | self._base_url = remove_url_trailing_slash(base_url) 59 | self._requester = requester 60 | 61 | self._rooms: Optional[AsyncRoomsClient] = None 62 | self._voices: Optional[AsyncVoicesClient] = None 63 | self._speech: Optional[AsyncSpeechClient] = None 64 | self._transcriptions: Optional[AsyncTranscriptionsClient] = None 65 | 66 | @property 67 | def rooms(self) -> "AsyncRoomsClient": 68 | if self._rooms is None: 69 | from .rooms import AsyncRoomsClient 70 | 71 | self._rooms = AsyncRoomsClient(base_url=self._base_url, requester=self._requester) 72 | return self._rooms 73 | 74 | @property 75 | def speech(self) -> "AsyncSpeechClient": 76 | if self._speech is None: 77 | from .speech import AsyncSpeechClient 78 | 79 | self._speech = AsyncSpeechClient(base_url=self._base_url, requester=self._requester) 80 | return self._speech 81 | 82 | @property 83 | def voices(self) -> "AsyncVoicesClient": 84 | if self._voices is None: 85 | from .voices import AsyncVoicesClient 86 | 87 | self._voices = AsyncVoicesClient(base_url=self._base_url, requester=self._requester) 88 | return self._voices 89 | 90 | @property 91 | def transcriptions(self) -> "AsyncTranscriptionsClient": 92 | if self._transcriptions is None: 93 | from .transcriptions import AsyncTranscriptionsClient 94 | 95 | self._transcriptions = AsyncTranscriptionsClient(base_url=self._base_url, requester=self._requester) 96 | return self._transcriptions 97 | -------------------------------------------------------------------------------- /cozepy/audio/rooms/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from cozepy.model import CozeModel 4 | from cozepy.request import Requester 5 | from cozepy.util import remove_none_values, remove_url_trailing_slash 6 | 7 | 8 | class RoomAudioConfig(CozeModel): 9 | # 房间音频编码格式,支持设置为: 10 | # AACLC: AAC-LC 编码格式。 11 | # G711A: G711A 编码格式。 12 | # OPUS: (默认)Opus 编码格式。 13 | # G722: G.722 编码格式。 14 | codec: Optional[str] 15 | 16 | 17 | class RoomVideoConfig(CozeModel): 18 | # 房间视频编码格式,支持设置为: 19 | # H264:(默认)H264 编码格式。 20 | # BYTEVC1: 火山引擎自研的视频编码格式。 21 | codec: Optional[str] 22 | # 房间视频流类型, 支持 main 和 screen。 23 | # main: 主流,包括通过摄像头/麦克风的内部采集机制获取的流,以及通过自定义采集方式获取的流。 24 | # screen: 屏幕流,用于屏幕共享或屏幕录制的视频流。 25 | stream_video_type: Optional[str] 26 | 27 | 28 | class RoomConfig(CozeModel): 29 | # The audio config of the room. 30 | audio_config: Optional[RoomAudioConfig] 31 | # The video config of the room. 32 | video_config: Optional[RoomVideoConfig] 33 | # 自定义开场白 34 | prologue_content: Optional[str] 35 | 36 | 37 | class CreateRoomResp(CozeModel): 38 | # Token to join the room 39 | token: str 40 | # The id of user 41 | uid: str 42 | # The id of room 43 | room_id: str 44 | # App id to join the room 45 | app_id: str 46 | 47 | 48 | class RoomsClient(object): 49 | """ 50 | Room service client. 51 | """ 52 | 53 | def __init__(self, base_url: str, requester: Requester): 54 | self._base_url = remove_url_trailing_slash(base_url) 55 | self._requester = requester 56 | 57 | def create( 58 | self, 59 | *, 60 | bot_id: str, 61 | voice_id: Optional[str] = None, 62 | conversation_id: Optional[str] = None, 63 | uid: Optional[str] = None, 64 | workflow_id: Optional[str] = None, 65 | config: Optional[RoomConfig] = None, 66 | ) -> CreateRoomResp: 67 | """ 68 | create rtc room 69 | 70 | :param bot_id: The id of the bot. 71 | :param voice_id: The voice id of the voice. 72 | :param conversation_id: The id of the conversation. 73 | :param uid: The id of the user. if not provided, it will be generated by the server. 74 | :param workflow_id: The id of the workflow. 75 | :param config: The config of the room. 76 | :return: create room result 77 | """ 78 | url = f"{self._base_url}/v1/audio/rooms" 79 | body = remove_none_values( 80 | { 81 | "bot_id": bot_id, 82 | "voice_id": voice_id, 83 | "conversation_id": conversation_id, 84 | "uid": uid, 85 | "workflow_id": workflow_id, 86 | "config": config.model_dump() if config else None, 87 | } 88 | ) 89 | return self._requester.request("post", url, stream=False, cast=CreateRoomResp, body=body) 90 | 91 | 92 | class AsyncRoomsClient(object): 93 | """ 94 | Room service async client. 95 | """ 96 | 97 | def __init__(self, base_url: str, requester: Requester): 98 | self._base_url = remove_url_trailing_slash(base_url) 99 | self._requester = requester 100 | 101 | async def create( 102 | self, 103 | *, 104 | bot_id: str, 105 | voice_id: Optional[str] = None, 106 | conversation_id: Optional[str] = None, 107 | uid: Optional[str] = None, 108 | workflow_id: Optional[str] = None, 109 | config: Optional[RoomConfig] = None, 110 | ) -> CreateRoomResp: 111 | """ 112 | create rtc room 113 | 114 | :param bot_id: The id of the bot. 115 | :param voice_id: The voice id of the voice. 116 | :param conversation_id: The id of the conversation. 117 | :param uid: The id of the user. if not provided, it will be generated by the server. 118 | :param workflow_id: The id of the workflow. 119 | :param config: The config of the room. 120 | :return: create room result 121 | """ 122 | url = f"{self._base_url}/v1/audio/rooms" 123 | body = remove_none_values( 124 | { 125 | "bot_id": bot_id, 126 | "voice_id": voice_id, 127 | "conversation_id": conversation_id, 128 | "uid": uid, 129 | "workflow_id": workflow_id, 130 | "config": config.model_dump() if config else None, 131 | } 132 | ) 133 | return await self._requester.arequest("post", url, stream=False, cast=CreateRoomResp, body=body) 134 | -------------------------------------------------------------------------------- /cozepy/audio/speech/__init__.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | from typing import Optional 3 | 4 | from cozepy.model import FileHTTPResponse 5 | from cozepy.request import Requester 6 | from cozepy.util import remove_url_trailing_slash 7 | 8 | 9 | class AudioFormat(str, Enum): 10 | WAV = "wav" 11 | PCM = "pcm" 12 | OGG_OPUS = "ogg_opus" 13 | MP3 = "mp3" 14 | 15 | 16 | class LanguageCode(str, Enum): 17 | ZH = "zh" 18 | EN = "en" 19 | JA = "ja" 20 | ES = "es" 21 | ID = "id" 22 | PT = "pt" 23 | 24 | 25 | class SpeechClient(object): 26 | """ 27 | speech service client. 28 | """ 29 | 30 | def __init__(self, base_url: str, requester: Requester): 31 | self._base_url = remove_url_trailing_slash(base_url) 32 | self._requester = requester 33 | 34 | def create( 35 | self, 36 | *, 37 | input: str, 38 | voice_id: str, 39 | response_format: AudioFormat = AudioFormat.MP3, 40 | speed: float = 1, 41 | sample_rate: int = 24000, 42 | **kwargs, 43 | ) -> FileHTTPResponse: 44 | """ 45 | Generate speech audio from input text with specified voice 46 | 47 | :param input: The text to generate audio for. Maximum length is 1024 characters. 48 | :param voice_id: The voice ID to generate audio with, obtained via .audio.voices.list 49 | :param response_format: Audio encoding format, wav / pcm / ogg_opus / mp3, defaults to mp3 50 | :param speed: Speech speed, [0.2,3], defaults to 1, typically one decimal place is sufficient 51 | :param sample_rate: Audio sample rate, defaults to 24000, available values: [8000, 16000, 22050, 24000, 32000, 44100, 48000] 52 | :return: The synthesized audio file content 53 | """ 54 | url = f"{self._base_url}/v1/audio/speech" 55 | headers: Optional[dict] = kwargs.get("headers") 56 | body = { 57 | "input": input, 58 | "voice_id": voice_id, 59 | "response_format": response_format, 60 | "speed": speed, 61 | "sample_rate": sample_rate, 62 | } 63 | return self._requester.request("post", url, stream=False, cast=FileHTTPResponse, headers=headers, body=body) 64 | 65 | 66 | class AsyncSpeechClient(object): 67 | """ 68 | speech service client. 69 | """ 70 | 71 | def __init__(self, base_url: str, requester: Requester): 72 | self._base_url = remove_url_trailing_slash(base_url) 73 | self._requester = requester 74 | 75 | async def create( 76 | self, 77 | *, 78 | input: str, 79 | voice_id: str, 80 | response_format: AudioFormat = AudioFormat.MP3, 81 | speed: float = 1, 82 | sample_rate: int = 24000, 83 | **kwargs, 84 | ) -> FileHTTPResponse: 85 | """ 86 | Generate speech audio from input text with specified voice 87 | 88 | :param input: The text to generate audio for. Maximum length is 1024 characters. 89 | :param voice_id: The voice ID to generate audio with, obtained via .audio.voices.list 90 | :param response_format: Audio encoding format, wav / pcm / ogg_opus / mp3, defaults to mp3 91 | :param speed: Speech speed, [0.2,3], defaults to 1, typically one decimal place is sufficient 92 | :param sample_rate: Audio sample rate, defaults to 24000, available values: [8000, 16000, 22050, 24000, 32000, 44100, 48000] 93 | :return: The synthesized audio file content 94 | """ 95 | url = f"{self._base_url}/v1/audio/speech" 96 | headers: Optional[dict] = kwargs.get("headers") 97 | body = { 98 | "input": input, 99 | "voice_id": voice_id, 100 | "response_format": response_format, 101 | "speed": speed, 102 | "sample_rate": sample_rate, 103 | } 104 | return await self._requester.arequest( 105 | "post", url, stream=False, cast=FileHTTPResponse, headers=headers, body=body 106 | ) 107 | -------------------------------------------------------------------------------- /cozepy/audio/transcriptions/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from cozepy.files import FileTypes, _try_fix_file 4 | from cozepy.model import CozeModel 5 | from cozepy.request import Requester 6 | from cozepy.util import remove_url_trailing_slash 7 | 8 | 9 | class CreateTranscriptionsResp(CozeModel): 10 | # The text of translation 11 | text: str 12 | 13 | 14 | class TranscriptionsClient(object): 15 | def __init__(self, base_url: str, requester: Requester): 16 | self._base_url = remove_url_trailing_slash(base_url) 17 | self._requester = requester 18 | 19 | def create( 20 | self, 21 | *, 22 | file: FileTypes, 23 | **kwargs, 24 | ) -> CreateTranscriptionsResp: 25 | """ 26 | create transcriptions 27 | 28 | :param file: The file to be translated. 29 | :return: create transcriptions result 30 | """ 31 | url = f"{self._base_url}/v1/audio/transcriptions" 32 | headers: Optional[dict] = kwargs.get("headers") 33 | files = {"file": _try_fix_file(file)} 34 | return self._requester.request( 35 | "post", url, stream=False, cast=CreateTranscriptionsResp, headers=headers, files=files 36 | ) 37 | 38 | 39 | class AsyncTranscriptionsClient(object): 40 | """ 41 | Room service async client. 42 | """ 43 | 44 | def __init__(self, base_url: str, requester: Requester): 45 | self._base_url = remove_url_trailing_slash(base_url) 46 | self._requester = requester 47 | 48 | async def create( 49 | self, 50 | *, 51 | file: FileTypes, 52 | **kwargs, 53 | ) -> CreateTranscriptionsResp: 54 | """ 55 | create transcriptions 56 | 57 | :param file: The file to be translated. 58 | :return: create transcriptions result 59 | """ 60 | url = f"{self._base_url}/v1/audio/transcriptions" 61 | files = {"file": _try_fix_file(file)} 62 | headers: Optional[dict] = kwargs.get("headers") 63 | return await self._requester.arequest( 64 | "post", url, stream=False, cast=CreateTranscriptionsResp, headers=headers, files=files 65 | ) 66 | -------------------------------------------------------------------------------- /cozepy/chat/message/__init__.py: -------------------------------------------------------------------------------- 1 | from cozepy.chat import Message 2 | from cozepy.model import ListResponse 3 | from cozepy.request import Requester 4 | from cozepy.util import remove_url_trailing_slash 5 | 6 | 7 | class ChatMessagesClient(object): 8 | def __init__(self, base_url: str, requester: Requester): 9 | self._base_url = remove_url_trailing_slash(base_url) 10 | self._requester = requester 11 | 12 | def list( 13 | self, 14 | *, 15 | conversation_id: str, 16 | chat_id: str, 17 | ) -> ListResponse[Message]: 18 | """ 19 | The information of messages in the specified conversation besides the Query, including model replies, 20 | intermediate results of the Bot's execution, and other messages. 21 | 22 | docs en: https://www.coze.com/docs/developer_guides/list_chat_messages 23 | docs zh: https://www.coze.cn/docs/developer_guides/list_chat_messages 24 | 25 | :param conversation_id: The ID of the conversation. 26 | :param chat_id: The ID of the chat. 27 | :return: list of Message object 28 | """ 29 | url = f"{self._base_url}/v3/chat/message/list" 30 | params = { 31 | "conversation_id": conversation_id, 32 | "chat_id": chat_id, 33 | } 34 | return self._requester.request("get", url, False, ListResponse[Message], params=params) 35 | 36 | 37 | class AsyncChatMessagesClient(object): 38 | def __init__(self, base_url: str, requester: Requester): 39 | self._base_url = remove_url_trailing_slash(base_url) 40 | self._requester = requester 41 | 42 | async def list( 43 | self, 44 | *, 45 | conversation_id: str, 46 | chat_id: str, 47 | ) -> ListResponse[Message]: 48 | """ 49 | The information of messages in the specified conversation besides the Query, including model replies, 50 | intermediate results of the Bot's execution, and other messages. 51 | 52 | docs en: https://www.coze.com/docs/developer_guides/list_chat_messages 53 | docs zh: https://www.coze.cn/docs/developer_guides/list_chat_messages 54 | 55 | :param conversation_id: The ID of the conversation. 56 | :param chat_id: The ID of the chat. 57 | :return: list of Message object 58 | """ 59 | url = f"{self._base_url}/v3/chat/message/list" 60 | params = { 61 | "conversation_id": conversation_id, 62 | "chat_id": chat_id, 63 | } 64 | return await self._requester.arequest("get", url, False, ListResponse[Message], params=params) 65 | -------------------------------------------------------------------------------- /cozepy/config.py: -------------------------------------------------------------------------------- 1 | import httpx 2 | 3 | # default coze base_url is api.coze.com 4 | COZE_COM_BASE_URL = "https://api.coze.com" 5 | # support change to api.coze.cn 6 | COZE_CN_BASE_URL = "https://api.coze.cn" 7 | 8 | # default timeout is 10 minutes, with 5 seconds connect timeout 9 | DEFAULT_TIMEOUT = httpx.Timeout(timeout=600.0, connect=5.0) 10 | DEFAULT_CONNECTION_LIMITS = httpx.Limits(max_connections=1000, max_keepalive_connections=100) 11 | -------------------------------------------------------------------------------- /cozepy/exception.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | from typing import Optional 3 | 4 | 5 | class CozeError(Exception): 6 | """ 7 | base class for all coze errors 8 | """ 9 | 10 | pass 11 | 12 | 13 | class CozeAPIError(CozeError): 14 | def __init__( 15 | self, code: Optional[int] = None, msg: str = "", logid: Optional[str] = None, debug_url: Optional[str] = None 16 | ): 17 | self.code = code 18 | self.msg = msg 19 | self.logid = logid 20 | self.debug_url = debug_url 21 | if code and code > 0: 22 | if self.debug_url: 23 | super().__init__(f"code: {code}, msg: {msg}, logid: {logid}, debug_url: {self.debug_url}") 24 | else: 25 | super().__init__(f"code: {code}, msg: {msg}, logid: {logid}") 26 | else: 27 | if self.debug_url: 28 | super().__init__(f"msg: {msg}, logid: {logid}, debug_url: {self.debug_url}") 29 | else: 30 | super().__init__(f"msg: {msg}, logid: {logid}") 31 | 32 | 33 | class CozePKCEAuthErrorType(str, Enum): 34 | AUTHORIZATION_PENDING = "authorization_pending" 35 | SLOW_DOWN = "slow_down" 36 | ACCESS_DENIED = "access_denied" 37 | EXPIRED_TOKEN = "expired_token" 38 | 39 | 40 | COZE_PKCE_AUTH_ERROR_TYPE_ENUMS = set(e.value for e in CozePKCEAuthErrorType) 41 | 42 | 43 | class CozePKCEAuthError(CozeError): 44 | def __init__(self, error: CozePKCEAuthErrorType, logid: Optional[str] = None): 45 | super().__init__(f"pkce auth error: {error.value}") 46 | self.error = error 47 | self.logid = logid 48 | 49 | 50 | class CozeInvalidEventError(CozeError): 51 | def __init__(self, field: str = "", data: str = "", logid: str = ""): 52 | self.field = field 53 | self.data = data 54 | self.logid = logid 55 | if field: 56 | super().__init__(f"invalid event, field: {field}, data: {data}, logid: {logid}") 57 | else: 58 | super().__init__(f"invalid event, data: {data}, logid: {logid}") 59 | -------------------------------------------------------------------------------- /cozepy/files/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | from pathlib import Path 3 | from typing import IO, Optional, Tuple, Union 4 | 5 | from cozepy.model import CozeModel 6 | from cozepy.request import Requester 7 | from cozepy.util import remove_url_trailing_slash 8 | 9 | FileContent = Union[IO[bytes], bytes, str, Path] 10 | FileTypes = Union[ 11 | # file (or bytes) 12 | FileContent, 13 | # (filename, file (or bytes)) 14 | Tuple[Optional[str], FileContent], 15 | ] 16 | 17 | 18 | class File(CozeModel): 19 | # 已上传的文件 ID。 20 | # The ID of the uploaded file. 21 | id: str 22 | 23 | # The total byte size of the file. 24 | # 文件的总字节数。 25 | bytes: Optional[int] = None 26 | 27 | # The upload time of the file, in the format of a 10-digit Unix timestamp in seconds (s). 28 | # 文件的上传时间,格式为 10 位的 Unixtime 时间戳,单位为秒(s)。 29 | created_at: Optional[int] = None 30 | 31 | # The name of the file. 32 | # 文件名称。 33 | file_name: Optional[str] = None 34 | 35 | 36 | def _try_fix_file(file: FileTypes) -> FileTypes: 37 | if isinstance(file, Path): 38 | if not file.exists(): 39 | raise ValueError(f"File not found: {file}") 40 | return open(file, "rb") 41 | 42 | if isinstance(file, str): 43 | if not os.path.isfile(file): 44 | raise ValueError(f"File not found: {file}") 45 | return open(file, "rb") 46 | 47 | return file 48 | 49 | 50 | class FilesClient(object): 51 | def __init__(self, base_url: str, requester: Requester): 52 | self._base_url = remove_url_trailing_slash(base_url) 53 | self._requester = requester 54 | 55 | def upload(self, *, file: FileTypes) -> File: 56 | """ 57 | Upload files to Coze platform. 58 | 59 | Local files cannot be used directly in messages. Before creating messages or conversations, 60 | you need to call this interface to upload local files to the platform first. 61 | After uploading the file, you can use it directly in multimodal content in messages 62 | by specifying the file_id. 63 | 64 | 调用接口上传文件到扣子。 65 | 66 | docs en: https://www.coze.com/docs/developer_guides/upload_files 67 | docs zh: https://www.coze.cn/docs/developer_guides/upload_files 68 | 69 | :param file: local file path 70 | :return: file info 71 | """ 72 | url = f"{self._base_url}/v1/files/upload" 73 | files = {"file": _try_fix_file(file)} 74 | return self._requester.request("post", url, False, File, files=files) 75 | 76 | def retrieve(self, *, file_id: str): 77 | """ 78 | Get the information of the specific file uploaded to Coze platform. 79 | 80 | 查看已上传的文件详情。 81 | 82 | docs en: https://www.coze.com/docs/developer_guides/retrieve_files 83 | docs cn: https://www.coze.cn/docs/developer_guides/retrieve_files 84 | 85 | :param file_id: file id 86 | :return: file info 87 | """ 88 | url = f"{self._base_url}/v1/files/retrieve" 89 | params = {"file_id": file_id} 90 | return self._requester.request("get", url, False, File, params=params) 91 | 92 | 93 | class AsyncFilesClient(object): 94 | def __init__(self, base_url: str, requester: Requester): 95 | self._base_url = remove_url_trailing_slash(base_url) 96 | self._requester = requester 97 | 98 | async def upload(self, *, file: FileTypes) -> File: 99 | """ 100 | Upload files to Coze platform. 101 | 102 | Local files cannot be used directly in messages. Before creating messages or conversations, 103 | you need to call this interface to upload local files to the platform first. 104 | After uploading the file, you can use it directly in multimodal content in messages 105 | by specifying the file_id. 106 | 107 | 调用接口上传文件到扣子。 108 | 109 | docs en: https://www.coze.com/docs/developer_guides/upload_files 110 | docs zh: https://www.coze.cn/docs/developer_guides/upload_files 111 | 112 | :param file: local file path 113 | :return: file info 114 | """ 115 | url = f"{self._base_url}/v1/files/upload" 116 | files = {"file": _try_fix_file(file)} 117 | return await self._requester.arequest("post", url, False, File, files=files) 118 | 119 | async def retrieve(self, *, file_id: str): 120 | """ 121 | Get the information of the specific file uploaded to Coze platform. 122 | 123 | 查看已上传的文件详情。 124 | 125 | docs en: https://www.coze.com/docs/developer_guides/retrieve_files 126 | docs cn: https://www.coze.cn/docs/developer_guides/retrieve_files 127 | 128 | :param file_id: file id 129 | :return: file info 130 | """ 131 | url = f"{self._base_url}/v1/files/retrieve" 132 | params = {"file_id": file_id} 133 | return await self._requester.arequest("get", url, False, File, params=params) 134 | -------------------------------------------------------------------------------- /cozepy/knowledge/__init__.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | from typing import TYPE_CHECKING, Optional 3 | 4 | from cozepy.request import Requester 5 | from cozepy.util import remove_url_trailing_slash 6 | 7 | if TYPE_CHECKING: 8 | from .documents import AsyncDocumentsClient, DocumentsClient 9 | 10 | 11 | class KnowledgeClient(object): 12 | def __init__(self, base_url: str, requester: Requester): 13 | warnings.warn( 14 | "The 'coze.knowledge' module is deprecated and will be removed in a future version. " 15 | "Please use 'coze.datasets' instead.", 16 | DeprecationWarning, 17 | stacklevel=2, 18 | ) 19 | self._base_url = remove_url_trailing_slash(base_url) 20 | self._requester = requester 21 | self._documents: Optional[DocumentsClient] = None 22 | 23 | @property 24 | def documents(self) -> "DocumentsClient": 25 | warnings.warn( 26 | "The 'coze.knowledge.documents' module is deprecated and will be removed in a future version. " 27 | "Please use 'coze.datasets.documents' instead.", 28 | DeprecationWarning, 29 | stacklevel=2, 30 | ) 31 | if self._documents is None: 32 | from .documents import DocumentsClient 33 | 34 | self._documents = DocumentsClient(base_url=self._base_url, requester=self._requester) 35 | return self._documents 36 | 37 | 38 | class AsyncKnowledgeClient(object): 39 | def __init__(self, base_url: str, requester: Requester): 40 | warnings.warn( 41 | "The 'coze.knowledge' module is deprecated and will be removed in a future version. " 42 | "Please use 'coze.datasets' instead.", 43 | DeprecationWarning, 44 | stacklevel=2, 45 | ) 46 | self._base_url = remove_url_trailing_slash(base_url) 47 | self._requester = requester 48 | self._documents: Optional[AsyncDocumentsClient] = None 49 | 50 | @property 51 | def documents(self) -> "AsyncDocumentsClient": 52 | warnings.warn( 53 | "The 'coze.knowledge.documents' module is deprecated and will be removed in a future version. " 54 | "Please use 'coze.datasets.documents' instead.", 55 | DeprecationWarning, 56 | stacklevel=2, 57 | ) 58 | if self._documents is None: 59 | from .documents import AsyncDocumentsClient 60 | 61 | self._documents = AsyncDocumentsClient(base_url=self._base_url, requester=self._requester) 62 | return self._documents 63 | -------------------------------------------------------------------------------- /cozepy/log.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logger = logging.getLogger("cozepy") 4 | 5 | # config logger 6 | formatter = logging.Formatter("[cozepy][%(levelname)s][%(asctime)s] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") 7 | handler = logging.StreamHandler() 8 | handler.setFormatter(formatter) 9 | logger.propagate = False 10 | logger.addHandler(handler) 11 | 12 | 13 | def setup_logging(level: int = logging.WARNING) -> None: 14 | if level not in [ 15 | logging.FATAL, 16 | logging.ERROR, 17 | logging.WARNING, 18 | logging.INFO, 19 | logging.DEBUG, 20 | logging.NOTSET, 21 | ]: 22 | raise ValueError(f"invalid log level: {level}") 23 | 24 | logger.setLevel(level) 25 | 26 | 27 | log_fatal = logger.fatal 28 | log_error = logger.error 29 | log_warning = logger.warning 30 | log_info = logger.info 31 | log_debug = logger.debug 32 | 33 | setup_logging(logging.WARNING) 34 | -------------------------------------------------------------------------------- /cozepy/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coze-dev/coze-py/628f590f17fa1b1076cb06728448202db2906a8d/cozepy/py.typed -------------------------------------------------------------------------------- /cozepy/templates/__init__.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | from typing import Optional 3 | 4 | from cozepy.model import CozeModel 5 | from cozepy.request import Requester 6 | from cozepy.util import remove_url_trailing_slash 7 | 8 | 9 | class TemplateEntityType(str, Enum): 10 | AGENT = "agent" 11 | 12 | 13 | class TemplateDuplicateResp(CozeModel): 14 | entity_id: str 15 | entity_type: TemplateEntityType 16 | 17 | 18 | class TemplatesClient(object): 19 | def __init__(self, base_url: str, requester: Requester): 20 | self._base_url = remove_url_trailing_slash(base_url) 21 | self._requester = requester 22 | 23 | def duplicate( 24 | self, *, template_id: str, workspace_id: str, name: Optional[str] = None, **kwargs 25 | ) -> TemplateDuplicateResp: 26 | url = f"{self._base_url}/v1/templates/{template_id}/duplicate" 27 | headers: Optional[dict] = kwargs.get("headers") 28 | body = { 29 | "workspace_id": workspace_id, 30 | "name": name, 31 | } 32 | return self._requester.request("post", url, False, TemplateDuplicateResp, headers=headers, body=body) 33 | 34 | 35 | class AsyncTemplatesClient(object): 36 | def __init__(self, base_url: str, requester: Requester): 37 | self._base_url = remove_url_trailing_slash(base_url) 38 | self._requester = requester 39 | 40 | async def duplicate( 41 | self, *, template_id: str, workspace_id: str, name: Optional[str] = None, **kwargs 42 | ) -> TemplateDuplicateResp: 43 | url = f"{self._base_url}/v1/templates/{template_id}/duplicate" 44 | headers: Optional[dict] = kwargs.get("headers") 45 | body = { 46 | "workspace_id": workspace_id, 47 | "name": name, 48 | } 49 | return await self._requester.arequest("post", url, False, TemplateDuplicateResp, headers=headers, body=body) 50 | -------------------------------------------------------------------------------- /cozepy/users/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from cozepy.model import CozeModel 4 | from cozepy.request import Requester 5 | from cozepy.util import remove_url_trailing_slash 6 | 7 | 8 | class User(CozeModel): 9 | user_id: str 10 | user_name: str 11 | nick_name: str 12 | avatar_url: str 13 | 14 | 15 | class UsersClient(object): 16 | def __init__(self, base_url: str, requester: Requester): 17 | self._base_url = remove_url_trailing_slash(base_url) 18 | self._requester = requester 19 | 20 | def me(self, **kwargs) -> User: 21 | url = f"{self._base_url}/v1/users/me" 22 | headers: Optional[dict] = kwargs.get("headers") 23 | return self._requester.request("get", url, False, User, headers=headers) 24 | 25 | 26 | class AsyncUsersClient(object): 27 | def __init__(self, base_url: str, requester: Requester): 28 | self._base_url = remove_url_trailing_slash(base_url) 29 | self._requester = requester 30 | 31 | async def me(self, **kwargs) -> User: 32 | url = f"{self._base_url}/v1/users/me" 33 | headers: Optional[dict] = kwargs.get("headers") 34 | return await self._requester.arequest("get", url, False, User, headers=headers) 35 | -------------------------------------------------------------------------------- /cozepy/util.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import hashlib 3 | import inspect 4 | import random 5 | import sys 6 | import wave 7 | 8 | from pydantic import BaseModel 9 | 10 | if sys.version_info < (3, 10): 11 | 12 | async def anext(iterator, default=None): 13 | try: 14 | return await iterator.__anext__() 15 | except StopAsyncIteration: 16 | if default is not None: 17 | return default 18 | raise 19 | else: 20 | from builtins import anext 21 | 22 | _ = anext 23 | 24 | 25 | def base64_encode_string(s: str) -> str: 26 | return base64.standard_b64encode(s.encode("utf-8")).decode("utf-8") 27 | 28 | 29 | def random_hex(length): 30 | hex_characters = "0123456789abcdef" 31 | return "".join(random.choice(hex_characters) for _ in range(length)) 32 | 33 | 34 | def gen_s256_code_challenge(code_verifier): 35 | # 1. SHA256(ASCII(code_verifier)) 36 | sha256_hash = hashlib.sha256(code_verifier.encode("ascii")).digest() 37 | # 2. BASE64URL-ENCODE 38 | code_challenge = base64.urlsafe_b64encode(sha256_hash).decode("ascii") 39 | # 3. remove = 40 | code_challenge = code_challenge.rstrip("=") 41 | return code_challenge 42 | 43 | 44 | def remove_url_trailing_slash(base_url: str) -> str: 45 | if base_url: 46 | return base_url.rstrip("/") 47 | return base_url 48 | 49 | 50 | def http_base_url_to_ws(base_url: str) -> str: 51 | if not base_url: 52 | raise ValueError("base_url cannot be empty") 53 | if not base_url.startswith("https://"): 54 | raise ValueError("base_url must start with 'https://'") 55 | base_url = base_url.replace("https://", "wss://") 56 | 57 | if "api-" in base_url: 58 | return base_url.replace("api-", "ws-") 59 | return base_url.replace("api.", "ws.") 60 | 61 | 62 | def remove_none_values(d: dict) -> dict: 63 | return {k: v for k, v in d.items() if v is not None} 64 | 65 | 66 | def write_pcm_to_wav_file( 67 | pcm_data: bytes, filepath: str, channels: int = 1, sample_width: int = 2, frame_rate: int = 24000 68 | ): 69 | """ 70 | Save PCM binary data to WAV file 71 | 72 | :param pcm_data: PCM binary data (24kHz, 16-bit, 1 channel, little-endian) 73 | :param filepath: Output WAV filename 74 | """ 75 | 76 | with wave.open(filepath, "wb") as wav_file: 77 | # Set WAV file parameters 78 | wav_file.setnchannels(channels) 79 | wav_file.setsampwidth(sample_width) 80 | wav_file.setframerate(frame_rate) 81 | 82 | # Write PCM data 83 | wav_file.writeframes(pcm_data) 84 | 85 | 86 | def get_methods(cls, prefix="on"): 87 | """ 88 | Get all methods of `cls` with prefix `prefix` 89 | """ 90 | method_list = [] 91 | for name in dir(cls): 92 | if not name.startswith(prefix): 93 | continue 94 | 95 | attr = getattr(cls, name) 96 | if inspect.ismethod(attr) or inspect.isfunction(attr): 97 | sig = inspect.signature(attr) 98 | params = list(sig.parameters.values()) 99 | 100 | if inspect.ismethod(attr) and not isinstance(attr, staticmethod): 101 | params = params[1:] # 去掉self/cls 102 | 103 | method_list.append( 104 | { 105 | "function": attr, 106 | "parameters": params, 107 | "type": "staticmethod" 108 | if isinstance(attr, staticmethod) 109 | else "classmethod" 110 | if inspect.ismethod(attr) 111 | else "instancemethod", 112 | } 113 | ) 114 | return method_list 115 | 116 | 117 | def get_model_default(model: type, field_name: str): 118 | """ 119 | Get default value of a field from `model` which is subclass of BaseModel 120 | """ 121 | if issubclass(model, BaseModel): 122 | field = model.model_fields.get(field_name) 123 | if not field: 124 | return None 125 | 126 | if field.default_factory is not None: 127 | return field.default_factory() 128 | 129 | return field.default if field.default is not None else None 130 | return None 131 | -------------------------------------------------------------------------------- /cozepy/variables/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | from cozepy.model import CozeModel, ListResponse 4 | from cozepy.request import Requester 5 | from cozepy.util import remove_none_values, remove_url_trailing_slash 6 | 7 | 8 | class VariableValue(CozeModel): 9 | keyword: str 10 | value: str 11 | create_time: int = 0 12 | update_time: int = 0 13 | 14 | 15 | class _PrivateVariablesRetrieveData(CozeModel): 16 | items: List[VariableValue] 17 | 18 | 19 | class UpdateVariableResp(CozeModel): 20 | pass 21 | 22 | 23 | class VariablesClient(object): 24 | def __init__(self, base_url: str, requester: Requester): 25 | self._base_url = remove_url_trailing_slash(base_url) 26 | self._requester = requester 27 | 28 | def retrieve( 29 | self, 30 | *, 31 | connector_uid: str, 32 | keywords: List[str], 33 | app_id: Optional[str] = None, 34 | bot_id: Optional[str] = None, 35 | connector_id: Optional[str] = None, 36 | ) -> ListResponse[VariableValue]: 37 | url = f"{self._base_url}/v1/variables" 38 | params = remove_none_values( 39 | { 40 | "app_id": app_id, 41 | "bot_id": bot_id, 42 | "connector_id": connector_id, 43 | "connector_uid": connector_uid, 44 | "keywords": ",".join(keywords), 45 | } 46 | ) 47 | res = self._requester.request("get", url, False, _PrivateVariablesRetrieveData, params=params) 48 | return ListResponse[VariableValue](raw_response=res.response._raw_response, data=res.items) 49 | 50 | def update( 51 | self, 52 | *, 53 | connector_uid: str, 54 | data: List[VariableValue], 55 | app_id: Optional[str] = None, 56 | bot_id: Optional[str] = None, 57 | connector_id: Optional[str] = None, 58 | ) -> UpdateVariableResp: 59 | url = f"{self._base_url}/v1/variables" 60 | body = remove_none_values( 61 | { 62 | "app_id": app_id, 63 | "bot_id": bot_id, 64 | "connector_id": connector_id, 65 | "connector_uid": connector_uid, 66 | "data": [ 67 | { 68 | "keyword": v.keyword, 69 | "value": v.value, 70 | } 71 | for v in data 72 | ], 73 | } 74 | ) 75 | return self._requester.request( 76 | "put", 77 | url, 78 | False, 79 | cast=UpdateVariableResp, 80 | body=body, 81 | ) 82 | 83 | 84 | class AsyncVariablesClient(object): 85 | def __init__(self, base_url: str, requester: Requester): 86 | self._base_url = remove_url_trailing_slash(base_url) 87 | self._requester = requester 88 | 89 | async def retrieve( 90 | self, 91 | *, 92 | connector_uid: str, 93 | keywords: List[str], 94 | app_id: Optional[str] = None, 95 | bot_id: Optional[str] = None, 96 | connector_id: Optional[str] = None, 97 | ) -> ListResponse[VariableValue]: 98 | url = f"{self._base_url}/v1/variables" 99 | params = remove_none_values( 100 | { 101 | "app_id": app_id, 102 | "bot_id": bot_id, 103 | "connector_id": connector_id, 104 | "connector_uid": connector_uid, 105 | "keywords": ",".join(keywords), 106 | } 107 | ) 108 | res = await self._requester.arequest("get", url, False, _PrivateVariablesRetrieveData, params=params) 109 | return ListResponse[VariableValue](raw_response=res.response._raw_response, data=res.items) 110 | 111 | async def update( 112 | self, 113 | *, 114 | connector_uid: str, 115 | data: List[VariableValue], 116 | app_id: Optional[str] = None, 117 | bot_id: Optional[str] = None, 118 | connector_id: Optional[str] = None, 119 | ) -> UpdateVariableResp: 120 | url = f"{self._base_url}/v1/variables" 121 | body = remove_none_values( 122 | { 123 | "app_id": app_id, 124 | "bot_id": bot_id, 125 | "connector_id": connector_id, 126 | "connector_uid": connector_uid, 127 | "data": [ 128 | { 129 | "keyword": v.keyword, 130 | "value": v.value, 131 | } 132 | for v in data 133 | ], 134 | } 135 | ) 136 | return await self._requester.arequest( 137 | "put", 138 | url, 139 | False, 140 | cast=UpdateVariableResp, 141 | body=body, 142 | ) 143 | -------------------------------------------------------------------------------- /cozepy/version.py: -------------------------------------------------------------------------------- 1 | import json 2 | import platform 3 | import sys 4 | from functools import lru_cache 5 | 6 | VERSION = "0.16.2" 7 | 8 | 9 | def get_os_version() -> str: 10 | os_name = platform.system().lower() 11 | if os_name == "darwin": 12 | os_name = "macos" 13 | 14 | if os_name == "macos": 15 | os_version = platform.mac_ver()[0] 16 | elif os_name == "windows": 17 | os_version = platform.win32_ver()[0] 18 | elif os_name == "linux": 19 | try: 20 | import distro 21 | 22 | os_version = distro.version(pretty=False, best=True) 23 | except ImportError: 24 | os_version = platform.release() 25 | else: 26 | os_version = platform.release() 27 | 28 | return os_version 29 | 30 | 31 | @lru_cache(maxsize=1) 32 | def user_agent(): 33 | python_version = ".".join(map(str, sys.version_info[:2])) 34 | 35 | os_name = platform.system().lower() 36 | os_version = get_os_version() 37 | 38 | return f"cozepy/{VERSION} python/{python_version} {os_name}/{os_version}".lower() 39 | 40 | 41 | @lru_cache(maxsize=1) 42 | def coze_client_user_agent() -> str: 43 | ua = { 44 | "version": VERSION, 45 | "lang": "python", 46 | "lang_version": ".".join(map(str, sys.version_info[:2])), 47 | "os_name": platform.system().lower(), 48 | "os_version": get_os_version(), 49 | } 50 | 51 | return json.dumps(ua) 52 | -------------------------------------------------------------------------------- /cozepy/websockets/__init__.py: -------------------------------------------------------------------------------- 1 | from cozepy.request import Requester 2 | from cozepy.util import http_base_url_to_ws, remove_url_trailing_slash 3 | 4 | from .audio import AsyncWebsocketsAudioClient, WebsocketsAudioClient 5 | from .chat import AsyncWebsocketsChatBuildClient, WebsocketsChatBuildClient 6 | 7 | 8 | class WebsocketsClient(object): 9 | def __init__(self, base_url: str, requester: Requester): 10 | self._base_url = http_base_url_to_ws(remove_url_trailing_slash(base_url)) 11 | self._requester = requester 12 | 13 | @property 14 | def audio(self) -> WebsocketsAudioClient: 15 | return WebsocketsAudioClient( 16 | base_url=self._base_url, 17 | requester=self._requester, 18 | ) 19 | 20 | @property 21 | def chat(self) -> WebsocketsChatBuildClient: 22 | return WebsocketsChatBuildClient( 23 | base_url=self._base_url, 24 | requester=self._requester, 25 | ) 26 | 27 | 28 | class AsyncWebsocketsClient(object): 29 | def __init__(self, base_url: str, requester: Requester): 30 | self._base_url = http_base_url_to_ws(remove_url_trailing_slash(base_url)) 31 | self._requester = requester 32 | 33 | @property 34 | def audio(self) -> AsyncWebsocketsAudioClient: 35 | return AsyncWebsocketsAudioClient( 36 | base_url=self._base_url, 37 | requester=self._requester, 38 | ) 39 | 40 | @property 41 | def chat(self) -> AsyncWebsocketsChatBuildClient: 42 | return AsyncWebsocketsChatBuildClient( 43 | base_url=self._base_url, 44 | requester=self._requester, 45 | ) 46 | -------------------------------------------------------------------------------- /cozepy/websockets/audio/__init__.py: -------------------------------------------------------------------------------- 1 | from cozepy.request import Requester 2 | 3 | from .speech import AsyncWebsocketsAudioSpeechBuildClient, WebsocketsAudioSpeechBuildClient 4 | from .transcriptions import AsyncWebsocketsAudioTranscriptionsBuildClient, WebsocketsAudioTranscriptionsBuildClient 5 | 6 | 7 | class WebsocketsAudioClient(object): 8 | def __init__(self, base_url: str, requester: Requester): 9 | self._base_url = base_url 10 | self._requester = requester 11 | 12 | @property 13 | def transcriptions(self) -> "WebsocketsAudioTranscriptionsBuildClient": 14 | return WebsocketsAudioTranscriptionsBuildClient( 15 | base_url=self._base_url, 16 | requester=self._requester, 17 | ) 18 | 19 | @property 20 | def speech(self) -> "WebsocketsAudioSpeechBuildClient": 21 | return WebsocketsAudioSpeechBuildClient( 22 | base_url=self._base_url, 23 | requester=self._requester, 24 | ) 25 | 26 | 27 | class AsyncWebsocketsAudioClient(object): 28 | def __init__(self, base_url: str, requester: Requester): 29 | self._base_url = base_url 30 | self._requester = requester 31 | 32 | @property 33 | def transcriptions(self) -> "AsyncWebsocketsAudioTranscriptionsBuildClient": 34 | return AsyncWebsocketsAudioTranscriptionsBuildClient( 35 | base_url=self._base_url, 36 | requester=self._requester, 37 | ) 38 | 39 | @property 40 | def speech(self) -> "AsyncWebsocketsAudioSpeechBuildClient": 41 | return AsyncWebsocketsAudioSpeechBuildClient( 42 | base_url=self._base_url, 43 | requester=self._requester, 44 | ) 45 | -------------------------------------------------------------------------------- /cozepy/workflows/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, Optional 2 | 3 | from cozepy.request import Requester 4 | from cozepy.util import remove_url_trailing_slash 5 | 6 | if TYPE_CHECKING: 7 | from .chat import AsyncWorkflowsChatClient, WorkflowsChatClient 8 | from .runs import AsyncWorkflowsRunsClient, WorkflowsRunsClient 9 | 10 | 11 | class WorkflowsClient(object): 12 | def __init__(self, base_url: str, requester: Requester): 13 | self._base_url = remove_url_trailing_slash(base_url) 14 | self._requester = requester 15 | self._runs: Optional[WorkflowsRunsClient] = None 16 | self._chat: Optional[WorkflowsChatClient] = None 17 | 18 | @property 19 | def runs(self) -> "WorkflowsRunsClient": 20 | if not self._runs: 21 | from .runs import WorkflowsRunsClient 22 | 23 | self._runs = WorkflowsRunsClient(self._base_url, self._requester) 24 | return self._runs 25 | 26 | @property 27 | def chat(self) -> "WorkflowsChatClient": 28 | if not self._chat: 29 | from .chat import WorkflowsChatClient 30 | 31 | self._chat = WorkflowsChatClient(self._base_url, self._requester) 32 | return self._chat 33 | 34 | 35 | class AsyncWorkflowsClient(object): 36 | def __init__(self, base_url: str, requester: Requester): 37 | self._base_url = remove_url_trailing_slash(base_url) 38 | self._requester = requester 39 | self._runs: Optional[AsyncWorkflowsRunsClient] = None 40 | self._chat: Optional[AsyncWorkflowsChatClient] = None 41 | 42 | @property 43 | def runs(self) -> "AsyncWorkflowsRunsClient": 44 | if not self._runs: 45 | from .runs import AsyncWorkflowsRunsClient 46 | 47 | self._runs = AsyncWorkflowsRunsClient(self._base_url, self._requester) 48 | return self._runs 49 | 50 | @property 51 | def chat(self) -> "AsyncWorkflowsChatClient": 52 | if not self._chat: 53 | from .chat import AsyncWorkflowsChatClient 54 | 55 | self._chat = AsyncWorkflowsChatClient(self._base_url, self._requester) 56 | return self._chat 57 | -------------------------------------------------------------------------------- /cozepy/workflows/runs/run_histories/__init__.py: -------------------------------------------------------------------------------- 1 | from enum import Enum, IntEnum 2 | from typing import Optional 3 | 4 | from pydantic import field_validator 5 | 6 | from cozepy.model import CozeModel, ListResponse 7 | from cozepy.request import Requester 8 | from cozepy.util import remove_url_trailing_slash 9 | 10 | 11 | class WorkflowExecuteStatus(str, Enum): 12 | # Execution succeeded. 13 | SUCCESS = "Success" 14 | # Execution in progress. 15 | RUNNING = "Running" 16 | # Execution failed. 17 | FAIL = "Fail" 18 | 19 | 20 | class WorkflowRunMode(IntEnum): 21 | SYNCHRONOUS = 0 22 | STREAMING = 1 23 | ASYNCHRONOUS = 2 24 | 25 | 26 | class WorkflowRunHistory(CozeModel): 27 | # The ID of execute. 28 | execute_id: str 29 | 30 | # Execute status: 31 | # success: Execution succeeded. 32 | # running: Execution in progress. 33 | # fail: Execution failed. 34 | execute_status: WorkflowExecuteStatus 35 | 36 | # The Bot ID specified when executing the workflow. Returns 0 if no Bot ID was specified. 37 | bot_id: str 38 | 39 | # The release connector ID of the agent. By default, only the Agent as API connector is 40 | # displayed, and the connector ID is 1024. 41 | connector_id: str 42 | 43 | # User ID, the user_id specified by the ext field when executing the workflow. If not 44 | # specified, the token applicant's button ID is returned. 45 | connector_uid: str 46 | 47 | # How the workflow runs: 48 | # 0: Synchronous operation. 49 | # 1: Streaming operation. 50 | # 2: Asynchronous operation. 51 | run_mode: WorkflowRunMode 52 | 53 | # The Log ID of the asynchronously running workflow. If the workflow is executed abnormally, 54 | # you can contact the service team to troubleshoot the problem through the Log ID. 55 | logid: str 56 | 57 | # The start time of the workflow, in Unix time timestamp format, in seconds. 58 | create_time: int 59 | 60 | # The workflow resume running time, in Unix time timestamp format, in seconds. 61 | update_time: int 62 | 63 | # The output of the workflow is usually a JSON serialized string, but it may also be a 64 | # non-JSON structured string. 65 | output: str 66 | 67 | # Status code. 0 represents a successful API call. Other values indicate that the call has failed. You can 68 | # determine the detailed reason for the error through the error_message field. 69 | error_code: int 70 | 71 | # Status message. You can get detailed error information when the API call fails. 72 | error_message: Optional[str] = "" 73 | 74 | # Workflow trial run debugging page. Visit this page to view the running results, input 75 | # and output information of each workflow node. 76 | debug_url: str 77 | 78 | @field_validator("error_code", mode="before") 79 | @classmethod 80 | def error_code_empty_str_to_zero(cls, v): 81 | if v == "": 82 | return 0 83 | return v 84 | 85 | 86 | class WorkflowsRunsRunHistoriesClient(object): 87 | def __init__(self, base_url: str, requester: Requester): 88 | self._base_url = remove_url_trailing_slash(base_url) 89 | self._requester = requester 90 | 91 | def retrieve(self, *, workflow_id: str, execute_id: str) -> WorkflowRunHistory: 92 | """ 93 | After the workflow runs async, retrieve the execution results. 94 | 95 | docs cn: https://www.coze.cn/docs/developer_guides/workflow_history 96 | 97 | :param workflow_id: The ID of the workflow. 98 | :param execute_id: The ID of the workflow async execute. 99 | :return: The result of the workflow execution 100 | """ 101 | url = f"{self._base_url}/v1/workflows/{workflow_id}/run_histories/{execute_id}" 102 | res = self._requester.request("get", url, False, ListResponse[WorkflowRunHistory]) 103 | data = res.data[0] 104 | data._raw_response = res._raw_response 105 | return data 106 | 107 | 108 | class AsyncWorkflowsRunsRunHistoriesClient(object): 109 | def __init__(self, base_url: str, requester: Requester): 110 | self._base_url = remove_url_trailing_slash(base_url) 111 | self._requester = requester 112 | 113 | async def retrieve(self, *, workflow_id: str, execute_id: str) -> WorkflowRunHistory: 114 | """ 115 | After the workflow runs async, retrieve the execution results. 116 | 117 | docs cn: https://www.coze.cn/docs/developer_guides/workflow_history 118 | 119 | :param workflow_id: The ID of the workflow. 120 | :param execute_id: The ID of the workflow async execute. 121 | :return: The result of the workflow execution 122 | """ 123 | url = f"{self._base_url}/v1/workflows/{workflow_id}/run_histories/{execute_id}" 124 | res = await self._requester.arequest("get", url, False, ListResponse[WorkflowRunHistory]) 125 | data = res.data[0] 126 | data._raw_response = res._raw_response 127 | return data 128 | -------------------------------------------------------------------------------- /cozepy/workspaces/__init__.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | from typing import List, Optional 3 | 4 | from cozepy.model import AsyncNumberPaged, CozeModel, HTTPRequest, NumberPaged, NumberPagedResponse 5 | from cozepy.request import Requester 6 | from cozepy.util import remove_url_trailing_slash 7 | 8 | 9 | class WorkspaceRoleType(str, Enum): 10 | OWNER = "owner" 11 | ADMIN = "admin" 12 | MEMBER = "member" 13 | 14 | 15 | class WorkspaceType(str, Enum): 16 | PERSONAL = "personal" 17 | TEAM = "team" 18 | 19 | 20 | class Workspace(CozeModel): 21 | # workspace id 22 | id: str 23 | # workspace name 24 | name: str 25 | # workspace icon url 26 | icon_url: str 27 | # user in workspace role type 28 | role_type: WorkspaceRoleType 29 | # workspace type 30 | workspace_type: WorkspaceType 31 | 32 | 33 | class _PrivateListWorkspacesData(CozeModel, NumberPagedResponse[Workspace]): 34 | total_count: int 35 | workspaces: List[Workspace] 36 | 37 | def get_total(self) -> Optional[int]: 38 | return self.total_count 39 | 40 | def get_has_more(self) -> Optional[bool]: 41 | return None 42 | 43 | def get_items(self) -> List[Workspace]: 44 | return self.workspaces 45 | 46 | 47 | class WorkspacesClient(object): 48 | """ 49 | Bot class. 50 | """ 51 | 52 | def __init__(self, base_url: str, requester: Requester): 53 | self._base_url = remove_url_trailing_slash(base_url) 54 | self._requester = requester 55 | 56 | def list(self, *, page_num: int = 1, page_size: int = 20, headers=None) -> NumberPaged[Workspace]: 57 | url = f"{self._base_url}/v1/workspaces" 58 | 59 | def request_maker(i_page_num: int, i_page_size: int) -> HTTPRequest: 60 | return self._requester.make_request( 61 | "GET", 62 | url, 63 | headers=headers, 64 | params={ 65 | "page_size": i_page_size, 66 | "page_num": i_page_num, 67 | }, 68 | cast=_PrivateListWorkspacesData, 69 | stream=False, 70 | ) 71 | 72 | return NumberPaged( 73 | page_num=page_num, 74 | page_size=page_size, 75 | requestor=self._requester, 76 | request_maker=request_maker, 77 | ) 78 | 79 | 80 | class AsyncWorkspacesClient(object): 81 | """ 82 | Bot class. 83 | """ 84 | 85 | def __init__(self, base_url: str, requester: Requester): 86 | self._base_url = remove_url_trailing_slash(base_url) 87 | self._requester = requester 88 | 89 | async def list(self, *, page_num: int = 1, page_size: int = 20, headers=None) -> AsyncNumberPaged[Workspace]: 90 | url = f"{self._base_url}/v1/workspaces" 91 | 92 | async def request_maker(i_page_num: int, i_page_size: int) -> HTTPRequest: 93 | return await self._requester.amake_request( 94 | "GET", 95 | url, 96 | headers=headers, 97 | params={ 98 | "page_size": i_page_size, 99 | "page_num": i_page_num, 100 | }, 101 | cast=_PrivateListWorkspacesData, 102 | stream=False, 103 | ) 104 | 105 | return await AsyncNumberPaged.build( 106 | page_num=page_num, 107 | page_size=page_size, 108 | requestor=self._requester, 109 | request_maker=request_maker, 110 | ) 111 | -------------------------------------------------------------------------------- /examples/audio.py: -------------------------------------------------------------------------------- 1 | """ 2 | This example is about how to use audio api. 3 | """ 4 | 5 | import logging 6 | import os 7 | 8 | from cozepy import ( 9 | COZE_CN_BASE_URL, 10 | Coze, 11 | TokenAuth, 12 | setup_logging, 13 | ) 14 | 15 | # Get an access_token through personal access token or oauth. 16 | coze_api_token = os.getenv("COZE_API_TOKEN") 17 | # The default access is api.coze.cn, but if you need to access api.coze.com, 18 | # please use base_url to configure the api endpoint to access 19 | coze_api_base = os.getenv("COZE_API_BASE") or COZE_CN_BASE_URL 20 | # Whether to print detailed logs 21 | is_debug = os.getenv("DEBUG") 22 | 23 | if is_debug: 24 | setup_logging(logging.DEBUG) 25 | 26 | # Init the Coze client through the access_token. 27 | coze = Coze(auth=TokenAuth(token=coze_api_token), base_url=coze_api_base) 28 | 29 | 30 | def get_voice_id() -> str: 31 | if os.getenv("COZE_VOICE_ID"): 32 | return os.getenv("COZE_VOICE_ID") 33 | voices = coze.audio.voices.list() 34 | for voice in voices.items: 35 | print("Get voice:", voice.voice_id, voice.name) 36 | 37 | return voices.items[-1].voice_id 38 | 39 | 40 | input_text = os.getenv("COZE_SPEECH_INPUT") or "你好世界" 41 | voice_id = get_voice_id() 42 | 43 | speech_file = coze.audio.speech.create(input=input_text, voice_id=voice_id) 44 | file_path = os.path.join(os.path.expanduser("~"), "Downloads", f"coze_{voice_id}_example.mp3") 45 | speech_file.write_to_file(file_path) 46 | print(f"Create speech of voice: {voice_id} to file: {file_path}") 47 | -------------------------------------------------------------------------------- /examples/auth_oauth_device.py: -------------------------------------------------------------------------------- 1 | """ 2 | This example is about how to use the device oauth process to acquire user authorization. 3 | """ 4 | 5 | # Firstly, users need to access https://www.coze.cn/open/oauth/apps. For the coze.com environment, 6 | # users need to access https://www.coze.com/open/oauth/apps to create an OAuth App of the type 7 | # of TVs/Limited Input devices/Command line programs. 8 | # The specific creation process can be referred to in the document: 9 | # https://www.coze.cn/docs/developer_guides/oauth_device_code. For the coze.com environment, it can be 10 | # accessed at https://www.coze.com/docs/developer_guides/oauth_device_code. 11 | # After the creation is completed, the client ID can be obtained. 12 | 13 | import os 14 | 15 | from cozepy import COZE_CN_BASE_URL, Coze, CozePKCEAuthError, CozePKCEAuthErrorType, DeviceOAuthApp, TokenAuth # noqa 16 | 17 | # The default access is api.coze.cn, but if you need to access api.coze.cn, 18 | # please use base_url to configure the api endpoint to access 19 | coze_api_base = os.getenv("COZE_API_BASE") or COZE_CN_BASE_URL 20 | 21 | # client ID 22 | device_oauth_client_id = os.getenv("COZE_DEVICE_OAUTH_CLIENT_ID") 23 | 24 | # The sdk offers the DeviceOAuthApp class to establish an authorization for PKCE OAuth. 25 | # Firstly, it is required to initialize the DeviceOAuthApp with the client ID. 26 | 27 | 28 | device_oauth_app = DeviceOAuthApp(client_id=device_oauth_client_id, base_url=coze_api_base) 29 | 30 | # In the device oauth authorization process, developers need to first call the interface 31 | # of Coze to generate the device code to obtain the user_code and device_code. Then generate 32 | # the authorization link through the user_code, guide the user to open the link, fill in the 33 | # user_code, and consent to the authorization. Developers need to call the interface of Coze 34 | # to generate the token through the device_code. When the user has not authorized or rejected 35 | # the authorization, the interface will throw an error and return a specific error code. 36 | # After the user consents to the authorization, the interface will succeed and return the 37 | # access_token. 38 | 39 | 40 | # First, make a call to obtain 'get_device_code' 41 | device_code = device_oauth_app.get_device_code() 42 | 43 | # The returned device_code contains an authorization link. Developers need to guide users 44 | # to open up this link. 45 | # open device_code.verification_url 46 | print("Please open url:", device_code.verification_url) 47 | 48 | # The developers then need to use the device_code to poll Coze's interface to obtain the token. 49 | # The SDK has encapsulated this part of the code in and handled the different returned error 50 | # codes. The developers only need to invoke get_access_token. 51 | try: 52 | oauth_token = device_oauth_app.get_access_token( 53 | device_code=device_code.device_code, 54 | poll=True, 55 | ) 56 | print("Get access token:", oauth_token.access_token) 57 | except CozePKCEAuthError as e: 58 | if e.error == CozePKCEAuthErrorType.ACCESS_DENIED: 59 | # The user rejected the authorization. 60 | # Developers need to guide the user to open the authorization link again. 61 | pass 62 | elif e.error == CozePKCEAuthErrorType.EXPIRED_TOKEN: 63 | # The token has expired. Developers need to guide the user to open 64 | # the authorization link again. 65 | pass 66 | else: 67 | # Other errors 68 | pass 69 | 70 | raise # for example, re-raise the error 71 | 72 | # use the access token to init Coze client 73 | coze = Coze(auth=TokenAuth(oauth_token.access_token), base_url=coze_api_base) 74 | 75 | # When the token expires, you can also refresh and re-obtain the token 76 | # oauth_token = device_oauth_app.refresh_access_token(oauth_token.refresh_token) 77 | -------------------------------------------------------------------------------- /examples/auth_oauth_jwt.py: -------------------------------------------------------------------------------- 1 | """ 2 | This example is about how to use the service jwt oauth process to acquire user authorization. 3 | """ 4 | 5 | # Firstly, users need to access https://www.coze.cn/open/oauth/apps. For the coze.com environment, 6 | # users need to access https://www.coze.com/open/oauth/apps to create an OAuth App of the type 7 | # of Service application. 8 | # The specific creation process can be referred to in the document: 9 | # https://www.coze.cn/docs/developer_guides/oauth_jwt. For the coze.com environment, it can be 10 | # accessed at https://www.coze.com/docs/developer_guides/oauth_jwt. 11 | # After the creation is completed, the client ID, private key, and public key id, can be obtained. 12 | # For the client secret and public key id, users need to keep it securely to avoid leakage. 13 | 14 | import os 15 | 16 | from cozepy import COZE_CN_BASE_URL, Coze, JWTAuth, JWTOAuthApp 17 | 18 | # The default access is api.coze.cn, but if you need to access api.coze.com, 19 | # please use base_url to configure the api endpoint to access 20 | coze_api_base = os.getenv("COZE_API_BASE") or COZE_CN_BASE_URL 21 | 22 | # client ID 23 | jwt_oauth_client_id = os.getenv("COZE_JWT_OAUTH_CLIENT_ID") 24 | # private key 25 | jwt_oauth_private_key = os.getenv("COZE_JWT_OAUTH_PRIVATE_KEY") 26 | # path to the private key file (usually with .pem extension) 27 | jwt_oauth_private_key_file_path = os.getenv("COZE_JWT_OAUTH_PRIVATE_KEY_FILE_PATH") 28 | # public key id 29 | jwt_oauth_public_key_id = os.getenv("COZE_JWT_OAUTH_PUBLIC_KEY_ID") 30 | 31 | if jwt_oauth_private_key_file_path: 32 | with open(jwt_oauth_private_key_file_path, "r") as f: 33 | jwt_oauth_private_key = f.read() 34 | 35 | 36 | # The sdk offers the JWTOAuthApp class to establish an authorization for Service OAuth. 37 | # Firstly, it is required to initialize the JWTOAuthApp. 38 | 39 | 40 | jwt_oauth_app = JWTOAuthApp( 41 | client_id=jwt_oauth_client_id, 42 | private_key=jwt_oauth_private_key, 43 | public_key_id=jwt_oauth_public_key_id, 44 | base_url=coze_api_base, 45 | ) 46 | 47 | # The jwt oauth type requires using private to be able to issue a jwt token, and through 48 | # the jwt token, apply for an access_token from the coze service. The sdk encapsulates 49 | # this procedure, and only needs to use get_access_token to obtain the access_token under 50 | # the jwt oauth process. 51 | 52 | # Generate the authorization token 53 | # The default ttl is 900s, and developers can customize the expiration time, which can be 54 | # set up to 24 hours at most. 55 | oauth_token = jwt_oauth_app.get_access_token(ttl=3600) 56 | 57 | # use the jwt oauth_app to init Coze client 58 | coze = Coze(auth=JWTAuth(oauth_app=jwt_oauth_app), base_url=coze_api_base) 59 | 60 | # The jwt oauth process does not support refreshing tokens. When the token expires, 61 | # just directly call get_access_token to generate a new token. 62 | print(coze.workspaces.list().items) 63 | -------------------------------------------------------------------------------- /examples/auth_oauth_pkce.py: -------------------------------------------------------------------------------- 1 | """ 2 | How to effectuate OpenAPI authorization through the OAuth Proof Key for Code Exchange method. 3 | """ 4 | 5 | # PKCE stands for Proof Key for Code Exchange, and it's an extension to the OAuth 2.0 authorization 6 | # code flow designed to enhance security for public clients, such as mobile and single-page 7 | # applications. 8 | 9 | # Firstly, users need to access https://www.coze.cn/open/oauth/apps. For the coze.com environment, 10 | # users need to access https://www.coze.com/open/oauth/apps to create an OAuth App of the type 11 | # of Mobile/PC/Single-page application. 12 | # The specific creation process can be referred to in the document: 13 | # https://www.coze.cn/docs/developer_guides/oauth_pkce. For the coze.com environment, it can be 14 | # accessed at https://www.coze.com/docs/developer_guides/oauth_pkce. 15 | # After the creation is completed, the client ID can be obtained. 16 | 17 | import os 18 | 19 | from cozepy import COZE_CN_BASE_URL, Coze, PKCEOAuthApp, TokenAuth 20 | 21 | # The default access is api.coze.cn, but if you need to access api.coze.com, 22 | # please use base_url to configure the api endpoint to access 23 | coze_api_base = os.getenv("COZE_API_BASE") or COZE_CN_BASE_URL 24 | 25 | # client ID 26 | pkce_oauth_client_id = os.getenv("COZE_PKCE_OAUTH_CLIENT_ID") 27 | # redirect link 28 | web_oauth_redirect_uri = os.getenv("COZE_WEB_OAUTH_REDIRECT_URI") 29 | 30 | # The sdk offers the PKCEOAuthApp class to establish an authorization for PKCE OAuth. 31 | # Firstly, it is required to initialize the PKCEOAuthApp with the client ID. 32 | 33 | 34 | pkce_oauth_app = PKCEOAuthApp(client_id=pkce_oauth_client_id, base_url=coze_api_base) 35 | 36 | # In the pkce oauth process, first, need to select a suitable code_challenge_method. 37 | # Coze supports two types: plain and s256. Then, based on the selected code_challenge_method 38 | # type, hash the code_verifier into the code_challenge. Finally, based on the callback address, 39 | # code_challenge, and code_challenge_method, an authorization link can be generated. 40 | 41 | 42 | # In the SDK, we have wrapped up the code_challenge process of PKCE. Developers only need 43 | # to select the code_challenge_method. 44 | code_verifier = "random code verifier" 45 | url = pkce_oauth_app.get_oauth_url( 46 | redirect_uri=web_oauth_redirect_uri, code_verifier=code_verifier, code_challenge_method="S256" 47 | ) 48 | 49 | # Developers should lead users to open up this authorization link. When the user 50 | # consents to the authorization, Coze will redirect with the code to the callback address 51 | # configured by the developer, and the developer can obtain this code. 52 | 53 | 54 | # Get from the query of the redirect interface: query.get('code') 55 | code = "mock code" 56 | 57 | # After obtaining the code after redirection, the interface to exchange the code for a 58 | # token can be invoked to generate the coze access_token of the authorized user. 59 | oauth_token = pkce_oauth_app.get_access_token( 60 | redirect_uri=web_oauth_redirect_uri, code=code, code_verifier=code_verifier 61 | ) 62 | 63 | # use the access token to init Coze client 64 | coze = Coze(auth=TokenAuth(oauth_token.access_token), base_url=coze_api_base) 65 | 66 | # When the token expires, you can also refresh and re-obtain the token 67 | oauth_token = pkce_oauth_app.refresh_access_token(oauth_token.refresh_token) 68 | -------------------------------------------------------------------------------- /examples/auth_oauth_web.py: -------------------------------------------------------------------------------- 1 | """ 2 | How to effectuate OpenAPI authorization through the OAuth authorization code method. 3 | """ 4 | 5 | # Firstly, users need to access https://www.coze.cn/open/oauth/apps. For the coze.com environment, 6 | # users need to access https://www.coze.com/open/oauth/apps to create an OAuth App of the type 7 | # of Web application. 8 | # The specific creation process can be referred to in the document: 9 | # https://www.coze.cn/docs/developer_guides/oauth_code. For the coze.com environment, it can be 10 | # accessed at https://www.coze.com/docs/developer_guides/oauth_code. 11 | # After the creation is completed, the client ID, client secret, and redirect link, can be 12 | # obtained. For the client secret, users need to keep it securely to avoid leakage. 13 | 14 | import os 15 | 16 | from cozepy import COZE_CN_BASE_URL, Coze, TokenAuth, WebOAuthApp 17 | 18 | # The default access is api.coze.cn, but if you need to access api.coze.com, 19 | # please use base_url to configure the api endpoint to access 20 | coze_api_base = os.getenv("COZE_API_BASE") or COZE_CN_BASE_URL 21 | 22 | # client ID 23 | web_oauth_client_id = os.getenv("COZE_WEB_OAUTH_CLIENT_ID") 24 | # client secret 25 | web_oauth_client_secret = os.getenv("COZE_WEB_OAUTH_CLIENT_SECRET") 26 | # redirect link 27 | web_oauth_redirect_uri = os.getenv("COZE_WEB_OAUTH_REDIRECT_URI") 28 | 29 | # The sdk offers the WebOAuthApp class to establish an authorization for Web OAuth. 30 | # Firstly, it is required to initialize the WebOAuthApp with the client ID and client secret. 31 | 32 | 33 | web_oauth_app = WebOAuthApp( 34 | client_id=web_oauth_client_id, 35 | client_secret=web_oauth_client_secret, 36 | base_url=coze_api_base, 37 | ) 38 | 39 | # The WebOAuth authorization process is to first generate a coze authorization link and 40 | # send it to the coze user requiring authorization. Once the coze user opens the link, 41 | # they can see the authorization consent button. 42 | 43 | # Generate the authorization link and direct the user to open it. 44 | url = web_oauth_app.get_oauth_url(redirect_uri=web_oauth_redirect_uri) 45 | 46 | # After the user clicks the authorization consent button, the coze web page will redirect 47 | # to the redirect address configured in the authorization link and carry the authorization 48 | # code and state parameters in the address via the query string. 49 | 50 | # Get from the query of the redirect interface: query.get('code') 51 | code = "mock code" 52 | 53 | # After obtaining the code after redirection, the interface to exchange the code for a 54 | # token can be invoked to generate the coze access_token of the authorized user. 55 | oauth_token = web_oauth_app.get_access_token(redirect_uri=web_oauth_redirect_uri, code=code) 56 | 57 | # use the access token to init Coze client 58 | coze = Coze(auth=TokenAuth(oauth_token.access_token), base_url=coze_api_base) 59 | 60 | # When the token expires, you can also refresh and re-obtain the token 61 | oauth_token = web_oauth_app.refresh_access_token(oauth_token.refresh_token) 62 | -------------------------------------------------------------------------------- /examples/auth_pat.py: -------------------------------------------------------------------------------- 1 | """ 2 | How to use personal access token to init Coze client. 3 | """ 4 | 5 | # Firstly, you need to access https://www.coze.cn/open/oauth/pats (for the coze.com environment, 6 | # visit https://www.coze.com/open/oauth/pats). 7 | # 8 | # Click to add a new token. After setting the appropriate name, expiration time, and 9 | # permissions, click OK to generate your personal access token. Please store it in a 10 | # secure environment to prevent this personal access token from being disclosed. 11 | 12 | import os 13 | 14 | from cozepy import COZE_CN_BASE_URL, AsyncCoze, AsyncTokenAuth, Coze, TokenAuth 15 | 16 | coze_api_token = os.getenv("COZE_API_TOKEN") 17 | # The default access is api.coze.cn, but if you need to access api.coze.com, 18 | # please use base_url to configure the api endpoint to access 19 | coze_api_base = os.getenv("COZE_API_BASE") or COZE_CN_BASE_URL 20 | 21 | # The Coze SDK offers the AuthToken class for constructing an Auth class based on a fixed 22 | # access token. Meanwhile, the Coze class enables the passing in of an Auth class to build 23 | # a coze client. 24 | # 25 | # Therefore, you can utilize the following code to initialize a coze client, or an asynchronous 26 | # coze client 27 | 28 | 29 | # Establish a synchronous coze client by using the access_token 30 | coze = Coze(auth=TokenAuth(token=coze_api_token), base_url=coze_api_base) 31 | 32 | # or 33 | # Establish an asynchronous coze client by using the access_token 34 | async_coze = AsyncCoze(auth=AsyncTokenAuth(token=coze_api_token), base_url=coze_api_base) 35 | -------------------------------------------------------------------------------- /examples/benchmark_ark_text.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | import time 4 | from typing import List 5 | 6 | 7 | def get_current_time_ms(): 8 | return int(time.time() * 1000) 9 | 10 | 11 | def cal_latency(current: int, latency_list: List[int]) -> str: 12 | if latency_list is None or len(latency_list) == 0: 13 | return "No latency data" 14 | if len(latency_list) == 1: 15 | return f"P99={latency_list[0]}ms, P90={latency_list[0]}ms, AVG={latency_list[0]}ms" 16 | 17 | # 对延迟数据进行排序 18 | sorted_latency = sorted(latency_list) 19 | length = len(sorted_latency) 20 | 21 | def fix_index(index): 22 | if index < 0: 23 | return 0 24 | if index >= length: 25 | return length - 1 26 | return index 27 | 28 | # 计算 P99 29 | p99_index = fix_index(round(length * 0.99) - 1) 30 | p99 = sorted_latency[p99_index] 31 | 32 | # 计算 P90 33 | p90_index = fix_index(round(length * 0.90) - 1) 34 | p90 = sorted_latency[p90_index] 35 | 36 | # 计算平均值 37 | avg = sum(sorted_latency) / length 38 | 39 | return f"P99={p99}ms, P90={p90}ms, AVG={avg:.2f}ms, CURRENT={current}ms" 40 | 41 | 42 | def test_latency(ep: str, token: str, text: str): 43 | from volcenginesdkarkruntime import Ark 44 | 45 | client = Ark(base_url="https://ark.cn-beijing.volces.com/api/v3", api_key=token) 46 | start = get_current_time_ms() 47 | stream = client.chat.completions.create( 48 | model=ep, 49 | messages=[ 50 | {"role": "user", "content": text}, 51 | ], 52 | stream=True, 53 | ) 54 | for chunk in stream: 55 | if not chunk.choices: 56 | continue 57 | 58 | if chunk.choices[0].delta.content: 59 | return ( 60 | stream.response.headers["x-request-id"], 61 | chunk.choices[0].delta.content, 62 | get_current_time_ms() - start, 63 | ) 64 | 65 | 66 | async def main(): 67 | ep = os.getenv("ARK_EP") 68 | token = os.getenv("ARK_TOKEN") 69 | text = os.getenv("COZE_TEXT") or "讲个笑话" 70 | 71 | times = 100 72 | text_latency = [] 73 | for i in range(times): 74 | logid, first_text, latency = test_latency(ep, token, text) 75 | text_latency.append(latency) 76 | print( 77 | f"[latency.ark.text] {i}, latency: {cal_latency(latency, text_latency)}, log: {logid}, text: {first_text}" 78 | ) 79 | 80 | 81 | if __name__ == "__main__": 82 | asyncio.run(main()) 83 | -------------------------------------------------------------------------------- /examples/benchmark_text_chat.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import json 3 | import logging 4 | import os 5 | import time 6 | from typing import List, Optional 7 | 8 | from cozepy import ( 9 | COZE_CN_BASE_URL, 10 | ChatEventType, 11 | Coze, 12 | DeviceOAuthApp, 13 | Message, 14 | TokenAuth, 15 | setup_logging, 16 | ) 17 | 18 | 19 | def get_coze_api_base() -> str: 20 | # The default access is api.coze.cn, but if you need to access api.coze.com, 21 | # please use base_url to configure the api endpoint to access 22 | coze_api_base = os.getenv("COZE_API_BASE") 23 | if coze_api_base: 24 | return coze_api_base 25 | 26 | return COZE_CN_BASE_URL # default 27 | 28 | 29 | def get_coze_api_token(workspace_id: Optional[str] = None) -> str: 30 | # Get an access_token through personal access token or oauth. 31 | coze_api_token = os.getenv("COZE_API_TOKEN") 32 | if coze_api_token: 33 | return coze_api_token 34 | 35 | coze_api_base = get_coze_api_base() 36 | 37 | device_oauth_app = DeviceOAuthApp(client_id="57294420732781205987760324720643.app.coze", base_url=coze_api_base) 38 | device_code = device_oauth_app.get_device_code(workspace_id) 39 | print(f"Please Open: {device_code.verification_url} to get the access token") 40 | return device_oauth_app.get_access_token(device_code=device_code.device_code, poll=True).access_token 41 | 42 | 43 | def setup_examples_logger(): 44 | coze_log = os.getenv("COZE_LOG") 45 | if coze_log: 46 | setup_logging(logging.getLevelNamesMapping().get(coze_log.upper(), logging.INFO)) 47 | 48 | 49 | def get_current_time_ms(): 50 | return int(time.time() * 1000) 51 | 52 | 53 | setup_examples_logger() 54 | 55 | kwargs = json.loads(os.getenv("COZE_KWARGS") or "{}") 56 | 57 | 58 | def cal_latency(current: int, latency_list: List[int]) -> str: 59 | if latency_list is None or len(latency_list) == 0: 60 | return "No latency data" 61 | if len(latency_list) == 1: 62 | return f"P99={latency_list[0]}ms, P90={latency_list[0]}ms, AVG={latency_list[0]}ms" 63 | 64 | # 对延迟数据进行排序 65 | sorted_latency = sorted(latency_list) 66 | length = len(sorted_latency) 67 | 68 | def fix_index(index): 69 | if index < 0: 70 | return 0 71 | if index >= length: 72 | return length - 1 73 | return index 74 | 75 | # 计算 P99 76 | p99_index = fix_index(round(length * 0.99) - 1) 77 | p99 = sorted_latency[p99_index] 78 | 79 | # 计算 P90 80 | p90_index = fix_index(round(length * 0.90) - 1) 81 | p90 = sorted_latency[p90_index] 82 | 83 | # 计算平均值 84 | avg = sum(sorted_latency) / length 85 | 86 | return f"P99={p99}ms, P90={p90}ms, AVG={avg:.2f}ms, CURRENT={current}ms" 87 | 88 | 89 | async def test_latency(coze: Coze, bot_id: str, text: str) -> (str, str, int): 90 | start = get_current_time_ms() 91 | stream = coze.chat.stream( 92 | bot_id=bot_id, 93 | user_id="user id", 94 | additional_messages=[ 95 | Message.build_user_question_text(text), 96 | ], 97 | ) 98 | for event in stream: 99 | if event.event == ChatEventType.CONVERSATION_MESSAGE_DELTA: 100 | return stream.response.logid, event.message.content, get_current_time_ms() - start 101 | 102 | 103 | async def main(): 104 | coze_api_token = get_coze_api_token() 105 | coze_api_base = get_coze_api_base() 106 | bot_id = os.getenv("COZE_BOT_ID") 107 | text = os.getenv("COZE_TEXT") or "讲个笑话" 108 | 109 | # Initialize Coze client 110 | coze = Coze( 111 | auth=TokenAuth(coze_api_token), 112 | base_url=coze_api_base, 113 | ) 114 | 115 | times = 100 116 | text_latency = [] 117 | for i in range(times): 118 | logid, first_text, latency = await test_latency(coze, bot_id, text) 119 | text_latency.append(latency) 120 | print(f"[latency.text] {i}, latency: {cal_latency(latency, text_latency)}, log: {logid}, text: {first_text}") 121 | 122 | 123 | if __name__ == "__main__": 124 | asyncio.run(main()) 125 | -------------------------------------------------------------------------------- /examples/bot_create.py: -------------------------------------------------------------------------------- 1 | """ 2 | This example is for describing how to create a bot. 3 | """ 4 | 5 | import logging 6 | import os 7 | import sys 8 | from typing import Optional 9 | 10 | from cozepy import ( 11 | COZE_CN_BASE_URL, 12 | BotSuggestReplyInfo, 13 | Coze, 14 | DeviceOAuthApp, 15 | SuggestReplyMode, 16 | TokenAuth, 17 | setup_logging, 18 | ) 19 | 20 | 21 | def get_coze_api_base() -> str: 22 | # The default access is api.coze.cn, but if you need to access api.coze.com, 23 | # please use base_url to configure the api endpoint to access 24 | coze_api_base = os.getenv("COZE_API_BASE") 25 | if coze_api_base: 26 | return coze_api_base 27 | 28 | return COZE_CN_BASE_URL # default 29 | 30 | 31 | def get_coze_api_token(workspace_id: Optional[str] = None) -> str: 32 | # Get an access_token through personal access token or oauth. 33 | coze_api_token = os.getenv("COZE_API_TOKEN") 34 | if coze_api_token: 35 | return coze_api_token 36 | 37 | coze_api_base = get_coze_api_base() 38 | 39 | device_oauth_app = DeviceOAuthApp(client_id="57294420732781205987760324720643.app.coze", base_url=coze_api_base) 40 | device_code = device_oauth_app.get_device_code(workspace_id) 41 | print(f"Please Open: {device_code.verification_url} to get the access token") 42 | return device_oauth_app.get_access_token(device_code=device_code.device_code, poll=True).access_token 43 | 44 | 45 | # Init the Coze client through the access_token. 46 | coze = Coze(auth=TokenAuth(token=get_coze_api_token()), base_url=get_coze_api_base()) 47 | # workspace id 48 | workspace_id = os.getenv("COZE_WORKSPACE_ID") or "your workspace id" 49 | # bot id 50 | bot_id = os.getenv("COZE_BOT_ID") or "your bot id" 51 | avatar_path = "" if len(sys.argv) < 2 else sys.argv[1] 52 | # Whether to print detailed logs 53 | is_debug = os.getenv("DEBUG") 54 | 55 | if is_debug: 56 | setup_logging(logging.DEBUG) 57 | 58 | file_id = None 59 | if avatar_path: 60 | file = coze.files.upload(file=avatar_path) 61 | file_id = file.id 62 | print("create avatar file: ", avatar_path, file) 63 | 64 | bot = coze.bots.create( 65 | space_id=workspace_id, 66 | name="test", 67 | icon_file_id=file_id, 68 | suggest_reply_info=BotSuggestReplyInfo( 69 | reply_mode=SuggestReplyMode.CUSTOMIZED, customized_prompt="generate custom user question reply suggestion" 70 | ), 71 | ) 72 | print("create bot", bot.model_dump_json(indent=2)) 73 | print("logid", bot.response.logid) 74 | -------------------------------------------------------------------------------- /examples/bot_publish.py: -------------------------------------------------------------------------------- 1 | """ 2 | This example is for describing how to create a bot, update a bot and publish a bot to the API. 3 | """ 4 | 5 | import logging 6 | import os 7 | import sys 8 | from pathlib import Path 9 | from typing import Optional 10 | 11 | from cozepy import ( 12 | COZE_CN_BASE_URL, 13 | BotPromptInfo, 14 | ChatEventType, 15 | Coze, 16 | DeviceOAuthApp, 17 | Message, 18 | MessageContentType, 19 | TokenAuth, 20 | setup_logging, 21 | ) 22 | 23 | 24 | def get_coze_api_base() -> str: 25 | # The default access is api.coze.cn, but if you need to access api.coze.com, 26 | # please use base_url to configure the api endpoint to access 27 | coze_api_base = os.getenv("COZE_API_BASE") 28 | if coze_api_base: 29 | return coze_api_base 30 | 31 | return COZE_CN_BASE_URL # default 32 | 33 | 34 | def get_coze_api_token(workspace_id: Optional[str] = None) -> str: 35 | # Get an access_token through personal access token or oauth. 36 | coze_api_token = os.getenv("COZE_API_TOKEN") 37 | if coze_api_token: 38 | return coze_api_token 39 | 40 | coze_api_base = get_coze_api_base() 41 | 42 | device_oauth_app = DeviceOAuthApp(client_id="57294420732781205987760324720643.app.coze", base_url=coze_api_base) 43 | device_code = device_oauth_app.get_device_code(workspace_id) 44 | print(f"Please Open: {device_code.verification_url} to get the access token") 45 | return device_oauth_app.get_access_token(device_code=device_code.device_code, poll=True).access_token 46 | 47 | 48 | # Init the Coze client through the access_token. 49 | coze = Coze(auth=TokenAuth(token=get_coze_api_token()), base_url=get_coze_api_base()) 50 | # workspace id 51 | workspace_id = os.getenv("COZE_WORKSPACE_ID") or "your workspace id" 52 | # Whether to print detailed logs 53 | is_debug = os.getenv("DEBUG") 54 | 55 | if is_debug: 56 | setup_logging(logging.DEBUG) 57 | 58 | 59 | # Call the upload file interface to get the avatar id. 60 | avatar_path = "/path/avatar.jpg" if len(sys.argv) < 2 else sys.argv[1] 61 | avatar = coze.files.upload(file=Path(avatar_path)) 62 | 63 | # Invoke the create interface to create a bot in the draft status. 64 | bot = coze.bots.create( 65 | # The bot should exist under a space and your space id needs configuration. 66 | space_id=workspace_id, 67 | # Bot name 68 | name="translator bot", 69 | # Bot avatar 70 | icon_file_id=avatar.id, 71 | # Bot system prompt 72 | prompt_info=BotPromptInfo(prompt="your are a translator, translate the following text from English to Chinese"), 73 | ) 74 | 75 | # Call the publish api to publish the bot on the api channel. 76 | coze.bots.publish(bot_id=bot.bot_id) 77 | 78 | # Developers can also modify the bot configuration and republish it. 79 | coze.bots.update( 80 | bot_id=bot.bot_id, 81 | name="translator bot 2.0", 82 | prompt_info=BotPromptInfo(prompt="your are a translator, translate the following text from Chinese to English"), 83 | ) 84 | coze.bots.publish(bot_id=bot.bot_id, connector_ids=["1024"]) 85 | 86 | # Call the coze.chat.stream method to create a chat. The create method is a streaming 87 | # chat and will return a Chat Iterator. Developers should iterate the iterator to get 88 | # chat event and handle them. 89 | for event in coze.chat.stream( 90 | # The published bot's id 91 | bot_id=bot.bot_id, 92 | # biz user id, maybe random string 93 | user_id="user id", 94 | # user input 95 | additional_messages=[Message.build_user_question_text("chinese")], 96 | ): 97 | if ( 98 | event.event == ChatEventType.CONVERSATION_MESSAGE_DELTA 99 | and event.message.content_type == MessageContentType.TEXT 100 | ): 101 | print(event.message.content, end="") 102 | -------------------------------------------------------------------------------- /examples/bot_retrieve.py: -------------------------------------------------------------------------------- 1 | """ 2 | This example is for describing how to retrieve a bot. 3 | """ 4 | 5 | import logging 6 | import os 7 | from typing import Optional 8 | 9 | from cozepy import ( 10 | COZE_CN_BASE_URL, 11 | Coze, 12 | DeviceOAuthApp, 13 | TokenAuth, 14 | setup_logging, 15 | ) 16 | 17 | 18 | def get_coze_api_base() -> str: 19 | # The default access is api.coze.cn, but if you need to access api.coze.com, 20 | # please use base_url to configure the api endpoint to access 21 | coze_api_base = os.getenv("COZE_API_BASE") 22 | if coze_api_base: 23 | return coze_api_base 24 | 25 | return COZE_CN_BASE_URL # default 26 | 27 | 28 | def get_coze_api_token(workspace_id: Optional[str] = None) -> str: 29 | # Get an access_token through personal access token or oauth. 30 | coze_api_token = os.getenv("COZE_API_TOKEN") 31 | if coze_api_token: 32 | return coze_api_token 33 | 34 | coze_api_base = get_coze_api_base() 35 | 36 | device_oauth_app = DeviceOAuthApp(client_id="57294420732781205987760324720643.app.coze", base_url=coze_api_base) 37 | device_code = device_oauth_app.get_device_code(workspace_id) 38 | print(f"Please Open: {device_code.verification_url} to get the access token") 39 | return device_oauth_app.get_access_token(device_code=device_code.device_code, poll=True).access_token 40 | 41 | 42 | # Init the Coze client through the access_token. 43 | coze = Coze(auth=TokenAuth(token=get_coze_api_token()), base_url=get_coze_api_base()) 44 | # workspace id 45 | workspace_id = os.getenv("COZE_WORKSPACE_ID") or "your workspace id" 46 | # bot id 47 | bot_id = os.getenv("COZE_BOT_ID") or "your bot id" 48 | # Whether to print detailed logs 49 | is_debug = os.getenv("DEBUG") 50 | 51 | if is_debug: 52 | setup_logging(logging.DEBUG) 53 | 54 | bot = coze.bots.retrieve(bot_id=bot_id) 55 | print("retrieve bot", bot.model_dump_json(indent=2)) 56 | print("logid", bot.response.logid) 57 | -------------------------------------------------------------------------------- /examples/bot_update.py: -------------------------------------------------------------------------------- 1 | """ 2 | This example is for describing how to update a bot. 3 | """ 4 | 5 | import logging 6 | import os 7 | from typing import Optional 8 | 9 | from cozepy import ( 10 | COZE_CN_BASE_URL, 11 | BotSuggestReplyInfo, 12 | Coze, 13 | DeviceOAuthApp, 14 | SuggestReplyMode, 15 | TokenAuth, 16 | setup_logging, 17 | ) 18 | 19 | 20 | def get_coze_api_base() -> str: 21 | # The default access is api.coze.cn, but if you need to access api.coze.com, 22 | # please use base_url to configure the api endpoint to access 23 | coze_api_base = os.getenv("COZE_API_BASE") 24 | if coze_api_base: 25 | return coze_api_base 26 | 27 | return COZE_CN_BASE_URL # default 28 | 29 | 30 | def get_coze_api_token(workspace_id: Optional[str] = None) -> str: 31 | # Get an access_token through personal access token or oauth. 32 | coze_api_token = os.getenv("COZE_API_TOKEN") 33 | if coze_api_token: 34 | return coze_api_token 35 | 36 | coze_api_base = get_coze_api_base() 37 | 38 | device_oauth_app = DeviceOAuthApp(client_id="57294420732781205987760324720643.app.coze", base_url=coze_api_base) 39 | device_code = device_oauth_app.get_device_code(workspace_id) 40 | print(f"Please Open: {device_code.verification_url} to get the access token") 41 | return device_oauth_app.get_access_token(device_code=device_code.device_code, poll=True).access_token 42 | 43 | 44 | # Init the Coze client through the access_token. 45 | coze = Coze(auth=TokenAuth(token=get_coze_api_token()), base_url=get_coze_api_base()) 46 | # workspace id 47 | workspace_id = os.getenv("COZE_WORKSPACE_ID") or "your workspace id" 48 | # bot id 49 | bot_id = os.getenv("COZE_BOT_ID") or "your bot id" 50 | # Whether to print detailed logs 51 | is_debug = os.getenv("DEBUG") 52 | 53 | if is_debug: 54 | setup_logging(logging.DEBUG) 55 | 56 | bot_update = coze.bots.update( 57 | bot_id=bot_id, 58 | suggest_reply_info=BotSuggestReplyInfo( 59 | reply_mode=SuggestReplyMode.ENABLE, customized_prompt="generate suggest reply" 60 | ), 61 | ) 62 | print("update logid", bot_update.response.logid) 63 | -------------------------------------------------------------------------------- /examples/chat_conversation_stream.py: -------------------------------------------------------------------------------- 1 | """ 2 | This example is about how to use conversation to pass context 3 | """ 4 | 5 | import os 6 | from typing import Callable, Optional 7 | 8 | from cozepy import COZE_CN_BASE_URL, ChatEventType, Coze, DeviceOAuthApp, Message, TokenAuth 9 | 10 | 11 | def build_translate_chinese_to_english_context(coze: Coze, bot_id: str) -> Callable[[str], str]: 12 | conversation = coze.conversations.create( 13 | messages=[ 14 | Message.build_user_question_text("You need to translate the Chinese to English."), 15 | Message.build_assistant_answer("OK."), 16 | ] 17 | ) 18 | 19 | # Call chat.stream and parse the return value to get the translation result. 20 | def translate_on_translate_chinese_to_english_context(text: str) -> str: 21 | for event in coze.chat.stream( 22 | bot_id=bot_id, 23 | user_id="fake user id", 24 | additional_messages=[ 25 | Message.build_user_question_text(text), 26 | ], 27 | conversation_id=conversation.id, 28 | ): 29 | if event.event == ChatEventType.CONVERSATION_MESSAGE_COMPLETED: 30 | return event.message.content 31 | 32 | return translate_on_translate_chinese_to_english_context 33 | 34 | 35 | def get_coze_api_base() -> str: 36 | # The default access is api.coze.cn, but if you need to access api.coze.com, 37 | # please use base_url to configure the api endpoint to access 38 | coze_api_base = os.getenv("COZE_API_BASE") 39 | if coze_api_base: 40 | return coze_api_base 41 | 42 | return COZE_CN_BASE_URL # default 43 | 44 | 45 | def get_coze_api_token(workspace_id: Optional[str] = None) -> str: 46 | # Get an access_token through personal access token or oauth. 47 | coze_api_token = os.getenv("COZE_API_TOKEN") 48 | if coze_api_token: 49 | return coze_api_token 50 | 51 | coze_api_base = get_coze_api_base() 52 | 53 | device_oauth_app = DeviceOAuthApp(client_id="57294420732781205987760324720643.app.coze", base_url=coze_api_base) 54 | device_code = device_oauth_app.get_device_code(workspace_id) 55 | print(f"Please Open: {device_code.verification_url} to get the access token") 56 | return device_oauth_app.get_access_token(device_code=device_code.device_code, poll=True).access_token 57 | 58 | 59 | # Init the Coze client through the access_token. 60 | coze = Coze(auth=TokenAuth(token=get_coze_api_token()), base_url=get_coze_api_base()) 61 | # Create a bot instance in Coze, copy the last number from the web link as the bot's ID. 62 | bot_id = os.getenv("COZE_BOT_ID") or "bot id" 63 | 64 | translate_func = build_translate_chinese_to_english_context(coze, bot_id) 65 | 66 | print("translate:", translate_func("地球与宇宙")) 67 | -------------------------------------------------------------------------------- /examples/chat_download_file.py: -------------------------------------------------------------------------------- 1 | """ 2 | This example is about how to use the streaming interface to start a chat request 3 | and handle chat events 4 | """ 5 | 6 | import logging 7 | import os 8 | from typing import Optional 9 | 10 | from cozepy import COZE_CN_BASE_URL, ChatEventType, Coze, DeviceOAuthApp, Message, TokenAuth, setup_logging # noqa 11 | 12 | 13 | def get_coze_api_base() -> str: 14 | # The default access is api.coze.cn, but if you need to access api.coze.com, 15 | # please use base_url to configure the api endpoint to access 16 | coze_api_base = os.getenv("COZE_API_BASE") 17 | if coze_api_base: 18 | return coze_api_base 19 | 20 | return COZE_CN_BASE_URL # default 21 | 22 | 23 | def get_coze_api_token(workspace_id: Optional[str] = None) -> str: 24 | # Get an access_token through personal access token or oauth. 25 | coze_api_token = os.getenv("COZE_API_TOKEN") 26 | if coze_api_token: 27 | return coze_api_token 28 | 29 | coze_api_base = get_coze_api_base() 30 | 31 | device_oauth_app = DeviceOAuthApp(client_id="57294420732781205987760324720643.app.coze", base_url=coze_api_base) 32 | device_code = device_oauth_app.get_device_code(workspace_id) 33 | print(f"Please Open: {device_code.verification_url} to get the access token") 34 | return device_oauth_app.get_access_token(device_code=device_code.device_code, poll=True).access_token 35 | 36 | 37 | # Init the Coze client through the access_token. 38 | coze = Coze(auth=TokenAuth(token=get_coze_api_token()), base_url=get_coze_api_base()) 39 | 40 | # Create a bot instance in Coze, copy the last number from the web link as the bot's ID. 41 | bot_id = os.getenv("COZE_BOT_ID") or "bot id" 42 | # The user id identifies the identity of a user. Developers can use a custom business ID 43 | # or a random string. 44 | user_id = "user id" 45 | # Whether to print detailed logs 46 | is_debug = os.getenv("DEBUG") 47 | 48 | if is_debug: 49 | setup_logging(logging.DEBUG) 50 | 51 | 52 | def save_file(url: str): 53 | print("saving", url) 54 | # file to md5 filename, support https:///example.com/1.png?signature=1234567890 55 | file_name = os.path.basename(url.split("?")[0]) 56 | file_path = os.path.join("./", file_name) 57 | # download file 58 | import requests 59 | 60 | r = requests.get(url) 61 | with open(file_path, "wb") as f: 62 | f.write(r.content) 63 | print("saved to", file_path) 64 | return file_path 65 | 66 | 67 | for event in coze.chat.stream( 68 | bot_id=bot_id, 69 | user_id=user_id, 70 | additional_messages=[ 71 | Message.build_user_question_text("hi"), 72 | ], 73 | ): 74 | if event.event == ChatEventType.CONVERSATION_MESSAGE_DELTA: 75 | if event.message.content.startswith("http"): 76 | save_file(event.message.content) 77 | -------------------------------------------------------------------------------- /examples/chat_local_plugin.py: -------------------------------------------------------------------------------- 1 | """ 2 | This use case teaches you how to use local plugin. 3 | """ 4 | 5 | import json 6 | import os 7 | from typing import List, Optional 8 | 9 | from cozepy import ( # noqa 10 | COZE_CN_BASE_URL, 11 | ChatEvent, 12 | ChatEventType, 13 | ChatStatus, 14 | Coze, 15 | DeviceOAuthApp, 16 | Message, 17 | MessageContentType, 18 | Stream, 19 | TokenAuth, 20 | ToolOutput, 21 | ) 22 | 23 | 24 | def get_coze_api_base() -> str: 25 | # The default access is api.coze.cn, but if you need to access api.coze.com, 26 | # please use base_url to configure the api endpoint to access 27 | coze_api_base = os.getenv("COZE_API_BASE") 28 | if coze_api_base: 29 | return coze_api_base 30 | 31 | return COZE_CN_BASE_URL # default 32 | 33 | 34 | def get_coze_api_token(workspace_id: Optional[str] = None) -> str: 35 | # Get an access_token through personal access token or oauth. 36 | coze_api_token = os.getenv("COZE_API_TOKEN") 37 | if coze_api_token: 38 | return coze_api_token 39 | 40 | coze_api_base = get_coze_api_base() 41 | 42 | device_oauth_app = DeviceOAuthApp(client_id="57294420732781205987760324720643.app.coze", base_url=coze_api_base) 43 | device_code = device_oauth_app.get_device_code(workspace_id) 44 | print(f"Please Open: {device_code.verification_url} to get the access token") 45 | return device_oauth_app.get_access_token(device_code=device_code.device_code, poll=True).access_token 46 | 47 | 48 | # Init the Coze client through the access_token. 49 | coze = Coze(auth=TokenAuth(token=get_coze_api_token()), base_url=get_coze_api_base()) 50 | # Create a bot instance in Coze, copy the last number from the web link as the bot's ID. 51 | bot_id = os.environ.get("BOT_ID") 52 | # The user id identifies the identity of a user. Developers can use a custom business ID 53 | # or a random string. 54 | user_id = "user id" 55 | 56 | 57 | # These two functions are mock implementations. 58 | class LocalPluginMocker(object): 59 | @staticmethod 60 | def get_schedule(): 61 | return "I have two interviews in the afternoon." 62 | 63 | @staticmethod 64 | def screenshot(): 65 | return "The background of my screen is a little dog running on the beach." 66 | 67 | @staticmethod 68 | def get_function(name: str): 69 | return { 70 | "get_schedule": LocalPluginMocker.get_schedule, 71 | "screenshot": LocalPluginMocker.screenshot, 72 | }[name] 73 | 74 | 75 | # `handle_stream` is used to handle events. When the `CONVERSATION_CHAT_REQUIRES_ACTION` event is received, 76 | # the `submit_tool_outputs` method needs to be called to submit the running result. 77 | def handle_stream(stream: Stream[ChatEvent]): 78 | for event in stream: 79 | if event.event == ChatEventType.CONVERSATION_MESSAGE_DELTA: 80 | print(event.message.content, end="", flush=True) 81 | 82 | if event.event == ChatEventType.CONVERSATION_CHAT_REQUIRES_ACTION: 83 | if not event.chat.required_action or not event.chat.required_action.submit_tool_outputs: 84 | continue 85 | tool_calls = event.chat.required_action.submit_tool_outputs.tool_calls 86 | tool_outputs: List[ToolOutput] = [] 87 | for tool_call in tool_calls: 88 | print(f"function call: {tool_call.function.name} {tool_call.function.arguments}") 89 | local_function = LocalPluginMocker.get_function(tool_call.function.name) 90 | output = json.dumps({"output": local_function()}) 91 | tool_outputs.append(ToolOutput(tool_call_id=tool_call.id, output=output)) 92 | 93 | handle_stream( 94 | coze.chat.submit_tool_outputs( 95 | conversation_id=event.chat.conversation_id, 96 | chat_id=event.chat.id, 97 | tool_outputs=tool_outputs, 98 | stream=True, 99 | ) 100 | ) 101 | 102 | if event.event == ChatEventType.CONVERSATION_CHAT_COMPLETED: 103 | print() 104 | print("token usage:", event.chat.usage.token_count) 105 | 106 | 107 | # The intelligent entity will call LocalPluginMocker.get_schedule to obtain the schedule. 108 | # get_schedule is just a mock method. 109 | handle_stream( 110 | coze.chat.stream( 111 | bot_id=bot_id, 112 | user_id=user_id, 113 | additional_messages=[ 114 | Message.build_user_question_text("What do I have to do in the afternoon?"), 115 | ], 116 | ) 117 | ) 118 | 119 | # The intelligent entity will obtain a screenshot through LocalPluginMocker.screenshot. 120 | handle_stream( 121 | coze.chat.stream( 122 | bot_id=bot_id, 123 | user_id=user_id, 124 | additional_messages=[ 125 | Message.build_user_question_text("What's on my screen?"), 126 | ], 127 | ) 128 | ) 129 | -------------------------------------------------------------------------------- /examples/chat_multimode_stream.py: -------------------------------------------------------------------------------- 1 | """ 2 | This example is about how to use the streaming interface to start a chat request 3 | with image upload and handle chat events 4 | """ 5 | 6 | import os 7 | import sys 8 | from pathlib import Path 9 | from typing import Optional 10 | 11 | from cozepy import ( # noqa 12 | COZE_CN_BASE_URL, 13 | ChatEventType, 14 | Coze, 15 | DeviceOAuthApp, 16 | Message, 17 | MessageObjectString, 18 | TokenAuth, 19 | ) 20 | 21 | 22 | def get_coze_api_base() -> str: 23 | # The default access is api.coze.cn, but if you need to access api.coze.com, 24 | # please use base_url to configure the api endpoint to access 25 | coze_api_base = os.getenv("COZE_API_BASE") 26 | if coze_api_base: 27 | return coze_api_base 28 | 29 | return COZE_CN_BASE_URL # default 30 | 31 | 32 | def get_coze_api_token(workspace_id: Optional[str] = None) -> str: 33 | # Get an access_token through personal access token or oauth. 34 | coze_api_token = os.getenv("COZE_API_TOKEN") 35 | if coze_api_token: 36 | return coze_api_token 37 | 38 | coze_api_base = get_coze_api_base() 39 | 40 | device_oauth_app = DeviceOAuthApp(client_id="57294420732781205987760324720643.app.coze", base_url=coze_api_base) 41 | device_code = device_oauth_app.get_device_code(workspace_id) 42 | print(f"Please Open: {device_code.verification_url} to get the access token") 43 | return device_oauth_app.get_access_token(device_code=device_code.device_code, poll=True).access_token 44 | 45 | 46 | # Init the Coze client through the access_token. 47 | coze = Coze(auth=TokenAuth(token=get_coze_api_token()), base_url=get_coze_api_base()) 48 | # Create a bot instance in Coze, copy the last number from the web link as the bot's ID. 49 | bot_id = os.getenv("COZE_BOT_ID") 50 | # The user id identifies the identity of a user. Developers can use a custom business ID 51 | # or a random string. 52 | user_id = "user id" 53 | 54 | 55 | # Call the upload interface to upload a picture requiring text recognition, and 56 | # obtain the file_id of the picture. 57 | file_path = sys.argv[1] if len(sys.argv) > 1 else "/path/image.jpg" 58 | file = coze.files.upload(file=Path(file_path)) 59 | 60 | # Call the coze.chat.stream method to create a chat. The create method is a streaming 61 | # chat and will return a Chat Iterator. Developers should iterate the iterator to get 62 | # chat event and handle them. 63 | for event in coze.chat.stream( 64 | bot_id=bot_id, 65 | user_id=user_id, 66 | additional_messages=[ 67 | Message.build_user_question_objects( 68 | [ 69 | MessageObjectString.build_text("What text is on the picture?"), 70 | MessageObjectString.build_image(file_id=file.id), 71 | ] 72 | ), 73 | ], 74 | ): 75 | if event.event == ChatEventType.CONVERSATION_MESSAGE_DELTA: 76 | message = event.message 77 | print(event.message.content, end="", flush=True) 78 | -------------------------------------------------------------------------------- /examples/chat_no_stream.py: -------------------------------------------------------------------------------- 1 | """ 2 | This example describes how to use the chat interface to initiate conversations, 3 | poll the status of the conversation, and obtain the messages after the conversation is completed. 4 | """ 5 | 6 | import logging 7 | import os 8 | import time 9 | from typing import Optional 10 | 11 | from cozepy import COZE_CN_BASE_URL, ChatStatus, Coze, DeviceOAuthApp, Message, TokenAuth 12 | from cozepy.log import setup_logging 13 | 14 | 15 | def get_coze_api_base() -> str: 16 | # The default access is api.coze.cn, but if you need to access api.coze.com, 17 | # please use base_url to configure the api endpoint to access 18 | coze_api_base = os.getenv("COZE_API_BASE") 19 | if coze_api_base: 20 | return coze_api_base 21 | 22 | return COZE_CN_BASE_URL # default 23 | 24 | 25 | def get_coze_api_token(workspace_id: Optional[str] = None) -> str: 26 | # Get an access_token through personal access token or oauth. 27 | coze_api_token = os.getenv("COZE_API_TOKEN") 28 | if coze_api_token: 29 | return coze_api_token 30 | 31 | coze_api_base = get_coze_api_base() 32 | 33 | device_oauth_app = DeviceOAuthApp(client_id="57294420732781205987760324720643.app.coze", base_url=coze_api_base) 34 | device_code = device_oauth_app.get_device_code(workspace_id) 35 | print(f"Please Open: {device_code.verification_url} to get the access token") 36 | return device_oauth_app.get_access_token(device_code=device_code.device_code, poll=True).access_token 37 | 38 | 39 | # Init the Coze client through the access_token. 40 | coze = Coze(auth=TokenAuth(token=get_coze_api_token()), base_url=get_coze_api_base()) 41 | # Create a bot instance in Coze, copy the last number from the web link as the bot's ID. 42 | bot_id = os.getenv("COZE_BOT_ID") or "bot id" 43 | # The user id identifies the identity of a user. Developers can use a custom business ID 44 | # or a random string. 45 | user_id = "user id" 46 | # Whether to print detailed logs 47 | is_debug = os.getenv("DEBUG") 48 | 49 | if is_debug: 50 | setup_logging(logging.DEBUG) 51 | 52 | if os.getenv("RUN_STEP_BY_STEP"): 53 | # Call the coze.chat.create method to create a chat. The create method is a non-streaming 54 | # chat and will return a Chat class. Developers should periodically check the status of the 55 | # chat and handle them separately according to different states. 56 | chat = coze.chat.create( 57 | bot_id=bot_id, 58 | user_id=user_id, 59 | additional_messages=[ 60 | Message.build_user_question_text("Who are you?"), 61 | Message.build_assistant_answer("I am Bot by Coze."), 62 | Message.build_user_question_text("What about you?"), 63 | ], 64 | ) 65 | 66 | # Assume the development allows at most one chat to run for 10 minutes. If it exceeds 10 67 | # minutes, the chat will be cancelled. 68 | # And when the chat status is not completed, poll the status of the chat once every second. 69 | # After the chat is completed, retrieve all messages in the chat. 70 | start = int(time.time()) 71 | timeout = 600 72 | while chat.status == ChatStatus.IN_PROGRESS: 73 | if int(time.time()) - start > timeout: 74 | # too long, cancel chat 75 | coze.chat.cancel(conversation_id=chat.conversation_id, chat_id=chat.id) 76 | break 77 | 78 | time.sleep(1) 79 | # Fetch the latest data through the retrieve interface 80 | chat = coze.chat.retrieve(conversation_id=chat.conversation_id, chat_id=chat.id) 81 | 82 | # When the chat status becomes completed, all messages under this chat can be retrieved through the list messages interface. 83 | messages = coze.chat.messages.list(conversation_id=chat.conversation_id, chat_id=chat.id) 84 | for message in messages: 85 | print(f"role={message.role}, content={message.content}") 86 | else: 87 | # To simplify the call, the SDK provides a wrapped function to complete non-streaming chat, 88 | # polling, and obtaining the messages of the chat. Developers can use create_and_poll to 89 | # simplify the process. 90 | chat_poll = coze.chat.create_and_poll( 91 | bot_id=bot_id, 92 | user_id=user_id, 93 | additional_messages=[ 94 | Message.build_user_question_text("Who are you?"), 95 | Message.build_assistant_answer("I am Bot by Coze."), 96 | Message.build_user_question_text("What about you?"), 97 | ], 98 | ) 99 | for message in chat_poll.messages: 100 | print(message.content, end="", flush=True) 101 | 102 | if chat_poll.chat.status == ChatStatus.COMPLETED: 103 | print() 104 | print("token usage:", chat_poll.chat.usage.token_count) 105 | -------------------------------------------------------------------------------- /examples/chat_simple_audio.py: -------------------------------------------------------------------------------- 1 | """ 2 | This example is about how to chat with audio input and handle audio response. 3 | """ 4 | 5 | import base64 6 | import logging 7 | import os 8 | import random 9 | import sys 10 | from pathlib import Path 11 | from typing import Optional 12 | 13 | from cozepy import ( 14 | COZE_CN_BASE_URL, 15 | AudioFormat, 16 | ChatEventType, 17 | Coze, 18 | DeviceOAuthApp, 19 | Message, 20 | MessageObjectString, 21 | TokenAuth, 22 | setup_logging, 23 | ) 24 | from cozepy.util import write_pcm_to_wav_file 25 | 26 | 27 | def get_coze_api_base() -> str: 28 | # The default access is api.coze.cn, but if you need to access api.coze.com, 29 | # please use base_url to configure the api endpoint to access 30 | coze_api_base = os.getenv("COZE_API_BASE") 31 | if coze_api_base: 32 | return coze_api_base 33 | 34 | return COZE_CN_BASE_URL # default 35 | 36 | 37 | def get_coze_api_token(workspace_id: Optional[str] = None) -> str: 38 | # Get an access_token through personal access token or oauth. 39 | coze_api_token = os.getenv("COZE_API_TOKEN") 40 | if coze_api_token: 41 | return coze_api_token 42 | 43 | coze_api_base = get_coze_api_base() 44 | 45 | device_oauth_app = DeviceOAuthApp(client_id="57294420732781205987760324720643.app.coze", base_url=coze_api_base) 46 | device_code = device_oauth_app.get_device_code(workspace_id) 47 | print(f"Please Open: {device_code.verification_url} to get the access token") 48 | return device_oauth_app.get_access_token(device_code=device_code.device_code, poll=True).access_token 49 | 50 | 51 | # Init the Coze client through the access_token. 52 | coze = Coze(auth=TokenAuth(token=get_coze_api_token()), base_url=get_coze_api_base()) 53 | # Create a bot instance in Coze, copy the last number from the web link as the bot's ID. 54 | bot_id = os.getenv("COZE_BOT_ID") 55 | # The user id identifies the identity of a user. Developers can use a custom business ID 56 | # or a random string. 57 | user_id = "user id" 58 | # Whether to print detailed logs 59 | is_debug = os.getenv("DEBUG") 60 | 61 | if is_debug: 62 | setup_logging(logging.DEBUG) 63 | 64 | 65 | # Get and upload audio file 66 | def get_audio_filepath() -> str: 67 | if len(sys.argv) > 1: 68 | # Get it from the program running parameters: 69 | # python examples/chat_audio.py ~/Downloads/input.wav 70 | print(f"Get audio filepath from arg: {sys.argv[1]}") 71 | return sys.argv[1] 72 | 73 | voices = [i for i in coze.audio.voices.list()] 74 | voice = random.choice(voices) 75 | print(f"Get random voice: {voice.voice_id} {voice.name} from voices list of length: {len(voices)}") 76 | 77 | input_text = "你是基于什么大模型构建的" 78 | audio_path = os.path.join(os.path.expanduser("~"), "Downloads", f"coze_{voice.voice_id}_{input_text}.wav") 79 | 80 | audio = coze.audio.speech.create(input=input_text, voice_id=voice.voice_id, response_format=AudioFormat.WAV) 81 | 82 | audio.write_to_file(audio_path) 83 | 84 | print(f"Get audio filepath from speech api: {audio_path}") 85 | return audio_path 86 | 87 | 88 | audio_file_path = get_audio_filepath() 89 | audio_file = coze.files.upload(file=Path(audio_file_path)) 90 | 91 | 92 | # Call the coze.chat.stream api and save pcm audio data to wav file. 93 | pcm_datas = b"" 94 | for event in coze.chat.stream( 95 | bot_id=bot_id, 96 | user_id=user_id, 97 | additional_messages=[ 98 | Message.build_user_question_objects( 99 | [ 100 | MessageObjectString.build_audio(file_id=audio_file.id), 101 | ] 102 | ), 103 | ], 104 | ): 105 | if event.event == ChatEventType.CONVERSATION_MESSAGE_DELTA: 106 | message = event.message 107 | print(event.message.content, end="", flush=True) 108 | elif event.event == ChatEventType.CONVERSATION_AUDIO_DELTA: 109 | pcm_datas += base64.b64decode(event.message.content) 110 | 111 | wav_audio_path = os.path.join(os.path.expanduser("~"), "Downloads", "coze_response_audio.wav") 112 | write_pcm_to_wav_file(pcm_datas, wav_audio_path) 113 | print(f"\nGet audio response from chat stream, save to {wav_audio_path}") 114 | -------------------------------------------------------------------------------- /examples/chat_stream.py: -------------------------------------------------------------------------------- 1 | """ 2 | This example is about how to use the streaming interface to start a chat request 3 | and handle chat events 4 | """ 5 | 6 | import json 7 | import logging 8 | import os 9 | from typing import Optional 10 | 11 | from cozepy import COZE_CN_BASE_URL, ChatEventType, Coze, DeviceOAuthApp, Message, TokenAuth, setup_logging # noqa 12 | 13 | 14 | def get_coze_api_base() -> str: 15 | # The default access is api.coze.cn, but if you need to access api.coze.com, 16 | # please use base_url to configure the api endpoint to access 17 | coze_api_base = os.getenv("COZE_API_BASE") 18 | if coze_api_base: 19 | return coze_api_base 20 | 21 | return COZE_CN_BASE_URL # default 22 | 23 | 24 | def get_coze_api_token(workspace_id: Optional[str] = None) -> str: 25 | # Get an access_token through personal access token or oauth. 26 | coze_api_token = os.getenv("COZE_API_TOKEN") 27 | if coze_api_token: 28 | return coze_api_token 29 | 30 | coze_api_base = get_coze_api_base() 31 | 32 | device_oauth_app = DeviceOAuthApp(client_id="57294420732781205987760324720643.app.coze", base_url=coze_api_base) 33 | device_code = device_oauth_app.get_device_code(workspace_id) 34 | print(f"Please Open: {device_code.verification_url} to get the access token") 35 | return device_oauth_app.get_access_token(device_code=device_code.device_code, poll=True).access_token 36 | 37 | 38 | # Init the Coze client through the access_token. 39 | coze = Coze(auth=TokenAuth(token=get_coze_api_token()), base_url=get_coze_api_base()) 40 | 41 | # Create a bot instance in Coze, copy the last number from the web link as the bot's ID. 42 | bot_id = os.getenv("COZE_BOT_ID") or "bot id" 43 | # The user id identifies the identity of a user. Developers can use a custom business ID 44 | # or a random string. 45 | user_id = "user id" 46 | # Whether to print detailed logs 47 | is_debug = os.getenv("DEBUG") 48 | 49 | parameters = json.loads(os.getenv("COZE_PARAMETERS") or "{}") 50 | 51 | if is_debug: 52 | setup_logging(logging.DEBUG) 53 | 54 | # Call the coze.chat.stream method to create a chat. The create method is a streaming 55 | # chat and will return a Chat Iterator. Developers should iterate the iterator to get 56 | # chat event and handle them. 57 | is_first_reasoning_content = True 58 | is_first_content = True 59 | stream = coze.chat.stream( 60 | bot_id=bot_id, 61 | user_id=user_id, 62 | additional_messages=[ 63 | Message.build_user_question_text("Tell a 500-word story."), 64 | ], 65 | parameters=parameters, 66 | ) 67 | print("logid:", stream.response.logid) 68 | for event in stream: 69 | if event.event == ChatEventType.CONVERSATION_MESSAGE_DELTA: 70 | if event.message.reasoning_content: 71 | if is_first_reasoning_content: 72 | is_first_reasoning_content = not is_first_reasoning_content 73 | print("----- reasoning_content start -----\n> ", end="", flush=True) 74 | print(event.message.reasoning_content, end="", flush=True) 75 | else: 76 | if is_first_content and not is_first_reasoning_content: 77 | is_first_content = not is_first_content 78 | print("----- reasoning_content end -----") 79 | print(event.message.content, end="", flush=True) 80 | 81 | if event.event == ChatEventType.CONVERSATION_CHAT_COMPLETED: 82 | print() 83 | print("token usage:", event.chat.usage.token_count) 84 | 85 | if event.event == ChatEventType.CONVERSATION_CHAT_FAILED: 86 | print() 87 | print("chat failed", event.chat.last_error) 88 | break 89 | -------------------------------------------------------------------------------- /examples/conversation.py: -------------------------------------------------------------------------------- 1 | """ 2 | This example is about how to create conversation and retrieve. 3 | """ 4 | 5 | import logging 6 | import os 7 | from typing import Optional 8 | 9 | from cozepy import ( # noqa 10 | COZE_CN_BASE_URL, 11 | BotPromptInfo, 12 | ChatEventType, 13 | Coze, 14 | DeviceOAuthApp, 15 | Message, 16 | MessageContentType, 17 | MessageRole, 18 | TokenAuth, 19 | setup_logging, 20 | ) 21 | 22 | 23 | def get_coze_api_base() -> str: 24 | # The default access is api.coze.cn, but if you need to access api.coze.com, 25 | # please use base_url to configure the api endpoint to access 26 | coze_api_base = os.getenv("COZE_API_BASE") 27 | if coze_api_base: 28 | return coze_api_base 29 | 30 | return COZE_CN_BASE_URL # default 31 | 32 | 33 | def get_coze_api_token(workspace_id: Optional[str] = None) -> str: 34 | # Get an access_token through personal access token or oauth. 35 | coze_api_token = os.getenv("COZE_API_TOKEN") 36 | if coze_api_token: 37 | return coze_api_token 38 | 39 | coze_api_base = get_coze_api_base() 40 | 41 | device_oauth_app = DeviceOAuthApp(client_id="57294420732781205987760324720643.app.coze", base_url=coze_api_base) 42 | device_code = device_oauth_app.get_device_code(workspace_id) 43 | print(f"Please Open: {device_code.verification_url} to get the access token") 44 | return device_oauth_app.get_access_token(device_code=device_code.device_code, poll=True).access_token 45 | 46 | 47 | # Init the Coze client through the access_token. 48 | coze = Coze(auth=TokenAuth(token=get_coze_api_token()), base_url=get_coze_api_base()) 49 | # Whether to print detailed logs 50 | is_debug = os.getenv("DEBUG") 51 | 52 | if is_debug: 53 | setup_logging(logging.DEBUG) 54 | 55 | 56 | conversation = coze.conversations.create() 57 | print("Create conversation: ", conversation) 58 | 59 | conversation_retrieve = coze.conversations.retrieve(conversation_id=conversation.id) 60 | print("Retrieve conversation:", conversation_retrieve) 61 | 62 | message = coze.conversations.messages.create( 63 | conversation_id=conversation.id, 64 | role=MessageRole.USER, 65 | content="hi", 66 | content_type=MessageContentType.TEXT, 67 | ) 68 | 69 | print("Append message to conversation:", message) 70 | 71 | section = coze.conversations.clear(conversation_id=conversation.id) 72 | print("Clear conversation:", section) 73 | -------------------------------------------------------------------------------- /examples/conversation_list.py: -------------------------------------------------------------------------------- 1 | """ 2 | This example is about how to list conversation. 3 | """ 4 | 5 | import logging 6 | import os 7 | from typing import Optional 8 | 9 | from cozepy import ( # noqa 10 | COZE_CN_BASE_URL, 11 | BotPromptInfo, 12 | ChatEventType, 13 | Coze, 14 | DeviceOAuthApp, 15 | Message, 16 | MessageContentType, 17 | MessageRole, 18 | TokenAuth, 19 | setup_logging, 20 | ) 21 | 22 | 23 | def get_coze_api_base() -> str: 24 | # The default access is api.coze.cn, but if you need to access api.coze.com, 25 | # please use base_url to configure the api endpoint to access 26 | coze_api_base = os.getenv("COZE_API_BASE") 27 | if coze_api_base: 28 | return coze_api_base 29 | 30 | return COZE_CN_BASE_URL # default 31 | 32 | 33 | def get_coze_api_token(workspace_id: Optional[str] = None) -> str: 34 | # Get an access_token through personal access token or oauth. 35 | coze_api_token = os.getenv("COZE_API_TOKEN") 36 | if coze_api_token: 37 | return coze_api_token 38 | 39 | coze_api_base = get_coze_api_base() 40 | 41 | device_oauth_app = DeviceOAuthApp(client_id="57294420732781205987760324720643.app.coze", base_url=coze_api_base) 42 | device_code = device_oauth_app.get_device_code(workspace_id) 43 | print(f"Please Open: {device_code.verification_url} to get the access token") 44 | return device_oauth_app.get_access_token(device_code=device_code.device_code, poll=True).access_token 45 | 46 | 47 | # Init the Coze client through the access_token. 48 | coze = Coze(auth=TokenAuth(token=get_coze_api_token()), base_url=get_coze_api_base()) 49 | # coze bot id 50 | coze_bot_id = os.getenv("COZE_BOT_ID") or "input your bot id" 51 | # Whether to print detailed logs 52 | is_debug = os.getenv("DEBUG") 53 | 54 | if is_debug: 55 | setup_logging(logging.DEBUG) 56 | 57 | 58 | conversations = coze.conversations.list(bot_id=coze_bot_id, page_size=2) 59 | 60 | for conversation in conversations: 61 | print("conversation[id]", conversation.id) 62 | print("conversation[created_at]", conversation.created_at) 63 | print("conversation[last_section_id]", conversation.last_section_id) 64 | print("") 65 | 66 | for page in conversations.iter_pages(): 67 | print("items", page.items) 68 | -------------------------------------------------------------------------------- /examples/dataset_create.py: -------------------------------------------------------------------------------- 1 | """ 2 | This example is about how to create a dataset. 3 | """ 4 | 5 | import logging 6 | import os 7 | from typing import Optional 8 | 9 | from cozepy import ( # noqa 10 | COZE_CN_BASE_URL, 11 | Coze, 12 | CreateDatasetResp, 13 | DeviceOAuthApp, 14 | DocumentFormatType, 15 | TokenAuth, 16 | setup_logging, 17 | ) 18 | 19 | 20 | def get_coze_api_base() -> str: 21 | # The default access is api.coze.cn, but if you need to access api.coze.com, 22 | # please use base_url to configure the api endpoint to access 23 | coze_api_base = os.getenv("COZE_API_BASE") 24 | if coze_api_base: 25 | return coze_api_base 26 | 27 | return COZE_CN_BASE_URL # default 28 | 29 | 30 | def get_coze_api_token(workspace_id: Optional[str] = None) -> str: 31 | # Get an access_token through personal access token or oauth. 32 | coze_api_token = os.getenv("COZE_API_TOKEN") 33 | if coze_api_token: 34 | return coze_api_token 35 | 36 | coze_api_base = get_coze_api_base() 37 | 38 | device_oauth_app = DeviceOAuthApp(client_id="57294420732781205987760324720643.app.coze", base_url=coze_api_base) 39 | device_code = device_oauth_app.get_device_code(workspace_id) 40 | print(f"Please Open: {device_code.verification_url} to get the access token") 41 | return device_oauth_app.get_access_token(device_code=device_code.device_code, poll=True).access_token 42 | 43 | 44 | # Init the Coze client through the access_token. 45 | coze = Coze(auth=TokenAuth(token=get_coze_api_token()), base_url=get_coze_api_base()) 46 | # workspace id 47 | workspace_id = os.getenv("COZE_WORKSPACE_ID") or "your workspace id" 48 | # Whether to print detailed logs 49 | is_debug = os.getenv("DEBUG") 50 | 51 | if is_debug: 52 | setup_logging(logging.DEBUG) 53 | 54 | 55 | res = coze.datasets.create( 56 | name="test", 57 | space_id=workspace_id, 58 | format_type=DocumentFormatType.DOCUMENT, 59 | ) 60 | print(f"Create dataset: {res.dataset_id}") 61 | -------------------------------------------------------------------------------- /examples/exception.py: -------------------------------------------------------------------------------- 1 | """ 2 | This example is for illustrating how to handle the abnormal errors in the process of using the sdk. 3 | """ 4 | 5 | import os 6 | from typing import Optional 7 | 8 | from cozepy import ( 9 | COZE_CN_BASE_URL, 10 | Coze, 11 | CozeAPIError, 12 | DeviceOAuthApp, 13 | TokenAuth, 14 | ) 15 | 16 | 17 | def get_coze_api_base() -> str: 18 | # The default access is api.coze.cn, but if you need to access api.coze.com, 19 | # please use base_url to configure the api endpoint to access 20 | coze_api_base = os.getenv("COZE_API_BASE") 21 | if coze_api_base: 22 | return coze_api_base 23 | 24 | return COZE_CN_BASE_URL # default 25 | 26 | 27 | def get_coze_api_token(workspace_id: Optional[str] = None) -> str: 28 | # Get an access_token through personal access token or oauth. 29 | coze_api_token = os.getenv("COZE_API_TOKEN") 30 | if coze_api_token: 31 | return coze_api_token 32 | 33 | coze_api_base = get_coze_api_base() 34 | 35 | device_oauth_app = DeviceOAuthApp(client_id="57294420732781205987760324720643.app.coze", base_url=coze_api_base) 36 | device_code = device_oauth_app.get_device_code(workspace_id) 37 | print(f"Please Open: {device_code.verification_url} to get the access token") 38 | return device_oauth_app.get_access_token(device_code=device_code.device_code, poll=True).access_token 39 | 40 | 41 | # Init the Coze client through the access_token. 42 | coze = Coze(auth=TokenAuth(token=get_coze_api_token()), base_url=get_coze_api_base()) 43 | 44 | # Invoke the create interface to create a bot in the draft status. 45 | try: 46 | bot = coze.bots.create( 47 | # The bot should exist under a space and your space id needs configuration. 48 | space_id="workspace id", 49 | # Bot name 50 | name="translator bot", 51 | ) 52 | except CozeAPIError as e: 53 | print(e.code, e.msg, e.logid) 54 | raise # for example, re-raise the error 55 | -------------------------------------------------------------------------------- /examples/files_upload.py: -------------------------------------------------------------------------------- 1 | """ 2 | This example describes how to upload files. 3 | """ 4 | 5 | import os 6 | import sys 7 | from typing import Optional 8 | 9 | from cozepy import COZE_CN_BASE_URL, Coze, DeviceOAuthApp, Stream, TokenAuth, WorkflowEvent, WorkflowEventType # noqa 10 | 11 | 12 | def get_coze_api_base() -> str: 13 | # The default access is api.coze.cn, but if you need to access api.coze.com, 14 | # please use base_url to configure the api endpoint to access 15 | coze_api_base = os.getenv("COZE_API_BASE") 16 | if coze_api_base: 17 | return coze_api_base 18 | 19 | return COZE_CN_BASE_URL # default 20 | 21 | 22 | def get_coze_api_token(workspace_id: Optional[str] = None) -> str: 23 | # Get an access_token through personal access token or oauth. 24 | coze_api_token = os.getenv("COZE_API_TOKEN") 25 | if coze_api_token: 26 | return coze_api_token 27 | 28 | coze_api_base = get_coze_api_base() 29 | 30 | device_oauth_app = DeviceOAuthApp(client_id="57294420732781205987760324720643.app.coze", base_url=coze_api_base) 31 | device_code = device_oauth_app.get_device_code(workspace_id) 32 | print(f"Please Open: {device_code.verification_url} to get the access token") 33 | return device_oauth_app.get_access_token(device_code=device_code.device_code, poll=True).access_token 34 | 35 | 36 | # Init the Coze client through the access_token. 37 | coze = Coze(auth=TokenAuth(token=get_coze_api_token()), base_url=get_coze_api_base()) 38 | 39 | file_path = sys.argv[1] if len(sys.argv) > 1 else "/path/image.jpg" 40 | 41 | file = coze.files.upload(file=file_path) 42 | print(f"uploaded file: {file.id}\n {file}") 43 | -------------------------------------------------------------------------------- /examples/log.py: -------------------------------------------------------------------------------- 1 | """ 2 | This example is for the configuration of logs. 3 | """ 4 | 5 | import logging 6 | import os 7 | 8 | from cozepy import setup_logging 9 | 10 | # open debug logging, default is warning 11 | setup_logging(level=logging.DEBUG) 12 | 13 | # Get an access_token through personal access token or oauth. 14 | coze_api_token = os.getenv("COZE_API_TOKEN") 15 | 16 | from cozepy import Coze, TokenAuth, BotPromptInfo, Message, ChatEventType, MessageContentType # noqa 17 | 18 | # Init the Coze client through the access_token. 19 | coze = Coze(auth=TokenAuth(token=coze_api_token or "mock")) 20 | 21 | # Invoke the create interface to create a bot in the draft status. 22 | bot = coze.bots.create( 23 | # The bot should exist under a space and your workspace id needs configuration. 24 | space_id="workspace id", 25 | # Bot name 26 | name="translator bot", 27 | ) 28 | -------------------------------------------------------------------------------- /examples/template_duplicate.py: -------------------------------------------------------------------------------- 1 | """ 2 | This example demonstrates how to duplicate a template. 3 | """ 4 | 5 | import os 6 | from typing import Optional 7 | 8 | from cozepy import Coze 9 | from cozepy.auth import DeviceOAuthApp, TokenAuth 10 | from cozepy.config import COZE_CN_BASE_URL 11 | 12 | 13 | def get_coze_api_base() -> str: 14 | # The default access is api.coze.cn, but if you need to access api.coze.com, 15 | # please use base_url to configure the api endpoint to access 16 | coze_api_base = os.getenv("COZE_API_BASE") 17 | if coze_api_base: 18 | return coze_api_base 19 | 20 | return COZE_CN_BASE_URL # default 21 | 22 | 23 | def get_coze_api_token(workspace_id: Optional[str] = None) -> str: 24 | # Get an access_token through personal access token or oauth. 25 | coze_api_token = os.getenv("COZE_API_TOKEN") 26 | if coze_api_token: 27 | return coze_api_token 28 | 29 | coze_api_base = get_coze_api_base() 30 | 31 | device_oauth_app = DeviceOAuthApp(client_id="57294420732781205987760324720643.app.coze", base_url=coze_api_base) 32 | device_code = device_oauth_app.get_device_code(workspace_id) 33 | print(f"Please Open: {device_code.verification_url} to get the access token") 34 | return device_oauth_app.get_access_token(device_code=device_code.device_code, poll=True).access_token 35 | 36 | 37 | # Init the Coze client through the access_token. 38 | coze = Coze(auth=TokenAuth(token=get_coze_api_token()), base_url=get_coze_api_base()) 39 | # workspace id 40 | workspace_id = os.getenv("COZE_WORKSPACE_ID") 41 | # template id 42 | template_id = os.getenv("COZE_TEMPLATE_ID") 43 | 44 | 45 | res = coze.templates.duplicate(template_id=template_id, workspace_id=workspace_id) 46 | print("entity_id:", res.entity_id) 47 | print("entity_type:", res.entity_type) 48 | -------------------------------------------------------------------------------- /examples/timeout.py: -------------------------------------------------------------------------------- 1 | """ 2 | How to configure timeout 3 | """ 4 | 5 | import os 6 | from typing import Optional 7 | 8 | import httpx 9 | 10 | from cozepy import ( # noqa 11 | COZE_CN_BASE_URL, 12 | AsyncCoze, 13 | AsyncHTTPClient, 14 | ChatEventType, 15 | ChatStatus, 16 | Coze, 17 | DeviceOAuthApp, 18 | Message, 19 | MessageContentType, 20 | SyncHTTPClient, 21 | TokenAuth, 22 | ) 23 | 24 | 25 | def get_coze_api_base() -> str: 26 | # The default access is api.coze.cn, but if you need to access api.coze.com, 27 | # please use base_url to configure the api endpoint to access 28 | coze_api_base = os.getenv("COZE_API_BASE") 29 | if coze_api_base: 30 | return coze_api_base 31 | 32 | return COZE_CN_BASE_URL # default 33 | 34 | 35 | def get_coze_api_token(workspace_id: Optional[str] = None) -> str: 36 | # Get an access_token through personal access token or oauth. 37 | coze_api_token = os.getenv("COZE_API_TOKEN") 38 | if coze_api_token: 39 | return coze_api_token 40 | 41 | coze_api_base = get_coze_api_base() 42 | 43 | device_oauth_app = DeviceOAuthApp(client_id="57294420732781205987760324720643.app.coze", base_url=coze_api_base) 44 | device_code = device_oauth_app.get_device_code(workspace_id) 45 | print(f"Please Open: {device_code.verification_url} to get the access token") 46 | return device_oauth_app.get_access_token(device_code=device_code.device_code, poll=True).access_token 47 | 48 | 49 | # Coze client is built on httpx, and supports passing a custom httpx.Client when initializing 50 | # Coze, and setting a timeout on the httpx.Client 51 | http_client = SyncHTTPClient( 52 | timeout=httpx.Timeout( 53 | # 600s timeout on elsewhere 54 | timeout=600.0, 55 | # 5s timeout on connect 56 | connect=5.0, 57 | ) 58 | ) 59 | # Init the Coze client through the access_token and custom timeout http client. 60 | coze = Coze(auth=TokenAuth(token=get_coze_api_token()), base_url=get_coze_api_base(), http_client=http_client) 61 | 62 | # The same is true for asynchronous clients 63 | async_coze = AsyncCoze( 64 | auth=TokenAuth(token=get_coze_api_token()), 65 | base_url=get_coze_api_base(), 66 | http_client=AsyncHTTPClient( 67 | timeout=httpx.Timeout( 68 | # 600s timeout on elsewhere 69 | timeout=600.0, 70 | # 5s timeout on connect 71 | connect=5.0, 72 | ) 73 | ), 74 | ) 75 | -------------------------------------------------------------------------------- /examples/users_me.py: -------------------------------------------------------------------------------- 1 | import os 2 | from typing import Optional 3 | 4 | from cozepy import ( 5 | COZE_CN_BASE_URL, 6 | Coze, 7 | DeviceOAuthApp, 8 | TokenAuth, 9 | ) 10 | 11 | 12 | def get_coze_api_base() -> str: 13 | # The default access is api.coze.cn, but if you need to access api.coze.com, 14 | # please use base_url to configure the api endpoint to access 15 | coze_api_base = os.getenv("COZE_API_BASE") 16 | if coze_api_base: 17 | return coze_api_base 18 | 19 | return COZE_CN_BASE_URL # default 20 | 21 | 22 | def get_coze_api_token(workspace_id: Optional[str] = None) -> str: 23 | # Get an access_token through personal access token or oauth. 24 | coze_api_token = os.getenv("COZE_API_TOKEN") 25 | if coze_api_token: 26 | return coze_api_token 27 | 28 | coze_api_base = get_coze_api_base() 29 | 30 | device_oauth_app = DeviceOAuthApp(client_id="57294420732781205987760324720643.app.coze", base_url=coze_api_base) 31 | device_code = device_oauth_app.get_device_code(workspace_id) 32 | print(f"Please Open: {device_code.verification_url} to get the access token") 33 | return device_oauth_app.get_access_token(device_code=device_code.device_code, poll=True).access_token 34 | 35 | 36 | # Init the Coze client through the access_token. 37 | coze = Coze(auth=TokenAuth(token=get_coze_api_token()), base_url=get_coze_api_base()) 38 | 39 | user = coze.users.me() 40 | print("user", user) 41 | print("logid", user.response.logid) 42 | -------------------------------------------------------------------------------- /examples/variable_retrieve.py: -------------------------------------------------------------------------------- 1 | import os 2 | from typing import Optional 3 | 4 | from cozepy import COZE_CN_BASE_URL, ChatEventType, Coze, DeviceOAuthApp, Message, TokenAuth, setup_logging # noqa 5 | 6 | 7 | def get_coze_api_base() -> str: 8 | # The default access is api.coze.cn, but if you need to access api.coze.com, 9 | # please use base_url to configure the api endpoint to access 10 | coze_api_base = os.getenv("COZE_API_BASE") 11 | if coze_api_base: 12 | return coze_api_base 13 | 14 | return COZE_CN_BASE_URL # default 15 | 16 | 17 | def get_coze_api_token(workspace_id: Optional[str] = None) -> str: 18 | # Get an access_token through personal access token or oauth. 19 | coze_api_token = os.getenv("COZE_API_TOKEN") 20 | if coze_api_token: 21 | return coze_api_token 22 | 23 | coze_api_base = get_coze_api_base() 24 | 25 | device_oauth_app = DeviceOAuthApp(client_id="57294420732781205987760324720643.app.coze", base_url=coze_api_base) 26 | device_code = device_oauth_app.get_device_code(workspace_id) 27 | print(f"Please Open: {device_code.verification_url} to get the access token") 28 | return device_oauth_app.get_access_token(device_code=device_code.device_code, poll=True).access_token 29 | 30 | 31 | # Init the Coze client through the access_token. 32 | coze = Coze(auth=TokenAuth(token=get_coze_api_token()), base_url=get_coze_api_base()) 33 | 34 | # Create a bot instance in Coze, copy the last number from the web link as the bot's ID. 35 | bot_id = os.getenv("COZE_BOT_ID") or "bot id" 36 | # The user id identifies the identity of a user. Developers can use a custom business ID 37 | # or a random string. 38 | user_id = "user id" 39 | 40 | res = coze.variables.retrieve(bot_id=bot_id, connector_uid=user_id, keywords=["name", "user_like"]) 41 | print("logid:", res.response.logid) 42 | print("retrieve response:", list(res)) 43 | -------------------------------------------------------------------------------- /examples/variable_update.py: -------------------------------------------------------------------------------- 1 | import os 2 | from typing import Optional 3 | 4 | from cozepy import ( 5 | COZE_CN_BASE_URL, 6 | Coze, 7 | DeviceOAuthApp, 8 | TokenAuth, 9 | VariableValue, # noqa 10 | ) 11 | 12 | 13 | def get_coze_api_base() -> str: 14 | # The default access is api.coze.cn, but if you need to access api.coze.com, 15 | # please use base_url to configure the api endpoint to access 16 | coze_api_base = os.getenv("COZE_API_BASE") 17 | if coze_api_base: 18 | return coze_api_base 19 | 20 | return COZE_CN_BASE_URL # default 21 | 22 | 23 | def get_coze_api_token(workspace_id: Optional[str] = None) -> str: 24 | # Get an access_token through personal access token or oauth. 25 | coze_api_token = os.getenv("COZE_API_TOKEN") 26 | if coze_api_token: 27 | return coze_api_token 28 | 29 | coze_api_base = get_coze_api_base() 30 | 31 | device_oauth_app = DeviceOAuthApp(client_id="57294420732781205987760324720643.app.coze", base_url=coze_api_base) 32 | device_code = device_oauth_app.get_device_code(workspace_id) 33 | print(f"Please Open: {device_code.verification_url} to get the access token") 34 | return device_oauth_app.get_access_token(device_code=device_code.device_code, poll=True).access_token 35 | 36 | 37 | # Init the Coze client through the access_token. 38 | coze = Coze(auth=TokenAuth(token=get_coze_api_token()), base_url=get_coze_api_base()) 39 | 40 | # Create a bot instance in Coze, copy the last number from the web link as the bot's ID. 41 | bot_id = os.getenv("COZE_BOT_ID") or "bot id" 42 | # The user id identifies the identity of a user. Developers can use a custom business ID 43 | # or a random string. 44 | user_id = "user id" 45 | 46 | res = coze.variables.update( 47 | bot_id=bot_id, 48 | connector_uid=user_id, 49 | data=[ 50 | VariableValue(keyword="user_like", value="coze"), 51 | ], 52 | ) 53 | print("logid:", res.response.logid) 54 | -------------------------------------------------------------------------------- /examples/websockets_audio_speech.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import json 3 | import logging 4 | import os 5 | from typing import Optional 6 | 7 | from cozepy import ( 8 | COZE_CN_BASE_URL, 9 | AsyncCoze, 10 | AsyncWebsocketsAudioSpeechClient, 11 | AsyncWebsocketsAudioSpeechEventHandler, 12 | DeviceOAuthApp, 13 | InputTextBufferAppendEvent, 14 | InputTextBufferCompletedEvent, 15 | SpeechAudioCompletedEvent, 16 | SpeechAudioUpdateEvent, 17 | TokenAuth, 18 | setup_logging, 19 | ) 20 | from cozepy.log import log_info 21 | from cozepy.util import write_pcm_to_wav_file 22 | 23 | 24 | def get_coze_api_base() -> str: 25 | # The default access is api.coze.cn, but if you need to access api.coze.com, 26 | # please use base_url to configure the api endpoint to access 27 | coze_api_base = os.getenv("COZE_API_BASE") 28 | if coze_api_base: 29 | return coze_api_base 30 | 31 | return COZE_CN_BASE_URL # default 32 | 33 | 34 | def get_coze_api_token(workspace_id: Optional[str] = None) -> str: 35 | # Get an access_token through personal access token or oauth. 36 | coze_api_token = os.getenv("COZE_API_TOKEN") 37 | if coze_api_token: 38 | return coze_api_token 39 | 40 | coze_api_base = get_coze_api_base() 41 | 42 | device_oauth_app = DeviceOAuthApp(client_id="57294420732781205987760324720643.app.coze", base_url=coze_api_base) 43 | device_code = device_oauth_app.get_device_code(workspace_id) 44 | print(f"Please Open: {device_code.verification_url} to get the access token") 45 | return device_oauth_app.get_access_token(device_code=device_code.device_code, poll=True).access_token 46 | 47 | 48 | def setup_examples_logger(): 49 | coze_log = os.getenv("COZE_LOG") 50 | if coze_log: 51 | setup_logging(logging.getLevelNamesMapping().get(coze_log.upper(), logging.INFO)) 52 | 53 | 54 | setup_examples_logger() 55 | 56 | kwargs = json.loads(os.getenv("COZE_KWARGS") or "{}") 57 | 58 | 59 | class AsyncWebsocketsAudioSpeechEventHandlerSub(AsyncWebsocketsAudioSpeechEventHandler): 60 | """ 61 | Class is not required, you can also use Dict to set callback 62 | """ 63 | 64 | delta = [] 65 | 66 | async def on_input_text_buffer_completed( 67 | self, cli: "AsyncWebsocketsAudioSpeechClient", event: InputTextBufferCompletedEvent 68 | ): 69 | log_info("[examples] Input text buffer completed") 70 | 71 | async def on_speech_audio_update(self, cli: AsyncWebsocketsAudioSpeechClient, event: SpeechAudioUpdateEvent): 72 | self.delta.append(event.data.delta) 73 | 74 | async def on_error(self, cli: AsyncWebsocketsAudioSpeechClient, e: Exception): 75 | log_info("[examples] Error occurred: %s", e) 76 | 77 | async def on_speech_audio_completed( 78 | self, cli: "AsyncWebsocketsAudioSpeechClient", event: SpeechAudioCompletedEvent 79 | ): 80 | log_info("[examples] Saving audio data to output.wav") 81 | write_pcm_to_wav_file(b"".join(self.delta), "output.wav") 82 | self.delta = [] 83 | 84 | 85 | async def main(): 86 | coze_api_token = get_coze_api_token() 87 | coze_api_base = get_coze_api_base() 88 | 89 | # Initialize Coze client 90 | coze = AsyncCoze( 91 | auth=TokenAuth(coze_api_token), 92 | base_url=coze_api_base, 93 | ) 94 | 95 | speech = coze.websockets.audio.speech.create( 96 | on_event=AsyncWebsocketsAudioSpeechEventHandlerSub(), 97 | **kwargs, 98 | ) 99 | 100 | # Text to be converted to speech 101 | text = "你今天好吗? 今天天气不错呀" 102 | 103 | async with speech() as client: 104 | await client.input_text_buffer_append( 105 | InputTextBufferAppendEvent.Data.model_validate( 106 | { 107 | "delta": text, 108 | } 109 | ) 110 | ) 111 | await client.input_text_buffer_complete() 112 | await client.wait() 113 | 114 | 115 | if __name__ == "__main__": 116 | asyncio.run(main()) 117 | -------------------------------------------------------------------------------- /examples/websockets_audio_transcriptions.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import json 3 | import logging 4 | import os 5 | from typing import Optional 6 | 7 | from cozepy import ( 8 | COZE_CN_BASE_URL, 9 | AsyncCoze, 10 | AsyncWebsocketsAudioTranscriptionsClient, 11 | AsyncWebsocketsAudioTranscriptionsEventHandler, 12 | AudioFormat, 13 | DeviceOAuthApp, 14 | InputAudioBufferAppendEvent, 15 | InputAudioBufferCompletedEvent, 16 | TokenAuth, 17 | TranscriptionsMessageUpdateEvent, 18 | setup_logging, 19 | ) 20 | from cozepy.log import log_info 21 | 22 | 23 | def get_coze_api_base() -> str: 24 | # The default access is api.coze.cn, but if you need to access api.coze.com, 25 | # please use base_url to configure the api endpoint to access 26 | coze_api_base = os.getenv("COZE_API_BASE") 27 | if coze_api_base: 28 | return coze_api_base 29 | 30 | return COZE_CN_BASE_URL # default 31 | 32 | 33 | def get_coze_api_token(workspace_id: Optional[str] = None) -> str: 34 | # Get an access_token through personal access token or oauth. 35 | coze_api_token = os.getenv("COZE_API_TOKEN") 36 | if coze_api_token: 37 | return coze_api_token 38 | 39 | coze_api_base = get_coze_api_base() 40 | 41 | device_oauth_app = DeviceOAuthApp(client_id="57294420732781205987760324720643.app.coze", base_url=coze_api_base) 42 | device_code = device_oauth_app.get_device_code(workspace_id) 43 | print(f"Please Open: {device_code.verification_url} to get the access token") 44 | return device_oauth_app.get_access_token(device_code=device_code.device_code, poll=True).access_token 45 | 46 | 47 | def setup_examples_logger(): 48 | coze_log = os.getenv("COZE_LOG") 49 | if coze_log: 50 | setup_logging(logging.getLevelNamesMapping().get(coze_log.upper(), logging.INFO)) 51 | 52 | 53 | setup_examples_logger() 54 | 55 | kwargs = json.loads(os.getenv("COZE_KWARGS") or "{}") 56 | 57 | 58 | class AudioTranscriptionsEventHandlerSub(AsyncWebsocketsAudioTranscriptionsEventHandler): 59 | """ 60 | Class is not required, you can also use Dict to set callback 61 | """ 62 | 63 | async def on_closed(self, cli: "AsyncWebsocketsAudioTranscriptionsClient"): 64 | log_info("[examples] Connect closed") 65 | 66 | async def on_error(self, cli: "AsyncWebsocketsAudioTranscriptionsClient", e: Exception): 67 | log_info("[examples] Error occurred: %s", e) 68 | 69 | async def on_transcriptions_message_update( 70 | self, cli: "AsyncWebsocketsAudioTranscriptionsClient", event: TranscriptionsMessageUpdateEvent 71 | ): 72 | log_info("[examples] Received: %s", event.data.content) 73 | 74 | async def on_input_audio_buffer_completed( 75 | self, cli: "AsyncWebsocketsAudioTranscriptionsClient", event: InputAudioBufferCompletedEvent 76 | ): 77 | log_info("[examples] Input audio buffer completed") 78 | 79 | 80 | def wrap_coze_speech_to_iterator(coze: AsyncCoze, text: str): 81 | async def iterator(): 82 | voices = await coze.audio.voices.list(**kwargs) 83 | content = await coze.audio.speech.create( 84 | input=text, 85 | voice_id=voices.items[0].voice_id, 86 | response_format=AudioFormat.WAV, 87 | sample_rate=24000, 88 | **kwargs, 89 | ) 90 | for data in content._raw_response.iter_bytes(chunk_size=1024): 91 | yield data 92 | 93 | return iterator 94 | 95 | 96 | async def main(): 97 | coze_api_token = get_coze_api_token() 98 | coze_api_base = get_coze_api_base() 99 | 100 | # Initialize Coze client 101 | coze = AsyncCoze( 102 | auth=TokenAuth(coze_api_token), 103 | base_url=coze_api_base, 104 | ) 105 | # Initialize Audio 106 | speech_stream = wrap_coze_speech_to_iterator(coze, "你今天好吗? 今天天气不错呀") 107 | 108 | transcriptions = coze.websockets.audio.transcriptions.create( 109 | on_event=AudioTranscriptionsEventHandlerSub(), 110 | **kwargs, 111 | ) 112 | 113 | # Create and connect WebSocket client 114 | async with transcriptions() as client: 115 | async for delta in speech_stream(): 116 | await client.input_audio_buffer_append( 117 | InputAudioBufferAppendEvent.Data.model_validate( 118 | { 119 | "delta": delta, 120 | } 121 | ) 122 | ) 123 | await client.input_audio_buffer_complete() 124 | await client.wait() 125 | 126 | 127 | if __name__ == "__main__": 128 | asyncio.run(main()) 129 | -------------------------------------------------------------------------------- /examples/workflow_async.py: -------------------------------------------------------------------------------- 1 | """ 2 | This example describes how to use the workflow with async. 3 | """ 4 | 5 | import logging 6 | import os 7 | import time 8 | from typing import Optional 9 | 10 | from cozepy import COZE_CN_BASE_URL, Coze, DeviceOAuthApp, TokenAuth, WorkflowExecuteStatus, setup_logging 11 | 12 | 13 | def get_coze_api_base() -> str: 14 | # The default access is api.coze.cn, but if you need to access api.coze.com, 15 | # please use base_url to configure the api endpoint to access 16 | coze_api_base = os.getenv("COZE_API_BASE") 17 | if coze_api_base: 18 | return coze_api_base 19 | 20 | return COZE_CN_BASE_URL # default 21 | 22 | 23 | def get_coze_api_token(workspace_id: Optional[str] = None) -> str: 24 | # Get an access_token through personal access token or oauth. 25 | coze_api_token = os.getenv("COZE_API_TOKEN") 26 | if coze_api_token: 27 | return coze_api_token 28 | 29 | coze_api_base = get_coze_api_base() 30 | 31 | device_oauth_app = DeviceOAuthApp(client_id="57294420732781205987760324720643.app.coze", base_url=coze_api_base) 32 | device_code = device_oauth_app.get_device_code(workspace_id) 33 | print(f"Please Open: {device_code.verification_url} to get the access token") 34 | return device_oauth_app.get_access_token(device_code=device_code.device_code, poll=True).access_token 35 | 36 | 37 | # Init the Coze client through the access_token. 38 | coze = Coze(auth=TokenAuth(token=get_coze_api_token()), base_url=get_coze_api_base()) 39 | # Whether to print detailed logs 40 | is_debug = os.getenv("DEBUG") 41 | if is_debug: 42 | setup_logging(logging.DEBUG) 43 | 44 | 45 | # Create a workflow instance in Coze, copy the last number from the web link as the workflow's ID. 46 | workflow_id = os.getenv("COZE_WORKFLOW_ID") or "your workflow id" 47 | 48 | # Call the coze.workflows.runs.create method to create a workflow run. The create method 49 | # is a non-streaming chat and will return a WorkflowRunResult class. 50 | workflow_run = coze.workflows.runs.create(workflow_id=workflow_id, is_async=True) 51 | 52 | print("Start async workflow run:", workflow_run.execute_id) 53 | 54 | while True: 55 | run_history = coze.workflows.runs.run_histories.retrieve( 56 | workflow_id=workflow_id, execute_id=workflow_run.execute_id 57 | ) 58 | if run_history.execute_status == WorkflowExecuteStatus.FAIL: 59 | print("Workflow run fail:", run_history.error_message) 60 | break 61 | elif run_history.execute_status == WorkflowExecuteStatus.RUNNING: 62 | print("Workflow still running, sleep 1s and continue") 63 | time.sleep(1) 64 | continue 65 | else: 66 | print("Workflow run success:", run_history.output) 67 | break 68 | -------------------------------------------------------------------------------- /examples/workflow_chat_multimode_stream.py: -------------------------------------------------------------------------------- 1 | """ 2 | This example is about how to use the streaming interface to start a chat request 3 | and handle chat events 4 | """ 5 | 6 | import logging 7 | import os 8 | import sys 9 | from typing import Optional 10 | 11 | from cozepy import ( 12 | COZE_CN_BASE_URL, 13 | ChatEventType, 14 | Coze, 15 | DeviceOAuthApp, 16 | Message, 17 | MessageObjectString, 18 | TokenAuth, 19 | setup_logging, 20 | ) 21 | 22 | 23 | def get_coze_api_base() -> str: 24 | # The default access is api.coze.cn, but if you need to access api.coze.com, 25 | # please use base_url to configure the api endpoint to access 26 | coze_api_base = os.getenv("COZE_API_BASE") 27 | if coze_api_base: 28 | return coze_api_base 29 | 30 | return COZE_CN_BASE_URL # default 31 | 32 | 33 | def get_coze_api_token(workspace_id: Optional[str] = None) -> str: 34 | # Get an access_token through personal access token or oauth. 35 | coze_api_token = os.getenv("COZE_API_TOKEN") 36 | if coze_api_token: 37 | return coze_api_token 38 | 39 | coze_api_base = get_coze_api_base() 40 | 41 | device_oauth_app = DeviceOAuthApp(client_id="57294420732781205987760324720643.app.coze", base_url=coze_api_base) 42 | device_code = device_oauth_app.get_device_code(workspace_id) 43 | print(f"Please Open: {device_code.verification_url} to get the access token") 44 | return device_oauth_app.get_access_token(device_code=device_code.device_code, poll=True).access_token 45 | 46 | 47 | # Init the Coze client through the access_token. 48 | coze = Coze(auth=TokenAuth(token=get_coze_api_token()), base_url=get_coze_api_base()) 49 | # Get the workflow id 50 | workflow_id = os.getenv("COZE_WORKFLOW_ID") or "workflow id" 51 | # Get the bot id 52 | bot_id = os.getenv("COZE_BOT_ID") 53 | # Whether to print detailed logs 54 | is_debug = os.getenv("DEBUG") 55 | 56 | if is_debug: 57 | setup_logging(logging.DEBUG) 58 | 59 | 60 | conversation = coze.conversations.create() 61 | 62 | # Call the upload interface to upload a picture requiring text recognition, and 63 | # obtain the file_id of the picture. 64 | file_path = sys.argv[1] if len(sys.argv) > 1 else "/path/image.jpg" 65 | file = coze.files.upload(file=file_path) 66 | 67 | # Call the coze.chat.stream method to create a chat. The create method is a streaming 68 | # chat and will return a Chat Iterator. Developers should iterate the iterator to get 69 | # chat event and handle them. 70 | for event in coze.workflows.chat.stream( 71 | workflow_id=workflow_id, 72 | bot_id=bot_id, 73 | conversation_id=conversation.id, 74 | additional_messages=[ 75 | Message.build_user_question_objects( 76 | [ 77 | MessageObjectString.build_text("What text is on the picture?"), 78 | MessageObjectString.build_image(file_id=file.id), 79 | ] 80 | ), 81 | ], 82 | ): 83 | if event.event == ChatEventType.CONVERSATION_MESSAGE_DELTA: 84 | print(event.message.content, end="", flush=True) 85 | 86 | if event.event == ChatEventType.CONVERSATION_CHAT_COMPLETED: 87 | print() 88 | print("token usage:", event.chat.usage.token_count) 89 | -------------------------------------------------------------------------------- /examples/workflow_chat_stream.py: -------------------------------------------------------------------------------- 1 | """ 2 | This example is about how to use the streaming interface to start a chat request 3 | and handle chat events 4 | """ 5 | 6 | import logging 7 | import os 8 | from typing import Optional 9 | 10 | from cozepy import ( 11 | COZE_CN_BASE_URL, 12 | ChatEventType, 13 | Coze, 14 | DeviceOAuthApp, 15 | Message, 16 | TokenAuth, 17 | setup_logging, 18 | ) 19 | 20 | 21 | def get_coze_api_base() -> str: 22 | # The default access is api.coze.cn, but if you need to access api.coze.com, 23 | # please use base_url to configure the api endpoint to access 24 | coze_api_base = os.getenv("COZE_API_BASE") 25 | if coze_api_base: 26 | return coze_api_base 27 | 28 | return COZE_CN_BASE_URL # default 29 | 30 | 31 | def get_coze_api_token(workspace_id: Optional[str] = None) -> str: 32 | # Get an access_token through personal access token or oauth. 33 | coze_api_token = os.getenv("COZE_API_TOKEN") 34 | if coze_api_token: 35 | return coze_api_token 36 | 37 | coze_api_base = get_coze_api_base() 38 | 39 | device_oauth_app = DeviceOAuthApp(client_id="57294420732781205987760324720643.app.coze", base_url=coze_api_base) 40 | device_code = device_oauth_app.get_device_code(workspace_id) 41 | print(f"Please Open: {device_code.verification_url} to get the access token") 42 | return device_oauth_app.get_access_token(device_code=device_code.device_code, poll=True).access_token 43 | 44 | 45 | # Init the Coze client through the access_token. 46 | coze = Coze(auth=TokenAuth(token=get_coze_api_token()), base_url=get_coze_api_base()) 47 | # Get the workflow id 48 | workflow_id = os.getenv("COZE_WORKFLOW_ID") or "workflow id" 49 | # Get the bot id 50 | bot_id = os.getenv("COZE_BOT_ID") 51 | # Whether to print detailed logs 52 | is_debug = os.getenv("DEBUG") 53 | 54 | if is_debug: 55 | setup_logging(logging.DEBUG) 56 | 57 | 58 | conversation = coze.conversations.create() 59 | 60 | # Call the coze.chat.stream method to create a chat. The create method is a streaming 61 | # chat and will return a Chat Iterator. Developers should iterate the iterator to get 62 | # chat event and handle them. 63 | for event in coze.workflows.chat.stream( 64 | workflow_id=workflow_id, 65 | bot_id=bot_id, 66 | conversation_id=conversation.id, 67 | additional_messages=[ 68 | Message.build_user_question_text("How are you?"), 69 | ], 70 | ): 71 | if event.event == ChatEventType.CONVERSATION_MESSAGE_DELTA: 72 | print(event.message.content, end="", flush=True) 73 | 74 | if event.event == ChatEventType.CONVERSATION_CHAT_COMPLETED: 75 | print() 76 | print("token usage:", event.chat.usage.token_count) 77 | -------------------------------------------------------------------------------- /examples/workflow_no_stream.py: -------------------------------------------------------------------------------- 1 | """ 2 | This example describes how to use the workflow interface to chat. 3 | """ 4 | 5 | import os 6 | from typing import Optional 7 | 8 | from cozepy import COZE_CN_BASE_URL, ChatStatus, Coze, DeviceOAuthApp, Message, MessageContentType, TokenAuth # noqa 9 | 10 | 11 | def get_coze_api_base() -> str: 12 | # The default access is api.coze.cn, but if you need to access api.coze.com, 13 | # please use base_url to configure the api endpoint to access 14 | coze_api_base = os.getenv("COZE_API_BASE") 15 | if coze_api_base: 16 | return coze_api_base 17 | 18 | return COZE_CN_BASE_URL # default 19 | 20 | 21 | def get_coze_api_token(workspace_id: Optional[str] = None) -> str: 22 | # Get an access_token through personal access token or oauth. 23 | coze_api_token = os.getenv("COZE_API_TOKEN") 24 | if coze_api_token: 25 | return coze_api_token 26 | 27 | coze_api_base = get_coze_api_base() 28 | 29 | device_oauth_app = DeviceOAuthApp(client_id="57294420732781205987760324720643.app.coze", base_url=coze_api_base) 30 | device_code = device_oauth_app.get_device_code(workspace_id) 31 | print(f"Please Open: {device_code.verification_url} to get the access token") 32 | return device_oauth_app.get_access_token(device_code=device_code.device_code, poll=True).access_token 33 | 34 | 35 | # Init the Coze client through the access_token. 36 | coze = Coze(auth=TokenAuth(token=get_coze_api_token()), base_url=get_coze_api_base()) 37 | # Create a workflow instance in Coze, copy the last number from the web link as the workflow's ID. 38 | workflow_id = "workflow id" 39 | 40 | # Call the coze.workflows.runs.create method to create a workflow run. The create method 41 | # is a non-streaming chat and will return a WorkflowRunResult class. 42 | workflow = coze.workflows.runs.create( 43 | workflow_id=workflow_id, 44 | ) 45 | 46 | print("workflow.data", workflow.data) 47 | -------------------------------------------------------------------------------- /examples/workflow_run_history.py: -------------------------------------------------------------------------------- 1 | """ 2 | This example describes how to use the workflow run_histories. 3 | """ 4 | 5 | import os 6 | from typing import Optional 7 | 8 | from cozepy import COZE_CN_BASE_URL, Coze, DeviceOAuthApp, Stream, TokenAuth, WorkflowEvent, WorkflowEventType # noqa 9 | 10 | 11 | def get_coze_api_base() -> str: 12 | # The default access is api.coze.cn, but if you need to access api.coze.com, 13 | # please use base_url to configure the api endpoint to access 14 | coze_api_base = os.getenv("COZE_API_BASE") 15 | if coze_api_base: 16 | return coze_api_base 17 | 18 | return COZE_CN_BASE_URL # default 19 | 20 | 21 | def get_coze_api_token(workspace_id: Optional[str] = None) -> str: 22 | # Get an access_token through personal access token or oauth. 23 | coze_api_token = os.getenv("COZE_API_TOKEN") 24 | if coze_api_token: 25 | return coze_api_token 26 | 27 | coze_api_base = get_coze_api_base() 28 | 29 | device_oauth_app = DeviceOAuthApp(client_id="57294420732781205987760324720643.app.coze", base_url=coze_api_base) 30 | device_code = device_oauth_app.get_device_code(workspace_id) 31 | print(f"Please Open: {device_code.verification_url} to get the access token") 32 | return device_oauth_app.get_access_token(device_code=device_code.device_code, poll=True).access_token 33 | 34 | 35 | # Init the Coze client through the access_token. 36 | coze = Coze(auth=TokenAuth(token=get_coze_api_token()), base_url=get_coze_api_base()) 37 | # Create a workflow instance in Coze, copy the last number from the web link as the workflow's ID. 38 | workflow_id = os.getenv("COZE_WORKFLOW_ID") or "workflow id" 39 | execute_id = os.getenv("COZE_EXECUTE_ID") or "execute id" 40 | 41 | run_history = coze.workflows.runs.run_histories.retrieve(workflow_id=workflow_id, execute_id=execute_id) 42 | print("run_history:", run_history) 43 | print("logid:", run_history.response.logid) 44 | -------------------------------------------------------------------------------- /examples/workflow_stream.py: -------------------------------------------------------------------------------- 1 | """ 2 | This example describes how to use the workflow interface to stream chat. 3 | """ 4 | 5 | import json 6 | import os 7 | from typing import Optional 8 | 9 | from cozepy import COZE_CN_BASE_URL, Coze, DeviceOAuthApp, Stream, TokenAuth, WorkflowEvent, WorkflowEventType # noqa 10 | 11 | 12 | def get_coze_api_base() -> str: 13 | # The default access is api.coze.cn, but if you need to access api.coze.com, 14 | # please use base_url to configure the api endpoint to access 15 | coze_api_base = os.getenv("COZE_API_BASE") 16 | if coze_api_base: 17 | return coze_api_base 18 | 19 | return COZE_CN_BASE_URL # default 20 | 21 | 22 | def get_coze_api_token(workspace_id: Optional[str] = None) -> str: 23 | # Get an access_token through personal access token or oauth. 24 | coze_api_token = os.getenv("COZE_API_TOKEN") 25 | if coze_api_token: 26 | return coze_api_token 27 | 28 | coze_api_base = get_coze_api_base() 29 | 30 | device_oauth_app = DeviceOAuthApp(client_id="57294420732781205987760324720643.app.coze", base_url=coze_api_base) 31 | device_code = device_oauth_app.get_device_code(workspace_id) 32 | print(f"Please Open: {device_code.verification_url} to get the access token") 33 | return device_oauth_app.get_access_token(device_code=device_code.device_code, poll=True).access_token 34 | 35 | 36 | # Init the Coze client through the access_token. 37 | coze = Coze(auth=TokenAuth(token=get_coze_api_token()), base_url=get_coze_api_base()) 38 | # Create a workflow instance in Coze, copy the last number from the web link as the workflow's ID. 39 | workflow_id = os.getenv("COZE_WORKFLOW_ID") or "workflow id" 40 | # parameters 41 | parameters = json.loads(os.getenv("COZE_PARAMETERS") or "{}") 42 | 43 | 44 | # The stream interface will return an iterator of WorkflowEvent. Developers should iterate 45 | # through this iterator to obtain WorkflowEvent and handle them separately according to 46 | # the type of WorkflowEvent. 47 | def handle_workflow_iterator(stream: Stream[WorkflowEvent]): 48 | for event in stream: 49 | if event.event == WorkflowEventType.MESSAGE: 50 | print("got message", event.message) 51 | elif event.event == WorkflowEventType.ERROR: 52 | print("got error", event.error) 53 | elif event.event == WorkflowEventType.INTERRUPT: 54 | handle_workflow_iterator( 55 | coze.workflows.runs.resume( 56 | workflow_id=workflow_id, 57 | event_id=event.interrupt.interrupt_data.event_id, 58 | resume_data="hey", 59 | interrupt_type=event.interrupt.interrupt_data.type, 60 | ) 61 | ) 62 | 63 | 64 | handle_workflow_iterator( 65 | coze.workflows.runs.stream( 66 | workflow_id=workflow_id, 67 | parameters=parameters, 68 | ) 69 | ) 70 | -------------------------------------------------------------------------------- /examples/workspace.py: -------------------------------------------------------------------------------- 1 | """ 2 | This example is about how to list workspaces. 3 | """ 4 | 5 | import os 6 | from typing import Optional 7 | 8 | from cozepy import ( 9 | COZE_CN_BASE_URL, 10 | Coze, 11 | DeviceOAuthApp, # noqa 12 | TokenAuth, 13 | ) 14 | 15 | 16 | def get_coze_api_base() -> str: 17 | # The default access is api.coze.cn, but if you need to access api.coze.com, 18 | # please use base_url to configure the api endpoint to access 19 | coze_api_base = os.getenv("COZE_API_BASE") 20 | if coze_api_base: 21 | return coze_api_base 22 | 23 | return COZE_CN_BASE_URL # default 24 | 25 | 26 | def get_coze_api_token(workspace_id: Optional[str] = None) -> str: 27 | # Get an access_token through personal access token or oauth. 28 | coze_api_token = os.getenv("COZE_API_TOKEN") 29 | if coze_api_token: 30 | return coze_api_token 31 | 32 | coze_api_base = get_coze_api_base() 33 | 34 | device_oauth_app = DeviceOAuthApp(client_id="57294420732781205987760324720643.app.coze", base_url=coze_api_base) 35 | device_code = device_oauth_app.get_device_code(workspace_id) 36 | print(f"Please Open: {device_code.verification_url} to get the access token") 37 | return device_oauth_app.get_access_token(device_code=device_code.device_code, poll=True).access_token 38 | 39 | 40 | # Init the Coze client through the access_token. 41 | coze = Coze(auth=TokenAuth(token=get_coze_api_token()), base_url=get_coze_api_base()) 42 | # Call the api to get workspace list. 43 | workspaces = coze.workspaces.list() 44 | for workspace in workspaces: 45 | # workspaces is an iterator. Traversing workspaces will automatically turn pages and 46 | # get all workspace results. 47 | print("Get workspace", workspace.id, workspace.name) 48 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "cozepy" 3 | version = "0.16.2" 4 | description = "OpenAPI SDK for Coze(coze.com/coze.cn)" 5 | authors = ["chyroc "] 6 | readme = "README.md" 7 | license = "MIT" 8 | 9 | [tool.poetry.dependencies] 10 | python = "^3.7" 11 | pydantic = "^2.5.0" 12 | authlib = "^1.2.0" 13 | httpx = [ 14 | { version = "^0.24.0", python = "<=3.7" }, 15 | { version = "^0.27.0", python = "^3.8" }, 16 | ] 17 | typing-extensions = "^4.3.0" 18 | distro = "^1.9.0" 19 | websockets = [ 20 | { version = "^14.1.0", python = ">=3.9" }, 21 | { version = "^13.1.0", python = ">=3.8,<3.9" }, 22 | { version = "^11.0.3", python = ">=3.7,<3.8" }, 23 | ] 24 | 25 | [tool.poetry.group.dev.dependencies] 26 | pytest = "^7.0.0" 27 | pytest-cov = "^4.0.0" 28 | pytest-asyncio = "^0.21.0" 29 | ruff = "^0.6.0" 30 | pre-commit = "^2.9.0" 31 | respx = "^0.21.1" 32 | mypy = "^1.0.0" 33 | 34 | [tool.ruff] 35 | line-length = 120 36 | 37 | [tool.ruff.lint] 38 | select = ["E", "F", "I"] 39 | ignore = ["E501"] 40 | 41 | [tool.mypy] 42 | exclude = [ 43 | "examples/", 44 | "tests/" 45 | ] 46 | 47 | [tool.coverage.report] 48 | precision = 1 49 | skip_covered = true 50 | exclude_lines = [ 51 | "abc.abstractmethod", 52 | "if TYPE_CHECKING.*:", 53 | "raise NotImplementedError", 54 | ] 55 | 56 | [build-system] 57 | requires = ["poetry-core"] 58 | build-backend = "poetry.core.masonry.api" 59 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coze-dev/coze-py/628f590f17fa1b1076cb06728448202db2906a8d/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_audio_rooms.py: -------------------------------------------------------------------------------- 1 | import httpx 2 | import pytest 3 | 4 | from cozepy import AsyncCoze, AsyncTokenAuth, Coze, CreateRoomResp, TokenAuth 5 | from cozepy.util import random_hex 6 | from tests.test_util import logid_key 7 | 8 | 9 | def mock_create_room(respx_mock): 10 | create_room_res = CreateRoomResp( 11 | token=random_hex(10), 12 | uid=random_hex(10), 13 | room_id=random_hex(10), 14 | app_id=random_hex(10), 15 | ) 16 | create_room_res._raw_response = httpx.Response( 17 | 200, 18 | json={"data": create_room_res.model_dump()}, 19 | headers={logid_key(): random_hex(10)}, 20 | ) 21 | respx_mock.post("/v1/audio/rooms").mock(create_room_res._raw_response) 22 | 23 | return create_room_res 24 | 25 | 26 | @pytest.mark.respx(base_url="https://api.coze.com") 27 | class TestSyncAudioRooms: 28 | def test_sync_rooms_create(self, respx_mock): 29 | coze = Coze(auth=TokenAuth(token="token")) 30 | 31 | bot_id = random_hex(10) 32 | voice_id = random_hex(10) 33 | mock_res = mock_create_room(respx_mock) 34 | 35 | res = coze.audio.rooms.create(bot_id=bot_id, voice_id=voice_id) 36 | assert res 37 | assert res.response.logid == mock_res.response.logid 38 | assert res.token == mock_res.token 39 | assert res.room_id == mock_res.room_id 40 | assert res.uid == mock_res.uid 41 | assert res.app_id == mock_res.app_id 42 | 43 | 44 | @pytest.mark.respx(base_url="https://api.coze.com") 45 | @pytest.mark.asyncio 46 | class TestAsyncAudioRooms: 47 | async def test_async_rooms_create(self, respx_mock): 48 | coze = AsyncCoze(auth=AsyncTokenAuth(token="token")) 49 | 50 | bot_id = random_hex(10) 51 | voice_id = random_hex(10) 52 | mock_res = mock_create_room(respx_mock) 53 | 54 | res = await coze.audio.rooms.create(bot_id=bot_id, voice_id=voice_id) 55 | assert res 56 | assert res.response.logid is not None 57 | assert res.response.logid == mock_res.response.logid 58 | assert res.token == mock_res.token 59 | assert res.room_id == mock_res.room_id 60 | assert res.uid == mock_res.uid 61 | assert res.app_id == mock_res.app_id 62 | -------------------------------------------------------------------------------- /tests/test_audio_speech.py: -------------------------------------------------------------------------------- 1 | import httpx 2 | import pytest 3 | 4 | from cozepy import AsyncCoze, AsyncTokenAuth, Coze, TokenAuth 5 | from cozepy.util import random_hex 6 | from tests.test_util import logid_key 7 | 8 | 9 | def mock_create_speech(respx_mock): 10 | logid = random_hex(10) 11 | raw_response = httpx.Response( 12 | 200, 13 | content="file content", 14 | headers={ 15 | "content-type": "audio/mpeg", 16 | logid_key(): logid, 17 | }, 18 | ) 19 | 20 | respx_mock.post("/v1/audio/speech").mock(raw_response) 21 | 22 | return logid 23 | 24 | 25 | @pytest.mark.respx(base_url="https://api.coze.com") 26 | class TestAudioSpeech: 27 | def test_sync_speech_create(self, respx_mock): 28 | coze = Coze(auth=TokenAuth(token="token")) 29 | 30 | mock_logid = mock_create_speech(respx_mock) 31 | 32 | res = coze.audio.speech.create(input=random_hex(10), voice_id=random_hex(10), speed=1.5, sample_rate=32000) 33 | assert res 34 | assert res.response.logid is not None 35 | assert res.response.logid == mock_logid 36 | 37 | 38 | @pytest.mark.respx(base_url="https://api.coze.com") 39 | @pytest.mark.asyncio 40 | class TestAsyncAudioSpeech: 41 | async def test_async_speech_create(self, respx_mock): 42 | coze = AsyncCoze(auth=AsyncTokenAuth(token="token")) 43 | 44 | mock_logid = mock_create_speech(respx_mock) 45 | 46 | res = await coze.audio.speech.create(input=random_hex(10), voice_id=random_hex(10)) 47 | assert res 48 | assert res.response.logid is not None 49 | assert res.response.logid == mock_logid 50 | -------------------------------------------------------------------------------- /tests/test_audio_translations.py: -------------------------------------------------------------------------------- 1 | import httpx 2 | import pytest 3 | 4 | from cozepy import AsyncCoze, AsyncTokenAuth, Coze, TokenAuth 5 | from cozepy.util import random_hex 6 | from tests.test_util import logid_key 7 | 8 | 9 | def mock_create_transcriptions(respx_mock): 10 | logid = random_hex(10) 11 | raw_response = httpx.Response( 12 | 200, 13 | json={ 14 | "text": "text", 15 | }, 16 | headers={ 17 | "content-type": "audio/mpeg", 18 | logid_key(): logid, 19 | }, 20 | ) 21 | 22 | respx_mock.post("/v1/audio/transcriptions").mock(raw_response) 23 | 24 | return logid 25 | 26 | 27 | @pytest.mark.respx(base_url="https://api.coze.com") 28 | class TestSyncAudioTranscriptions: 29 | def test_sync_transcriptions_create(self, respx_mock): 30 | coze = Coze(auth=TokenAuth(token="token")) 31 | 32 | mock_logid = mock_create_transcriptions(respx_mock) 33 | 34 | res = coze.audio.transcriptions.create(file=("filename", "content")) 35 | assert res 36 | assert res.response.logid is not None 37 | assert res.response.logid == mock_logid 38 | 39 | 40 | @pytest.mark.respx(base_url="https://api.coze.com") 41 | @pytest.mark.asyncio 42 | class TestAsyncAudioTranscriptions: 43 | async def test_async_transcriptions_create(self, respx_mock): 44 | coze = AsyncCoze(auth=AsyncTokenAuth(token="token")) 45 | 46 | mock_logid = mock_create_transcriptions(respx_mock) 47 | 48 | res = await coze.audio.transcriptions.create(file=("filename", "content")) 49 | assert res 50 | assert res.response.logid == mock_logid 51 | -------------------------------------------------------------------------------- /tests/test_audio_voices.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | import httpx 4 | import pytest 5 | 6 | from cozepy import AsyncCoze, AsyncTokenAuth, AudioFormat, Coze, TokenAuth, Voice 7 | from cozepy.util import random_hex 8 | from tests.test_util import logid_key 9 | 10 | 11 | def mock_list_voices(respx_mock) -> str: 12 | logid = random_hex(10) 13 | raw_response = httpx.Response( 14 | 200, 15 | json={ 16 | "data": { 17 | "voice_list": [ 18 | Voice( 19 | voice_id="voice_id", 20 | name="name", 21 | is_system_voice=False, 22 | language_code="language_code", 23 | language_name="language_name", 24 | preview_text="preview_text", 25 | preview_audio="preview_audio", 26 | available_training_times=1, 27 | create_time=int(time.time()), 28 | update_time=int(time.time()), 29 | ).model_dump() 30 | ], 31 | "has_more": False, 32 | } 33 | }, 34 | headers={logid_key(): logid}, 35 | ) 36 | 37 | respx_mock.get("/v1/audio/voices").mock(raw_response) 38 | 39 | return logid 40 | 41 | 42 | def mock_clone_voice(respx_mock) -> Voice: 43 | voice = Voice( 44 | voice_id="voice_id", 45 | name="name", 46 | is_system_voice=False, 47 | language_code="language_code", 48 | language_name="language_name", 49 | preview_text="preview_text", 50 | preview_audio="preview_audio", 51 | available_training_times=1, 52 | create_time=int(time.time()), 53 | update_time=int(time.time()), 54 | ) 55 | voice._raw_response = httpx.Response( 56 | 200, 57 | json={"data": voice.model_dump()}, 58 | headers={logid_key(): random_hex(10)}, 59 | ) 60 | respx_mock.post("/v1/audio/voices/clone").mock(voice._raw_response) 61 | return voice 62 | 63 | 64 | @pytest.mark.respx(base_url="https://api.coze.com") 65 | class TestSyncAudioVoices: 66 | def test_sync_voices_list(self, respx_mock): 67 | coze = Coze(auth=TokenAuth(token="token")) 68 | 69 | mock_logid = mock_list_voices(respx_mock) 70 | 71 | voices = coze.audio.voices.list() 72 | assert voices.response.logid == mock_logid 73 | 74 | voices = [i for i in voices] 75 | assert voices 76 | assert len(voices) == 1 77 | 78 | def test_clone_voice(self, respx_mock): 79 | coze = Coze(auth=TokenAuth(token="token")) 80 | 81 | mock_voice = mock_clone_voice(respx_mock) 82 | 83 | voice = coze.audio.voices.clone(voice_name="voice_name", file=("name", "content"), audio_format=AudioFormat.MP3) 84 | assert voice.response.logid == mock_voice.response.logid 85 | 86 | 87 | @pytest.mark.respx(base_url="https://api.coze.com") 88 | @pytest.mark.asyncio 89 | class TestAsyncAudioVoices: 90 | async def test_async_voices_list(self, respx_mock): 91 | coze = AsyncCoze(auth=AsyncTokenAuth(token="token")) 92 | 93 | mock_logid = mock_list_voices(respx_mock) 94 | 95 | voices = await coze.audio.voices.list() 96 | assert voices.response.logid == mock_logid 97 | 98 | voices = [i async for i in voices] 99 | assert voices 100 | assert len(voices) == 1 101 | 102 | async def test_async_clone_voice(self, respx_mock): 103 | coze = AsyncCoze(auth=AsyncTokenAuth(token="token")) 104 | 105 | mock_voice = mock_clone_voice(respx_mock) 106 | 107 | voice = await coze.audio.voices.clone( 108 | voice_name="voice_name", file=("name", "content"), audio_format=AudioFormat.MP3 109 | ) 110 | assert voice.response.logid == mock_voice.response.logid 111 | -------------------------------------------------------------------------------- /tests/test_chat_messages.py: -------------------------------------------------------------------------------- 1 | import httpx 2 | import pytest 3 | 4 | from cozepy import AsyncCoze, AsyncTokenAuth, Coze, Message, TokenAuth 5 | from cozepy.util import random_hex 6 | from tests.test_util import logid_key 7 | 8 | 9 | def mock_chat_messages_list(respx_mock): 10 | logid = random_hex(10) 11 | msg = Message.build_user_question_text("hi") 12 | msg2 = Message.build_user_question_text("hey") 13 | respx_mock.get("/v3/chat/message/list").mock( 14 | httpx.Response( 15 | 200, 16 | json={"data": [msg.model_dump(), msg2.model_dump()]}, 17 | headers={logid_key(): logid}, 18 | ) 19 | ) 20 | return msg, msg2, logid 21 | 22 | 23 | @pytest.mark.respx(base_url="https://api.coze.com") 24 | class TestSyncChatMessages: 25 | def test_sync_chat_messages_list(self, respx_mock): 26 | coze = Coze(auth=TokenAuth(token="token")) 27 | 28 | msg, msg2, logid = mock_chat_messages_list(respx_mock) 29 | 30 | res = coze.chat.messages.list(conversation_id="conversation id", chat_id="chat id") 31 | assert res 32 | assert res.response.logid is not None 33 | assert res.response.logid == logid 34 | assert res[0].content == msg.content 35 | assert res[1].content == msg2.content 36 | 37 | 38 | @pytest.mark.respx(base_url="https://api.coze.com") 39 | @pytest.mark.asyncio 40 | class TestAsyncChatMessages: 41 | async def test_async_chat_messages_list(self, respx_mock): 42 | coze = AsyncCoze(auth=AsyncTokenAuth(token="token")) 43 | 44 | msg, msg2, logid = mock_chat_messages_list(respx_mock) 45 | 46 | res = await coze.chat.messages.list(conversation_id="conversation id", chat_id="chat id") 47 | assert res 48 | assert res.response.logid is not None 49 | assert res.response.logid == logid 50 | assert res[0].content == msg.content 51 | assert res[1].content == msg2.content 52 | -------------------------------------------------------------------------------- /tests/test_datasets_images.py: -------------------------------------------------------------------------------- 1 | import httpx 2 | import pytest 3 | 4 | from cozepy import ( 5 | AsyncCoze, 6 | AsyncTokenAuth, 7 | Coze, 8 | TokenAuth, 9 | ) 10 | from cozepy.datasets.documents import DocumentSourceType 11 | from cozepy.datasets.images import Photo, PhotoStatus 12 | from cozepy.util import random_hex 13 | from tests.test_util import logid_key 14 | 15 | 16 | def mock_update_datasets_images(respx_mock): 17 | dataset_id = random_hex(10) 18 | document_id = random_hex(10) 19 | logid = random_hex(10) 20 | respx_mock.put(f"/v1/datasets/{dataset_id}/images/{document_id}").mock( 21 | httpx.Response( 22 | 200, 23 | headers={logid_key(): logid}, 24 | json={"data": None}, 25 | ) 26 | ) 27 | return dataset_id, document_id, logid 28 | 29 | 30 | def mock_list_dataset_images(respx_mock, dataset_id, total_count, page): 31 | respx_mock.get( 32 | f"https://api.coze.com/v1/datasets/{dataset_id}/images", 33 | params={ 34 | "page_num": page, 35 | }, 36 | ).mock( 37 | httpx.Response( 38 | 200, 39 | headers={logid_key(): "logid"}, 40 | json={ 41 | "data": { 42 | "photo_infos": [ 43 | Photo( 44 | document_id=f"id_{page}", 45 | status=PhotoStatus.COMPLETED, 46 | source_type=DocumentSourceType.UPLOAD_FILE_ID, 47 | ).model_dump() 48 | ], 49 | "total_count": total_count, 50 | } 51 | }, 52 | ) 53 | ) 54 | 55 | 56 | @pytest.mark.respx(base_url="https://api.coze.com") 57 | class TestSyncDatasetsImages: 58 | def test_sync_datasets_images_update(self, respx_mock): 59 | coze = Coze(auth=TokenAuth(token="token")) 60 | 61 | dataset_id, document_id, mock_logid = mock_update_datasets_images(respx_mock) 62 | 63 | res = coze.datasets.images.update(dataset_id=dataset_id, document_id=document_id, caption="caption") 64 | assert res 65 | assert res.response.logid == mock_logid 66 | 67 | def test_sync_datasets_images_list(self, respx_mock): 68 | coze = Coze(auth=TokenAuth(token="token")) 69 | 70 | dataset_id = random_hex(10) 71 | total = 10 72 | size = 1 73 | for idx in range(total): 74 | mock_list_dataset_images(respx_mock, dataset_id, total_count=total, page=idx + 1) 75 | 76 | # no iter 77 | resp = coze.datasets.images.list(dataset_id=dataset_id, page_num=1, page_size=1) 78 | assert resp 79 | assert resp.has_more is True 80 | 81 | # iter dataset 82 | total_result = 0 83 | for dataset in resp: 84 | total_result += 1 85 | assert dataset 86 | assert dataset.document_id == f"id_{total_result}" 87 | assert total_result == total 88 | 89 | # iter page 90 | total_result = 0 91 | for page in resp.iter_pages(): 92 | total_result += 1 93 | assert page 94 | assert page.has_more == (total_result < total) 95 | assert len(page.items) == size 96 | dataset = page.items[0] 97 | assert dataset.document_id == f"id_{total_result}" 98 | assert total_result == total 99 | 100 | 101 | @pytest.mark.respx(base_url="https://api.coze.com") 102 | @pytest.mark.asyncio 103 | class TestAsyncDatasetsDocuments: 104 | async def test_sync_datasets_images_update(self, respx_mock): 105 | coze = AsyncCoze(auth=AsyncTokenAuth(token="token")) 106 | 107 | dataset_id, document_id, mock_logid = mock_update_datasets_images(respx_mock) 108 | 109 | res = await coze.datasets.images.update(dataset_id=dataset_id, document_id=document_id, caption="caption") 110 | assert res 111 | assert res.response.logid == mock_logid 112 | 113 | async def test_sync_datasets_images_list(self, respx_mock): 114 | coze = AsyncCoze(auth=AsyncTokenAuth(token="token")) 115 | 116 | dataset_id = random_hex(10) 117 | total = 10 118 | size = 1 119 | for idx in range(total): 120 | mock_list_dataset_images(respx_mock, dataset_id, total_count=total, page=idx + 1) 121 | 122 | # no iter 123 | resp = await coze.datasets.images.list(dataset_id=dataset_id, page_num=1, page_size=1) 124 | assert resp 125 | assert resp.has_more is True 126 | 127 | # iter dataset 128 | total_result = 0 129 | async for dataset in resp: 130 | total_result += 1 131 | assert dataset 132 | assert dataset.document_id == f"id_{total_result}" 133 | assert total_result == total 134 | 135 | # iter page 136 | total_result = 0 137 | async for page in resp.iter_pages(): 138 | total_result += 1 139 | assert page 140 | assert page.has_more == (total_result < total) 141 | assert len(page.items) == size 142 | dataset = page.items[0] 143 | assert dataset.document_id == f"id_{total_result}" 144 | assert total_result == total 145 | -------------------------------------------------------------------------------- /tests/test_exception.py: -------------------------------------------------------------------------------- 1 | from cozepy import CozeAPIError, CozeInvalidEventError, CozePKCEAuthError, CozePKCEAuthErrorType 2 | 3 | 4 | def test_coze_error(): 5 | err = CozeAPIError(1, "msg", "logid") 6 | assert err.code == 1 7 | assert err.msg == "msg" 8 | assert err.logid == "logid" 9 | assert str(err) == "code: 1, msg: msg, logid: logid" 10 | 11 | err = CozeAPIError(None, "msg", "logid") 12 | assert err.code is None 13 | assert err.msg == "msg" 14 | assert err.logid == "logid" 15 | assert str(err) == "msg: msg, logid: logid" 16 | 17 | err = CozePKCEAuthError(CozePKCEAuthErrorType.AUTHORIZATION_PENDING) 18 | assert err.error == "authorization_pending" 19 | 20 | err = CozeInvalidEventError("event", "xxx", "logid") 21 | assert str(err) == "invalid event, field: event, data: xxx, logid: logid" 22 | 23 | err = CozeInvalidEventError("", "xxx", "logid") 24 | assert str(err) == "invalid event, data: xxx, logid: logid" 25 | -------------------------------------------------------------------------------- /tests/test_file.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from unittest.mock import mock_open, patch 3 | 4 | import httpx 5 | import pytest 6 | 7 | from cozepy import AsyncCoze, AsyncTokenAuth, Coze, File, TokenAuth 8 | from cozepy.files import _try_fix_file 9 | from cozepy.util import random_hex 10 | from tests.test_util import logid_key 11 | 12 | 13 | def mock_upload_files(respx_mock): 14 | file = File(id="1", bytes=2, created_at=3, file_name="name") 15 | file._raw_response = httpx.Response(200, json={"data": file.model_dump()}, headers={logid_key(): random_hex(10)}) 16 | respx_mock.post("/v1/files/upload").mock(file._raw_response) 17 | return file 18 | 19 | 20 | def mock_retrieve_files(respx_mock): 21 | file = File(id="1", bytes=2, created_at=3, file_name="name") 22 | file._raw_response = httpx.Response(200, json={"data": file.model_dump()}, headers={logid_key(): random_hex(10)}) 23 | respx_mock.get("/v1/files/retrieve").mock(file._raw_response) 24 | return file 25 | 26 | 27 | def test_fix_file(): 28 | path = __file__ 29 | 30 | # raw str 31 | f = _try_fix_file(path) 32 | assert f.name == path 33 | 34 | # path 35 | f = _try_fix_file(Path(path)) 36 | assert f.name == path 37 | 38 | # tuple 39 | f = _try_fix_file(("name", "content")) 40 | assert isinstance(f, tuple) 41 | assert f[0] == "name" 42 | assert f[1] == "content" 43 | 44 | 45 | @pytest.mark.respx(base_url="https://api.coze.com") 46 | class TestSyncFiles: 47 | def test_sync_files_upload(self, respx_mock): 48 | coze = Coze(auth=TokenAuth(token="token")) 49 | 50 | mock_file = mock_upload_files(respx_mock) 51 | 52 | with patch("builtins.open", mock_open(read_data="data")): 53 | file = coze.files.upload(file=Path(__file__)) 54 | assert file 55 | assert file.response.logid == mock_file.response.logid 56 | assert file.file_name == "name" 57 | 58 | def test_sync_files_retrieve(self, respx_mock): 59 | coze = Coze(auth=TokenAuth(token="token")) 60 | 61 | mock_file = mock_retrieve_files(respx_mock) 62 | 63 | file = coze.files.retrieve(file_id="id") 64 | assert file 65 | assert file.response.logid == mock_file.response.logid 66 | assert file.file_name == "name" 67 | 68 | 69 | @pytest.mark.respx(base_url="https://api.coze.com") 70 | @pytest.mark.asyncio 71 | class TestAsyncFiles: 72 | async def test_async_files_upload(self, respx_mock): 73 | coze = AsyncCoze(auth=AsyncTokenAuth(token="token")) 74 | 75 | mock_file = mock_upload_files(respx_mock) 76 | 77 | with patch("builtins.open", mock_open(read_data="data")): 78 | file = await coze.files.upload(file=Path(__file__)) 79 | assert file 80 | assert file.response.logid == mock_file.response.logid 81 | assert file.file_name == "name" 82 | 83 | async def test_async_files_retrieve(self, respx_mock): 84 | coze = AsyncCoze(auth=AsyncTokenAuth(token="token")) 85 | 86 | mock_file = mock_retrieve_files(respx_mock) 87 | 88 | file = await coze.files.retrieve(file_id="id") 89 | assert file 90 | assert file.response.logid == mock_file.response.logid 91 | assert file.file_name == "name" 92 | -------------------------------------------------------------------------------- /tests/test_log.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | import pytest 4 | 5 | from cozepy import setup_logging 6 | 7 | 8 | def test_log(): 9 | with pytest.raises(ValueError): 10 | setup_logging(123) 11 | 12 | setup_logging(logging.DEBUG) 13 | -------------------------------------------------------------------------------- /tests/test_model.py: -------------------------------------------------------------------------------- 1 | from typing import Dict 2 | 3 | import httpx 4 | import pytest 5 | 6 | from cozepy import AsyncStream, CozeInvalidEventError, ListResponse, Stream 7 | from cozepy.util import anext 8 | 9 | from .test_util import mock_response, to_async_iterator 10 | 11 | 12 | def mock_sync_handler(d: Dict[str, str], logid: str): 13 | return d 14 | 15 | 16 | async def mock_async_handler(d: Dict[str, str], logid: str): 17 | return d 18 | 19 | 20 | class TestSyncStream: 21 | def test_sync_stream_invalid_event(self): 22 | items = ["event:x"] 23 | response = mock_response() 24 | s = Stream(response._raw_response, iter(items), ["field"], mock_sync_handler) 25 | with pytest.raises(CozeInvalidEventError, match="invalid event, data: event:x, logid: " + response.logid): 26 | next(s) 27 | 28 | def test_stream_invalid_field(self): 29 | items = ["event:x1", "event:x2"] 30 | response = mock_response() 31 | s = Stream(response._raw_response, iter(items), ["event", "second"], mock_sync_handler) 32 | 33 | with pytest.raises( 34 | CozeInvalidEventError, match="invalid event, field: event, data: event:x2, logid: " + response.logid 35 | ): 36 | next(s) 37 | 38 | 39 | @pytest.mark.asyncio 40 | class TestAsyncStream: 41 | async def test_asynv_stream_invalid_event(self): 42 | response = mock_response() 43 | items = ["event:x"] 44 | s = AsyncStream(to_async_iterator(items), ["field"], mock_async_handler, response._raw_response) 45 | 46 | with pytest.raises(CozeInvalidEventError, match="invalid event, data: event:x, logid: " + response.logid): 47 | await anext(s) 48 | 49 | async def test_stream_invalid_field(self): 50 | response = mock_response() 51 | items = ["event:x1", "event:x2"] 52 | s = AsyncStream(to_async_iterator(items), ["event", "second"], mock_async_handler, response._raw_response) 53 | 54 | with pytest.raises( 55 | CozeInvalidEventError, match="invalid event, field: event, data: event:x2, logid: " + response.logid 56 | ): 57 | await anext(s) 58 | 59 | 60 | class TestListResponse: 61 | def test_slice(self): 62 | res = ListResponse(httpx.Response(200), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) 63 | # len 64 | assert len(res) == 10 65 | # iter 66 | assert list(res) == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 67 | # contains 68 | assert 1 in res 69 | assert 11 not in res 70 | # reversed 71 | assert list(reversed(res)) == [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] 72 | # get item 73 | assert res[0] == 1 74 | # get item with slice 75 | assert res[1:3] == [2, 3] 76 | # get item with slice and step 77 | assert res[1:3:2] == [2] 78 | assert res[1:10:3] == [2, 5, 8] 79 | # set item 80 | assert list(res) == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 81 | res[1:3] = [11, 12] 82 | assert list(res) == [1, 11, 12, 4, 5, 6, 7, 8, 9, 10] 83 | # set item with slice 84 | res[1:3] = [13, 14, 15] 85 | assert list(res) == [1, 13, 14, 15, 4, 5, 6, 7, 8, 9, 10] 86 | # set item with slice and step 87 | res[1:10:3] = [16, 17, 18] 88 | assert list(res) == [1, 16, 14, 15, 17, 5, 6, 18, 8, 9, 10] 89 | # del item 90 | del res[1] 91 | assert list(res) == [1, 14, 15, 17, 5, 6, 18, 8, 9, 10] 92 | # del item with slice 93 | del res[1:3] 94 | assert list(res) == [1, 17, 5, 6, 18, 8, 9, 10] 95 | # del item with slice and step 96 | del res[1:10:3] 97 | assert list(res) == [1, 5, 6, 8, 9] 98 | -------------------------------------------------------------------------------- /tests/test_request.py: -------------------------------------------------------------------------------- 1 | import httpx 2 | import pytest 3 | 4 | from cozepy import CozeAPIError, CozePKCEAuthError 5 | from cozepy.model import CozeModel 6 | from cozepy.request import Requester 7 | from tests.test_util import logid_key 8 | 9 | 10 | class ModelForTest(CozeModel): 11 | id: str 12 | 13 | 14 | class DebugModelForTest(CozeModel): 15 | debug_url: str 16 | data: str 17 | 18 | 19 | @pytest.mark.respx(base_url="https://api.coze.com") 20 | class TestRequester: 21 | def test_code_msg(self, respx_mock): 22 | respx_mock.post("/api/test").mock( 23 | httpx.Response( 24 | 200, 25 | json={"code": 100, "msg": "request failed"}, 26 | headers={logid_key(): "mock-logid"}, 27 | ) 28 | ) 29 | 30 | with pytest.raises(CozeAPIError, match="code: 100, msg: request failed, logid: mock-logid"): 31 | Requester().request("post", "https://api.coze.com/api/test", False, ModelForTest) 32 | 33 | def test_auth_slow_down(self, respx_mock): 34 | respx_mock.post("/api/test").mock( 35 | httpx.Response( 36 | 200, 37 | json={ 38 | "error_code": "slow_down", 39 | }, 40 | headers={logid_key(): "mock-logid"}, 41 | ) 42 | ) 43 | 44 | with pytest.raises(CozePKCEAuthError, match="pkce auth error: slow_down"): 45 | Requester().request("post", "https://api.coze.com/api/test", False, ModelForTest) 46 | 47 | def test_error_message(self, respx_mock): 48 | respx_mock.post("/api/test").mock( 49 | httpx.Response( 50 | 200, 51 | json={ 52 | "error_message": "error_message", 53 | }, 54 | headers={logid_key(): "mock-logid"}, 55 | ) 56 | ) 57 | 58 | with pytest.raises(CozeAPIError, match="msg: error_message, logid: mock-logid"): 59 | Requester().request("post", "https://api.coze.com/api/test", False, ModelForTest) 60 | 61 | def test_debug_url(self, respx_mock): 62 | respx_mock.post("/api/test").mock( 63 | httpx.Response( 64 | 200, 65 | json={ 66 | "debug_url": "debug_url", 67 | "data": "data", 68 | }, 69 | headers={logid_key(): "mock-logid"}, 70 | ) 71 | ) 72 | 73 | Requester().request("post", "https://api.coze.com/api/test", False, DebugModelForTest) 74 | 75 | 76 | @pytest.mark.respx(base_url="https://api.coze.com") 77 | @pytest.mark.asyncio 78 | class TestAsyncRequester: 79 | async def test_code_msg(self, respx_mock): 80 | respx_mock.post("/api/test").mock( 81 | httpx.Response(200, json={"code": 100, "msg": "request failed"}, headers={logid_key(): "mock-logid"}) 82 | ) 83 | 84 | with pytest.raises(CozeAPIError, match="code: 100, msg: request failed, logid: mock-logid"): 85 | await Requester().arequest("post", "https://api.coze.com/api/test", False, ModelForTest) 86 | 87 | async def test_auth_slow_down(self, respx_mock): 88 | respx_mock.post("/api/test").mock( 89 | httpx.Response( 90 | 200, 91 | json={ 92 | "error_code": "slow_down", 93 | }, 94 | headers={logid_key(): "mock-logid"}, 95 | ) 96 | ) 97 | 98 | with pytest.raises(CozePKCEAuthError, match="pkce auth error: slow_down"): 99 | await Requester().arequest("post", "https://api.coze.com/api/test", False, ModelForTest) 100 | 101 | async def test_error_message(self, respx_mock): 102 | respx_mock.post("/api/test").mock( 103 | httpx.Response( 104 | 200, 105 | json={ 106 | "error_message": "error_message", 107 | }, 108 | headers={logid_key(): "mock-logid"}, 109 | ) 110 | ) 111 | 112 | with pytest.raises(CozeAPIError, match="msg: error_message, logid: mock-logid"): 113 | await Requester().arequest("post", "https://api.coze.com/api/test", False, ModelForTest) 114 | 115 | async def test_debug_url(self, respx_mock): 116 | respx_mock.post("/api/test").mock( 117 | httpx.Response( 118 | 200, 119 | json={ 120 | "debug_url": "debug_url", 121 | "data": "data", 122 | }, 123 | headers={logid_key(): "mock-logid"}, 124 | ) 125 | ) 126 | 127 | await Requester().arequest("post", "https://api.coze.com/api/test", False, DebugModelForTest) 128 | -------------------------------------------------------------------------------- /tests/test_template.py: -------------------------------------------------------------------------------- 1 | import httpx 2 | import pytest 3 | 4 | from cozepy import AsyncCoze, AsyncTokenAuth, Coze, TemplateDuplicateResp, TokenAuth 5 | from cozepy.util import random_hex 6 | 7 | 8 | def mock_template_duplicate(respx_mock, entity_id, entity_type): 9 | respx_mock.post("/v1/templates/template_id/duplicate").mock( 10 | httpx.Response( 11 | 200, 12 | json={ 13 | "data": TemplateDuplicateResp( 14 | entity_id=entity_id, 15 | entity_type=entity_type, 16 | ).model_dump() 17 | }, 18 | ) 19 | ) 20 | 21 | 22 | @pytest.mark.respx(base_url="https://api.coze.com") 23 | class TestTemplate: 24 | def test_sync_template_duplicate(self, respx_mock): 25 | coze = Coze(auth=TokenAuth(token="token")) 26 | 27 | entity_id = random_hex(10) 28 | entity_type = "agent" 29 | 30 | mock_template_duplicate(respx_mock, entity_id, entity_type) 31 | 32 | res = coze.templates.duplicate(template_id="template_id", workspace_id="workspace_id", name="name") 33 | assert res 34 | assert res.entity_id == entity_id 35 | assert res.entity_type == entity_type 36 | 37 | 38 | @pytest.mark.respx(base_url="https://api.coze.com") 39 | @pytest.mark.asyncio 40 | class TestAsyncTemplate: 41 | async def test_async_template_duplicate(self, respx_mock): 42 | coze = AsyncCoze(auth=AsyncTokenAuth(token="token")) 43 | 44 | entity_id = random_hex(10) 45 | entity_type = "agent" 46 | 47 | mock_template_duplicate(respx_mock, entity_id, entity_type) 48 | 49 | res = await coze.templates.duplicate(template_id="template_id", workspace_id="workspace_id", name="name") 50 | assert res 51 | assert res.entity_id == entity_id 52 | assert res.entity_type == entity_type 53 | -------------------------------------------------------------------------------- /tests/test_users.py: -------------------------------------------------------------------------------- 1 | import httpx 2 | import pytest 3 | 4 | from cozepy import AsyncCoze, AsyncTokenAuth, Coze, TokenAuth, User 5 | from cozepy.util import random_hex 6 | from tests.test_util import logid_key 7 | 8 | 9 | def mock_retrieve_users_me( 10 | respx_mock, 11 | ) -> User: 12 | user = User( 13 | user_id="user_id", 14 | user_name=random_hex(10), 15 | nick_name=random_hex(10), 16 | avatar_url=random_hex(10), 17 | ) 18 | user._raw_response = httpx.Response( 19 | 200, 20 | json={"data": user.model_dump()}, 21 | headers={logid_key(): random_hex(10)}, 22 | ) 23 | respx_mock.get("/v1/users/me").mock(user._raw_response) 24 | return user 25 | 26 | 27 | @pytest.mark.respx(base_url="https://api.coze.com") 28 | class TestSyncUsers: 29 | def test_sync_users_retrieve_me(self, respx_mock): 30 | coze = Coze(auth=TokenAuth(token="token")) 31 | 32 | mock_user = mock_retrieve_users_me(respx_mock) 33 | 34 | user = coze.users.me() 35 | assert user 36 | assert user.response.logid == mock_user.response.logid 37 | assert user.user_id == mock_user.user_id 38 | 39 | 40 | @pytest.mark.respx(base_url="https://api.coze.com") 41 | @pytest.mark.asyncio 42 | class TestAsyncUsers: 43 | async def test_async_users_retrieve_me(self, respx_mock): 44 | coze = AsyncCoze(auth=AsyncTokenAuth(token="token")) 45 | 46 | mock_user = mock_retrieve_users_me(respx_mock) 47 | 48 | user = await coze.users.me() 49 | assert user 50 | assert user.response.logid == mock_user.response.logid 51 | assert user.user_id == mock_user.user_id 52 | -------------------------------------------------------------------------------- /tests/test_util.py: -------------------------------------------------------------------------------- 1 | import os 2 | from typing import Any, AsyncIterator, List, Optional 3 | 4 | import httpx 5 | import pytest 6 | 7 | from cozepy import COZE_COM_BASE_URL 8 | from cozepy.model import HTTPResponse 9 | from cozepy.util import anext, base64_encode_string, random_hex, remove_url_trailing_slash 10 | 11 | 12 | class ListAsyncIterator: 13 | def __init__(self, lst: List[Any]): 14 | self.lst = lst 15 | self.index = 0 16 | 17 | def __aiter__(self): 18 | return self 19 | 20 | async def __anext__(self): 21 | if self.index >= len(self.lst): 22 | raise StopAsyncIteration 23 | value = self.lst[self.index] 24 | self.index += 1 25 | return value 26 | 27 | 28 | def to_async_iterator(lst: List[Any]) -> AsyncIterator: 29 | return ListAsyncIterator(lst) 30 | 31 | 32 | def test_base64_encode_string(): 33 | assert "aGk=" == base64_encode_string("hi") 34 | 35 | 36 | @pytest.mark.asyncio 37 | async def test_anext(): 38 | async_iterator = to_async_iterator(["mock"]) 39 | assert "mock" == await anext(async_iterator) 40 | 41 | with pytest.raises(StopAsyncIteration): 42 | await anext(async_iterator) 43 | 44 | 45 | def test_remove_url_trailing_slash(): 46 | assert remove_url_trailing_slash(None) is None 47 | assert remove_url_trailing_slash(COZE_COM_BASE_URL) == COZE_COM_BASE_URL 48 | assert remove_url_trailing_slash(COZE_COM_BASE_URL + "/") == COZE_COM_BASE_URL 49 | assert remove_url_trailing_slash(COZE_COM_BASE_URL + "//") == COZE_COM_BASE_URL 50 | assert remove_url_trailing_slash(COZE_COM_BASE_URL + "///") == COZE_COM_BASE_URL 51 | 52 | 53 | def logid_key(): 54 | return "x-tt-logid" 55 | 56 | 57 | def read_file(path: str): 58 | current_dir = os.path.dirname(os.path.abspath(__file__)) 59 | file_path = os.path.join(current_dir, path) 60 | 61 | with open(file_path, "r", encoding="utf-8") as file: 62 | content = file.read() 63 | return content 64 | 65 | 66 | def mock_response(content: Optional[str] = None) -> HTTPResponse: 67 | if content: 68 | return HTTPResponse(httpx.Response(200, content=content, headers={logid_key(): random_hex(10)})) 69 | return HTTPResponse(httpx.Response(200, headers={logid_key(): random_hex(10)})) 70 | -------------------------------------------------------------------------------- /tests/test_version.py: -------------------------------------------------------------------------------- 1 | from cozepy import VERSION 2 | from cozepy.version import user_agent 3 | 4 | 5 | def test_user_agent(): 6 | res = user_agent() 7 | assert f"cozepy/{VERSION}" in res 8 | assert "python" in res 9 | -------------------------------------------------------------------------------- /tests/test_workflow_run_history.py: -------------------------------------------------------------------------------- 1 | from cozepy import WorkflowRunHistory 2 | 3 | 4 | def test_empty_str_to_zero(): 5 | data = { 6 | "logid": "logid", 7 | "debug_url": "https://www.coze.cn/work_flow", 8 | "execute_id": "123", 9 | "bot_id": "0", 10 | "update_time": 1744960910, 11 | "token": "0", 12 | "execute_status": "Running", 13 | "connector_uid": "123", 14 | "connector_id": "1024", 15 | "create_time": 1744960910, 16 | "run_mode": 2, 17 | "output": '{"Output":"null"}', 18 | "cost": "0.00000", 19 | "error_code": "", 20 | } 21 | history = WorkflowRunHistory(**data) 22 | assert history.error_code == 0 23 | -------------------------------------------------------------------------------- /tests/test_workspaces.py: -------------------------------------------------------------------------------- 1 | import httpx 2 | import pytest 3 | 4 | from cozepy import AsyncCoze, AsyncTokenAuth, Coze, TokenAuth, Workspace, WorkspaceRoleType, WorkspaceType 5 | 6 | 7 | def mock_list_workspaces(respx_mock, total_count, page): 8 | respx_mock.get( 9 | "/v1/workspaces", 10 | params={ 11 | "page_num": page, 12 | }, 13 | ).mock( 14 | httpx.Response( 15 | 200, 16 | json={ 17 | "data": { 18 | "total_count": total_count, 19 | "workspaces": [ 20 | Workspace( 21 | id=f"id_{page}" if page else "id", 22 | name="name", 23 | icon_url="icon_url", 24 | role_type=WorkspaceRoleType.ADMIN, 25 | workspace_type=WorkspaceType.PERSONAL, 26 | ).model_dump() 27 | ], 28 | } 29 | }, 30 | ) 31 | ) 32 | 33 | 34 | @pytest.mark.respx(base_url="https://api.coze.com") 35 | class TestSyncWorkspaces: 36 | def test_sync_workspaces_list(self, respx_mock): 37 | coze = Coze(auth=TokenAuth(token="token")) 38 | 39 | total = 10 40 | size = 1 41 | for idx in range(total): 42 | mock_list_workspaces(respx_mock, total_count=total, page=idx + 1) 43 | 44 | # no iter 45 | resp = coze.workspaces.list(page_num=1, page_size=1) 46 | assert resp 47 | assert resp.has_more is True 48 | 49 | # iter 50 | total_result = 0 51 | for workspace in resp: 52 | total_result += 1 53 | assert workspace 54 | assert workspace.id == f"id_{total_result}" 55 | assert total_result == total 56 | 57 | # iter page 58 | total_result = 0 59 | for page in resp.iter_pages(): 60 | total_result += 1 61 | assert page 62 | assert page.has_more == (total_result < total) 63 | assert len(page.items) == size 64 | workspace = page.items[0] 65 | assert workspace.id == f"id_{total_result}" 66 | assert total_result == total 67 | 68 | 69 | @pytest.mark.respx(base_url="https://api.coze.com") 70 | @pytest.mark.asyncio 71 | class TestAsyncWorkspaces: 72 | async def test_async_workspaces_list(self, respx_mock): 73 | coze = AsyncCoze(auth=AsyncTokenAuth(token="token")) 74 | 75 | total = 10 76 | size = 1 77 | for idx in range(total): 78 | mock_list_workspaces(respx_mock, total_count=total, page=idx + 1) 79 | 80 | # no iter 81 | resp = await coze.workspaces.list(page_num=1, page_size=1) 82 | assert resp 83 | assert resp.has_more is True 84 | 85 | # iter 86 | total_result = 0 87 | async for workspace in resp: 88 | total_result += 1 89 | assert workspace 90 | assert workspace.id == f"id_{total_result}" 91 | assert total_result == total 92 | 93 | # iter page 94 | total_result = 0 95 | async for page in resp.iter_pages(): 96 | total_result += 1 97 | assert page 98 | assert page.has_more == (total_result < total) 99 | assert len(page.items) == size 100 | workspace = page.items[0] 101 | assert workspace.id == f"id_{total_result}" 102 | assert total_result == total 103 | -------------------------------------------------------------------------------- /tests/testdata/chat_error_resp.txt: -------------------------------------------------------------------------------- 1 | event:error 2 | data:{} -------------------------------------------------------------------------------- /tests/testdata/chat_failed_resp.txt: -------------------------------------------------------------------------------- 1 | event:conversation.chat.failed 2 | data:{"id":"7390342853760696354","conversation_id":"7390331532575195148","bot_id":"7374724495711502387","completed_at":1720698285,"failed_at":1720698286,"last_error":{"code":5000,"msg":"event interval error"},"status":"failed","usage":{"token_count":0,"output_count":0,"input_count":0}} -------------------------------------------------------------------------------- /tests/testdata/chat_invalid_resp.txt: -------------------------------------------------------------------------------- 1 | event:invalid 2 | data:{} -------------------------------------------------------------------------------- /tests/testdata/chat_text_stream_resp.txt: -------------------------------------------------------------------------------- 1 | event:conversation.chat.created 2 | data:{"id":"7382159487131697202","conversation_id":"7381473525342978089","bot_id":"7379462189365198898","completed_at":1718792949,"last_error":{"code":0,"msg":""},"status":"created","usage":{"token_count":0,"output_count":0,"input_count":0}} 3 | 4 | event:conversation.chat.in_progress 5 | data:{"id":"7382159487131697202","conversation_id":"7381473525342978089","bot_id":"7379462189365198898","completed_at":1718792949,"last_error":{"code":0,"msg":""},"status":"in_progress","usage":{"token_count":0,"output_count":0,"input_count":0}} 6 | 7 | event:conversation.message.delta 8 | data:{"id":"7382159494123470858","conversation_id":"7381473525342978089","bot_id":"7379462189365198898","role":"assistant","type":"answer","content":"2","content_type":"text","chat_id":"7382159487131697202"} 9 | 10 | event:conversation.message.delta 11 | data:{"id":"7382159494123470858","conversation_id":"7381473525342978089","bot_id":"7379462189365198898","role":"assistant","type":"answer","content":"0","content_type":"text","chat_id":"7382159487131697202"} 12 | 13 | event:conversation.message.delta 14 | data:{"id":"7382159494123470858","conversation_id":"7381473525342978089","bot_id":"7379462189365198898","role":"assistant","type":"answer","content":"星期三","content_type":"text","chat_id":"7382159487131697202"} 15 | 16 | event:conversation.message.delta 17 | data:{"id":"7382159494123470858","conversation_id":"7381473525342978089","bot_id":"7379462189365198898","role":"assistant","type":"answer","content":"。","content_type":"text","chat_id":"7382159487131697202"} 18 | 19 | event:conversation.message.completed 20 | data:{"id":"7382159494123470858","conversation_id":"7381473525342978089","bot_id":"7379462189365198898","role":"assistant","type":"answer","content":"2024 年 10 月 1 日是星期三。","content_type":"text","chat_id":"7382159487131697202"} 21 | 22 | event:conversation.chat.completed 23 | data:{"id":"7382159487131697202","conversation_id":"7381473525342978089","bot_id":"7379462189365198898","completed_at":1718792949,"last_error":{"code":0,"msg":""},"status":"completed","usage":{"token_count":633,"output_count":19,"input_count":614}} 24 | 25 | event:done 26 | data:"[DONE]" -------------------------------------------------------------------------------- /tests/testdata/private_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCj1Mlf7zfg/kx4 3 | DHogPkN7gTkAYi7FM6TktFZFHDm8Zs1KiL6WrpU+UTqBiHHhlMVB3RiaJxWH40ia 4 | 9OWJvIpM5lCaMnzGNX/4wC+4Pxc3KNoUhijP6ofS4yI5xSpUyMrjl9q95ePBNmmP 5 | Tv+s4uTa2y0e1ZlDHwIWC8InZ5NX65RO+yIF+95gclFkANgp5l7aBHaLiSebYRJT 6 | aluZmS4ZUH06Y9LHkS+QvuvOPaQu3Y+xdgHnzEYtNn83tTmLCBAt2ZYcJi0WIeJZ 7 | acaLsi59N1LH+2ZFtMc6+l7qlB0i4m7Dko+9i9OGtBD4y6rMO85VKUAQTs862O3W 8 | KIsWsKXjAgMBAAECggEAAoxg5uxK9O1WTFg3OOw7QEDoUjHLXWPKQtP8sxNxrFjo 9 | yFcx1WQTdYRXHioasuikNn/Tc6vOyc/bXdnq/jzlXg/pbByaWEH/XwHhHgbNNJXb 10 | JhXfrVlv+zAkGXE9czVYILF1xIcgcKI9zhsYl0IXT1gxMmwO98XX0lisPcHY7IhV 11 | JqSGg9cpLi7agyu4E6xBnK8B7rlk34WOrQf7WElwZ+1bddqA2WLmlls5m3dcJ6IF 12 | kJAEMmHYlkpNBC5fhocui0enfVxDncVghZFMugmY6AtxY8kB2U5Fy1hFHi0Eu9Yg 13 | I9XDJD4S/vzpoKojeAVFr/iQkzTj/ObzeF6gaFWN0QKBgQDlM9l69oQX/p94jr9t 14 | 6U2G3BK2NJk/O2j1jcOYX7ud1erdRlfeGJwEpReYQ6Ug+cLc3n3cj8qWg2x2Yw8L 15 | 45bVuJPxfJ0KPWI03syb+llAsIY3MC70quNu4b9vDTNS6pN6F4trTvT0Woz0x4vo 16 | i3pz3u3NPnfL1I0EoPKobDf7bwKBgQC2/FbOpXTM7a1UHVgd2y1OKzpGcuC0eOKN 17 | /DO2P24CFCgAdySnzsfLYlIKoU8DYvEndyIVysZav6pNC5PJc0vpJ6Oqg3izXigw 18 | viM5CJhFVxPWrtyMcN02JNUSHNWOaiuCOlZIPQEgYCTUECjE/Xl1COonVS38mO+N 19 | FSF7Z3mSzQKBgEmX+2W7D7Dwpd284AR3m9gIg82TV/1wowPtT/d2DbThQfdopb// 20 | YOEw7UGLvtK2v3XRztHqLZ9kdYgRyHwFyKG5EW/Bll76VLUrMMGIge3+gCnqQ7l1 21 | wW8R9zi+IVOnVFEojDCZepeXF5llFSxG1Lutwedb/nUpO1pYH3IqxVLrAoGBAIVv 22 | MSXzhV7CmrhRxaXP5BOydgZVUwKHfD2pgVQOoPunExxzxSkRIqRvCAB0bJe9mLj8 23 | qMBXY5ldVqRkItqt1tcobrKyuFuj947DuA+o8tDtlKviSzWmP8lxxmY03I3DYgLO 24 | 44g95Apl0bVKK1CqvdzYKVeRR72BEH5CwG2qoP6pAoGAUpvD0LSVh171UwQFkT6K 25 | b2mWBz1LV7EYLg4bfmi7wRBUCeEuK16+PEJ63yYUg8cSGTZqOFyRbc4tNf2Ow8BL 26 | gpsiuY9Mn2TnbscpeK841s68IHx4l90Je4tbbjK4E/yv+vgARkiiWQbG0BZSkBjO 27 | qI39/arl6ZhTeQMv7TrpQ6Q= 28 | -----END PRIVATE KEY----- -------------------------------------------------------------------------------- /tests/testdata/workflow_run_invalid_stream_resp.txt: -------------------------------------------------------------------------------- 1 | id:0 2 | event:invalid 3 | data:{} -------------------------------------------------------------------------------- /tests/testdata/workflow_run_stream_resp.txt: -------------------------------------------------------------------------------- 1 | id: 0 2 | event: Message 3 | data: {"content":"msg","node_is_finish":false,"node_seq_id":"0","node_title":"Message"} 4 | 5 | id: 1 6 | event: Message 7 | data: {"content":"为","node_is_finish":false,"node_seq_id":"1","node_title":"Message"} 8 | 9 | id: 2 10 | event: Message 11 | data: {"content":"什么小明要带一把尺子去看电影因","node_is_finish":false,"node_seq_id":"2","node_title":"Message"} 12 | 13 | id: 3 14 | event: Message 15 | data: {"content":"为他听说电影很长,怕","node_is_finish":false,"node_seq_id":"3","node_title":"Message"} 16 | 17 | id: 4 18 | event: Message 19 | data: {"content":"坐不下!","node_is_finish":true,"node_seq_id":"4","node_title":"Message"} 20 | 21 | id: 5 22 | event: Message 23 | data: {"content":"{\"output\":\"为什么小明要带一把尺子去看电影?因为他听说电影很长,怕坐不下!\"}","cost":"0.00","node_is_finish":true,"node_seq_id":"0","node_title":"","token":0} 24 | 25 | id: 0 26 | event: Error 27 | data: {"error_code":4000,"error_message":"Request parameter error"} 28 | 29 | id: 0 30 | event: Message 31 | data: {"content":"请问你想查看哪个城市、哪一天的天气呢","content_type":"text","node_is_finish":true,"node_seq_id":"0","node_title":"问答"} 32 | 33 | id: 1 34 | event: Interrupt 35 | data: {"interrupt_data":{"data":"","event_id":"7404830425073352713/2769808280134765896","type":2},"node_title":"问答"} 36 | 37 | id: 6 38 | event: Done 39 | data: {} -------------------------------------------------------------------------------- /tests/testdata/workflows_chat_stream_resp.txt: -------------------------------------------------------------------------------- 1 | event: conversation.chat.created 2 | data: {"id":"id_1111","conversation_id":"conv_111111111","created_at":1735115982,"last_error":{"code":0,"msg":""},"status":"created","usage":{"token_count":0,"output_count":0,"input_count":0},"section_id":"conv_111111111"} 3 | 4 | event: conversation.chat.in_progress 5 | data: {"id":"id_1111","conversation_id":"conv_111111111","created_at":1735115982,"last_error":{"code":0,"msg":""},"status":"in_progress","usage":{"token_count":0,"output_count":0,"input_count":0},"section_id":"conv_111111111"} 6 | 7 | event: conversation.message.delta 8 | data: {"id":"id_2222","conversation_id":"conv_111111111","role":"assistant","type":"answer","content":"I","content_type":"text","chat_id":"id_1111","section_id":"conv_111111111"} 9 | 10 | event: conversation.message.delta 11 | data: {"id":"id_2222","conversation_id":"conv_111111111","role":"assistant","type":"answer","content":"'m fine, thank you. How about you","content_type":"text","chat_id":"id_1111","section_id":"conv_111111111"} 12 | 13 | event: conversation.message.delta 14 | data: {"id":"id_2222","conversation_id":"conv_111111111","role":"assistant","type":"answer","content":"?","content_type":"text","chat_id":"id_1111","section_id":"conv_111111111","created_at":1735115983} 15 | 16 | event: conversation.message.completed 17 | data: {"id":"id_2222","conversation_id":"conv_111111111","role":"assistant","type":"answer","content":"I'm fine, thank you. How about you?","content_type":"text","chat_id":"id_1111","section_id":"conv_111111111","created_at":1735115983} 18 | 19 | event: conversation.message.completed 20 | data: {"id":"id_3333","conversation_id":"conv_111111111","role":"assistant","type":"verbose","content":"{\"msg_type\":\"generate_answer_finish\",\"data\":\"{\\\"finish_reason\\\":0,\\\"FinData\\\":\\\"\\\"}\",\"from_module\":null,\"from_unit\":null}","content_type":"text","chat_id":"id_1111","section_id":"conv_111111111","created_at":1735115983,"updated_at":1735115983} 21 | 22 | event: conversation.chat.completed 23 | data: {"id":"id_1111","conversation_id":"conv_111111111","created_at":1735115982,"completed_at":1735115983,"last_error":{"code":0,"msg":""},"status":"completed","usage":{"token_count":32,"output_count":11,"input_count":21},"section_id":"conv_111111111"} 24 | 25 | event: done 26 | data: {"debug_url":"https://www.coze.cn/work_flow?execute_id=execute_1111\u0026space_id=space_111\u0026workflow_id=workflow_1111"} --------------------------------------------------------------------------------