chat_service.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. #! /usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. # vim:fenc=utf-8
  4. #
  5. import os
  6. import threading
  7. from typing import List, Dict, Optional
  8. from enum import Enum, auto
  9. from logging_service import logger
  10. import cozepy
  11. from cozepy import Coze, TokenAuth, Message, ChatStatus, MessageType, JWTOAuthApp, JWTAuth
  12. import time
  13. from openai import OpenAI
  14. COZE_API_TOKEN = os.getenv("COZE_API_TOKEN")
  15. COZE_CN_BASE_URL = 'https://api.coze.cn'
  16. VOLCENGINE_API_TOKEN = '5e275c38-44fd-415f-abcf-4b59f6377f72'
  17. VOLCENGINE_BASE_URL = "https://ark.cn-beijing.volces.com/api/v3"
  18. VOLCENGINE_MODEL_DEEPSEEK_V3 = "deepseek-v3-250324"
  19. VOLCENGINE_MODEL_DOUBAO_PRO_1_5 = 'ep-20250307150409-4blz9'
  20. VOLCENGINE_MODEL_DOUBAO_PRO_32K = 'ep-20250414202859-6nkz5'
  21. VOLCENGINE_MODEL_DOUBAO_1_5_VISION_PRO = 'ep-20250421193334-nz5wd'
  22. DEEPSEEK_API_TOKEN = 'sk-67daad8f424f4854bda7f1fed7ef220b'
  23. DEEPSEEK_BASE_URL = 'https://api.deepseek.com/'
  24. DEEPSEEK_CHAT_MODEL = 'deepseek-chat'
  25. VOLCENGINE_BOT_BASE_URL = "https://ark.cn-beijing.volces.com/api/v3/bots"
  26. VOLCENGINE_BOT_DEEPSEEK_V3_SEARCH = "bot-20250427173459-9h2xp"
  27. OPENAI_API_TOKEN = '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'
  28. OPENAI_BASE_URL = 'https://api.openai.com/v1'
  29. OPENAI_MODEL_GPT_4o = 'gpt-4o'
  30. OPENAI_MODEL_GPT_4o_mini = 'gpt-4o-mini'
  31. class ChatServiceType(Enum):
  32. OPENAI_COMPATIBLE = auto()
  33. COZE_CHAT = auto()
  34. class OpenAICompatible:
  35. @staticmethod
  36. def create_client(model_name, **kwargs):
  37. volcengine_models = [
  38. VOLCENGINE_MODEL_DOUBAO_PRO_32K,
  39. VOLCENGINE_MODEL_DOUBAO_PRO_1_5,
  40. VOLCENGINE_MODEL_DOUBAO_1_5_VISION_PRO,
  41. VOLCENGINE_MODEL_DEEPSEEK_V3
  42. ]
  43. deepseek_models = [
  44. DEEPSEEK_CHAT_MODEL,
  45. ]
  46. openai_models = [
  47. OPENAI_MODEL_GPT_4o_mini,
  48. OPENAI_MODEL_GPT_4o
  49. ]
  50. if model_name in volcengine_models:
  51. llm_client = OpenAI(api_key=VOLCENGINE_API_TOKEN, base_url=VOLCENGINE_BASE_URL, **kwargs)
  52. elif model_name in deepseek_models:
  53. llm_client = OpenAI(api_key=DEEPSEEK_API_TOKEN, base_url=DEEPSEEK_BASE_URL, **kwargs)
  54. elif model_name in openai_models:
  55. llm_client = OpenAI(api_key=OPENAI_API_TOKEN, base_url=OPENAI_BASE_URL, **kwargs)
  56. else:
  57. raise Exception("Unsupported model: %s" % model_name)
  58. return llm_client
  59. class CrossAccountJWTOAuthApp(JWTOAuthApp):
  60. def __init__(self, account_id: str, client_id: str, private_key: str, public_key_id: str, base_url):
  61. self.account_id = account_id
  62. super().__init__(client_id, private_key, public_key_id, base_url)
  63. def get_access_token(
  64. self, ttl: int = 900, scope: Optional[cozepy.Scope] = None, session_name: Optional[str] = None
  65. ) -> cozepy.OAuthToken:
  66. jwt_token = self._gen_jwt(self._public_key_id, self._private_key, 3600, session_name)
  67. url = f"{self._base_url}/api/permission/oauth2/account/{self.account_id}/token"
  68. headers = {"Authorization": f"Bearer {jwt_token}"}
  69. body = {
  70. "duration_seconds": ttl,
  71. "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
  72. "scope": scope.model_dump() if scope else None,
  73. }
  74. return self._requester.request("post", url, False, cozepy.OAuthToken, headers=headers, body=body)
  75. class CozeChat:
  76. def __init__(self, base_url: str, auth_token: Optional[str] = None, auth_app: Optional[JWTOAuthApp] = None):
  77. if not auth_token and not auth_app:
  78. raise ValueError("Either auth_token or auth_app must be provided.")
  79. self.thread = None
  80. self.thread_running = False
  81. self.last_token_fresh = 0
  82. if auth_token:
  83. self.coze = Coze(auth=TokenAuth(auth_token), base_url=base_url)
  84. else:
  85. self.auth_app = auth_app
  86. oauth_token = auth_app.get_access_token(ttl=12*3600)
  87. self.last_token_fresh = time.time()
  88. self.coze = Coze(auth=JWTAuth(oauth_app=auth_app), base_url=base_url)
  89. self.setup_token_refresh()
  90. def create(self, bot_id: str, user_id: str, messages: List, custom_variables: Dict):
  91. response = self.coze.chat.create_and_poll(
  92. bot_id=bot_id, user_id=user_id, additional_messages=messages,
  93. custom_variables=custom_variables)
  94. logger.debug("Coze response size: {}".format(len(response.messages)))
  95. if response.chat.status != ChatStatus.COMPLETED:
  96. logger.error("Coze chat not completed: {}".format(response.chat.status))
  97. return None
  98. final_response = None
  99. for message in response.messages:
  100. if message.type == MessageType.ANSWER:
  101. final_response = message.content
  102. return final_response
  103. def setup_token_refresh(self):
  104. self.thread = threading.Thread(target=self.refresh_token_loop)
  105. self.thread.start()
  106. self.thread_running = True
  107. def refresh_token_loop(self):
  108. while self.thread_running:
  109. if time.time() - self.last_token_fresh < 11*3600:
  110. time.sleep(1)
  111. continue
  112. if self.auth_app:
  113. self.auth_app.get_access_token(ttl=12*3600)
  114. self.last_token_fresh = time.time()
  115. def __del__(self):
  116. self.thread_running = False
  117. @staticmethod
  118. def get_oauth_app(client_id, private_key_path, public_key_id, base_url=None, account_id=None) -> JWTOAuthApp:
  119. if not base_url:
  120. base_url = COZE_CN_BASE_URL
  121. with open(private_key_path, "r") as f:
  122. private_key = f.read()
  123. if not account_id:
  124. jwt_oauth_app = JWTOAuthApp(
  125. client_id=str(client_id),
  126. private_key=private_key,
  127. public_key_id=public_key_id,
  128. base_url=base_url,
  129. )
  130. else:
  131. jwt_oauth_app = CrossAccountJWTOAuthApp(
  132. account_id=account_id,
  133. client_id=str(client_id),
  134. private_key=private_key,
  135. public_key_id=public_key_id,
  136. base_url=base_url,
  137. )
  138. return jwt_oauth_app
  139. if __name__ == '__main__':
  140. # Init the Coze client through the access_token.
  141. coze = Coze(auth=TokenAuth(token=COZE_API_TOKEN), base_url=COZE_CN_BASE_URL)
  142. # Create a bot instance in Coze, copy the last number from the web link as the bot's ID.
  143. bot_id = "7491250992952999973"
  144. # The user id identifies the identity of a user. Developers can use a custom business ID
  145. # or a random string.
  146. user_id = "dev_user"
  147. chat = coze.chat.create_and_poll(
  148. bot_id=bot_id,
  149. user_id=user_id,
  150. additional_messages=[Message.build_user_question_text("钱塘江边 樱花开得不错,推荐一个视频吧")],
  151. custom_variables={
  152. 'agent_name': '芳华',
  153. 'agent_age': '25',
  154. 'agent_region': '北京',
  155. 'name': '李明',
  156. 'preferred_nickname': '李叔',
  157. 'age': '70',
  158. 'last_interaction_interval': '12',
  159. 'current_time_period': '上午',
  160. 'if_first_interaction': 'False',
  161. 'if_active_greeting': 'False'
  162. }
  163. )
  164. for message in chat.messages:
  165. print(message, flush=True)
  166. if chat.chat.status == ChatStatus.COMPLETED:
  167. print("token usage:", chat.chat.usage.token_count)