第三方API包装源自此,感谢两位作者贡献:
https://github.com/KoushikNavuluri/Claude-API/pull/102
以下是第三方API包装,的代码:
import json
import os
import uuid
from enum import Enum
from typing import Dict, List, Optional, Union
import requests as req
import tzlocal
from curl_cffi import requests
class ContentType(Enum):
PDF = 'application/pdf'
TXT = 'text/plain'
CSV = 'text/csv'
OCTET_STREAM = 'application/octet-stream'
class Client:
BASE_URL = "https://claude.ai/api"
USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36'
def __init__(self, cookie: str):
self.cookie = cookie
self.organization_id = self._get_organization_id()
def _get_organization_id(self) -> str:
url = f"{self.BASE_URL}/organizations"
headers = self._get_headers()
response = requests.get(url, headers=headers, impersonate="chrome110")
response.raise_for_status()
return response.json()[0]["uuid"]
def _get_headers(self, extra_headers: Optional[Dict[str, str]] = None) -> Dict[str, str]:
headers = {
'User-Agent': self.USER_AGENT,
'Accept-Language': 'en-US,en;q=0.5',
'Referer': 'https://claude.ai/chats',
'Content-Type': 'application/json',
'Connection': 'keep-alive',
'Cookie': self.cookie,
}
if extra_headers:
headers.update(extra_headers)
return headers
@staticmethod
def _get_content_type(file_path: str) -> str:
extension = os.path.splitext(file_path)[-1].lower()
return (
ContentType[extension[1:].upper()].value
if extension[1:].upper() in ContentType.__members__
else ContentType.OCTET_STREAM.value
)
def list_all_conversations(self) -> List[Dict]:
url = f"{self.BASE_URL}/organizations/{self.organization_id}/chat_conversations"
response = requests.get(url, headers=self._get_headers(), impersonate="chrome110")
response.raise_for_status()
return response.json()
def send_message(
self,
prompt: str,
conversation_id: str,
attachment: Optional[str] = None,
timeout: int = 500,
print_stream: bool = False,
) -> str:
url = f"{self.BASE_URL}/organizations/{self.organization_id}/chat_conversations/{conversation_id}/completion"
attachments = [self.upload_attachment(attachment)] if attachment else []
payload = json.dumps(
{"prompt": prompt, "timezone": tzlocal.get_localzone_name(), "attachments": attachments}
)
headers = self._get_headers({
'Accept': 'text/event-stream, text/event-stream',
'Origin': 'https://claude.ai',
'DNT': '1',
'TE': 'trailers',
})
response = requests.post(
url,
headers=headers,
data=payload,
impersonate="chrome110",
timeout=timeout,
stream=True,
)
return self._process_stream_response(response, print_stream)
@staticmethod
def _process_stream_response(response, print_stream: bool) -> str:
gpt_response = []
for line in response.iter_lines():
if not line:
continue
decoded_line = line.decode('utf-8').strip()
if not decoded_line.startswith('data: '):
continue
try:
data = json.loads(decoded_line[6:])
if data['type'] == 'completion':
completion = data['completion']
gpt_response.append(completion)
if print_stream:
print(completion, end="", flush=True)
except json.JSONDecodeError:
continue
return "".join(gpt_response)
def delete_conversation(self, conversation_id: str) -> bool:
url = f"{self.BASE_URL}/organizations/{self.organization_id}/chat_conversations/{conversation_id}"
response = requests.delete(
url,
headers=self._get_headers(),
data=json.dumps(conversation_id),
impersonate="chrome110",
)
return response.status_code == 204
def chat_conversation_history(self, conversation_id: str) -> Dict:
url = f"{self.BASE_URL}/organizations/{self.organization_id}/chat_conversations/{conversation_id}"
response = requests.get(url, headers=self._get_headers(), impersonate="chrome110")
response.raise_for_status()
return response.json()
@staticmethod
def generate_uuid() -> str:
return str(uuid.uuid4())
def create_new_chat(self) -> Dict:
url = f"{self.BASE_URL}/organizations/{self.organization_id}/chat_conversations"
payload = json.dumps({"uuid": self.generate_uuid(), "name": ""})
response = requests.post(
url, headers=self._get_headers(), data=payload, impersonate="chrome110"
)
response.raise_for_status()
return response.json()
def reset_all(self) -> bool:
conversations = self.list_all_conversations()
for conversation in conversations:
self.delete_conversation(conversation["uuid"])
return True
def upload_attachment(self, file_path: str) -> Union[Dict, bool]:
if file_path.endswith('.txt'):
return self._process_text_file(file_path)
url = f'{self.BASE_URL}/convert_document'
file_name = os.path.basename(file_path)
content_type = self._get_content_type(file_path)
files = {
'file': (file_name, open(file_path, 'rb'), content_type),
'orgUuid': (None, self.organization_id),
}
response = req.post(url, headers=self._get_headers(), files=files)
response.raise_for_status()
return response.json()
@staticmethod
def _process_text_file(file_path: str) -> Dict:
file_name = os.path.basename(file_path)
file_size = os.path.getsize(file_path)
with open(file_path, 'r', encoding='utf-8') as file:
file_content = file.read()
return {
"file_name": file_name,
"file_type": ContentType.TXT.value,
"file_size": file_size,
"extracted_content": file_content,
}
def rename_chat(self, title: str, conversation_id: str) -> bool:
url = f"{self.BASE_URL}/rename_chat"
payload = json.dumps({
"organization_uuid": self.organization_id,
"conversation_uuid": conversation_id,
"title": title,
})
response = requests.post(
url, headers=self._get_headers(), data=payload, impersonate="chrome110"
)
return response.status_code == 200
以下是我基于他们,实现的http API
import os
from flask import Flask, request, jsonify
from claude_api import Client
import base64
app = Flask(__name__)
# 使用环境变量获取Claude AI cookie
cookie = os.environ.get('CLAUDE_COOKIE')
claude_client = Client(cookie)
@app.route('/send_message', methods=['POST'])
def send_message():
data = request.json
prompt = data.get('prompt')
conversation_id = data.get('conversation_id')
attachment = data.get('attachment')
if not prompt:
return jsonify({"error": "No prompt provided"}), 400
if not conversation_id:
conversation_id = claude_client.create_new_chat()['uuid']
attachment_path = None
if attachment:
# 解码Base64并保存为临时文件
file_content = base64.b64decode(attachment['content'])
attachment_path = f"temp_{attachment['filename']}"
with open(attachment_path, 'wb') as f:
f.write(file_content)
try:
response = claude_client.send_message(prompt, conversation_id, attachment=attachment_path)
return jsonify({"response": response, "conversation_id": conversation_id})
finally:
# 删除临时文件
if attachment_path and os.path.exists(attachment_path):
os.remove(attachment_path)
@app.route('/send_message_text', methods=['POST'])
def send_message_text():
data = request.json
prompt = data.get('prompt')
conversation_id = data.get('conversation_id')
if not prompt:
return "Error: No prompt provided", 400
if not conversation_id:
conversation_id = claude_client.create_new_chat()['uuid']
response = claude_client.send_message(prompt, conversation_id)
return response
@app.route('/list_conversations', methods=['GET'])
def list_conversations():
conversations = claude_client.list_all_conversations()
return jsonify(conversations)
@app.route('/create_conversation', methods=['POST'])
def create_conversation():
new_chat = claude_client.create_new_chat()
return jsonify(new_chat)
@app.route('/rename_conversation', methods=['POST'])
def rename_conversation():
data = request.json
conversation_id = data.get('conversation_id')
new_title = data.get('title')
if not conversation_id or not new_title:
return jsonify({"error": "Missing conversation_id or title"}), 400
success = claude_client.rename_chat(new_title, conversation_id)
return jsonify({"success": success})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5001)
如何使用
首先,你需要修改import,以引入Client类
如果遇到关于时区的报错
[2024-08-29 23:22:57,694] ERROR in app: Exception on /send_message_text [POST]
Traceback (most recent call last):
File "/home/likewendy/Desktop/claude.ai/venv/lib/python3.12/site-packages/flask/app.py", line 1473, in wsgi_app
response = self.full_dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/likewendy/Desktop/claude.ai/venv/lib/python3.12/site-packages/flask/app.py", line 882, in full_dispatch_request
rv = self.handle_user_exception(e)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/likewendy/Desktop/claude.ai/venv/lib/python3.12/site-packages/flask/app.py", line 880, in full_dispatch_request
rv = self.dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^
File "/home/likewendy/Desktop/claude.ai/venv/lib/python3.12/site-packages/flask/app.py", line 865, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args) # type: ignore[no-any-return]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/likewendy/Desktop/claude.ai/main.py", line 53, in send_message_text
response = claude_client.send_message(prompt, conversation_id)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/likewendy/Desktop/claude.ai/venv/lib/python3.12/site-packages/claude_api.py", line 74, in send_message
{"prompt": prompt, "timezone": tzlocal.get_localzone_name(), "attachments": attachments}
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/likewendy/Desktop/claude.ai/venv/lib/python3.12/site-packages/tzlocal/unix.py", line 209, in get_localzone_name
_cache_tz_name = _get_localzone_name()
^^^^^^^^^^^^^^^^^^^^^
File "/home/likewendy/Desktop/claude.ai/venv/lib/python3.12/site-packages/tzlocal/unix.py", line 159, in _get_localzone_name
raise zoneinfo.ZoneInfoNotFoundError(message)
zoneinfo._common.ZoneInfoNotFoundError: 'Multiple conflicting time zone configurations found:\n/etc/timezone: Asia/Beijing\n/etc/localtime is a symlink to: Asia/Shanghai\nFix the configuration, or set the time zone in a TZ environment variable.\n'
127.0.0.1 - - [29/Aug/2024 23:22:57] "POST /send_message_text HTTP/1.1" 500 -
^[[A^C(venv) likewendy@likewendy-PC:~/Desktop/claude.ai
则尝试
export TZ=Asia/Shangha
然后你需要
export CLAUDE_COOKIE="你的cookie“
下面是http API文档
Claude API HTTP服务文档
本文档概述了与Claude AI服务交互的API端点。
基础URL
所有端点都相对于:http://你的服务器地址:5001
认证
该服务使用环境变量CLAUDE_COOKIE
进行认证。确保在启动服务器之前设置此变量。
端点
1. 发送消息
向Claude发送消息并获取回复。
- URL:
/send_message
- 方法:
POST
- 内容类型:
application/json
请求体
{
"prompt": "你发送给Claude的消息",
"conversation_id": "可选的对话ID",
"attachment": {
"filename": "可选的文件名.ext",
"content": "可选的Base64编码文件内容"
}
}
prompt
(必需):发送给Claude的消息。conversation_id
(可选):现有对话的ID。如果不提供,将创建一个新的对话。attachment
(可选):如果要发送附件,包含文件信息的对象。
响应
{
"response": "Claude的回复",
"conversation_id": "对话ID"
}
2. 发送消息(纯文本响应)
向Claude发送消息并获取纯文本回复。
- URL:
/send_message_text
- 方法:
POST
- 内容类型:
application/json
请求体
{
"prompt": "你发送给Claude的消息",
"conversation_id": "可选的对话ID"
}
响应
Claude的纯文本回复。
3. 列出对话
获取所有对话的列表。
- URL:
/list_conversations
- 方法:
GET
响应
[
{
"uuid": "对话ID",
"name": "对话名称",
"created_at": "创建时间戳",
"updated_at": "更新时间戳"
},
// ... 更多对话
]
4. 创建对话
创建一个新的对话。
- URL:
/create_conversation
- 方法:
POST
响应
{
"uuid": "新对话ID",
"name": "新对话名称",
"created_at": "创建时间戳",
"updated_at": "更新时间戳"
}
5. 重命名对话
重命名现有对话。
- URL:
/rename_conversation
- 方法:
POST
- 内容类型:
application/json
请求体
{
"conversation_id": "现有对话ID",
"title": "新对话标题"
}
响应
{
"success": true
}
错误处理
所有端点都会返回适当的HTTP状态码:
- 200:操作成功
- 400:错误请求(例如,缺少必需参数)
- 500:服务器错误
错误响应将包含一个带有"error"键的JSON对象,描述问题。
注意事项
- 使用带附件的
/send_message
端点时,文件内容应该是Base64编码的。 - 服务器默认运行在5001端口。确保此端口可用且可访问。
- 除了
/send_message_text
端点返回纯文本外,所有响应均为JSON格式。