From 278954eb1800024380ad2aea7030d4f4b8221c6a Mon Sep 17 00:00:00 2001 From: Junyan Qin <1010553892@qq.com> Date: Sat, 7 Oct 2023 03:56:52 +0000 Subject: [PATCH 1/3] feat: add supports for stream mode --- claude-api/claude_api.py | 107 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/claude-api/claude_api.py b/claude-api/claude_api.py index f7c4973..a3070d2 100644 --- a/claude-api/claude_api.py +++ b/claude-api/claude_api.py @@ -4,6 +4,7 @@ from curl_cffi import requests import requests as req import re +import typing class Client: @@ -134,6 +135,112 @@ def send_message(self, prompt, conversation_id, attachment=None,timeout=500): # Returns answer return answer + + def _query_stream( + self, + prompt, + conversation_id, + attachment=None, + timeout=500, + ) -> typing.Generator[dict, None, None]: + + url = "https://claude.ai/api/append_message" + + # Upload attachment if provided + attachments = [] + if attachment: + attachment_response = self.upload_attachment(attachment) + if attachment_response: + attachments = [attachment_response] + else: + return {"Error: Invalid file format. Please try again."} + + # Ensure attachments is an empty list when no attachment is provided + if not attachment: + attachments = [] + + payload = json.dumps({ + "completion": { + "prompt": f"{prompt}", + "timezone": "Asia/Kolkata", + "model": "claude-2" + }, + "organization_uuid": f"{self.organization_id}", + "conversation_uuid": f"{conversation_id}", + "text": f"{prompt}", + "attachments": attachments + }) + + headers = { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0', + 'Accept': 'text/event-stream, text/event-stream', + 'Accept-Language': 'en-US,en;q=0.5', + 'Referer': 'https://claude.ai/chats', + 'Content-Type': 'application/json', + 'Origin': 'https://claude.ai', + 'DNT': '1', + 'Connection': 'keep-alive', + 'Cookie': f'{self.cookie}', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'same-origin', + 'TE': 'trailers' + } + + with requests.Session() as s: + response = s.post( url, headers=headers, data=payload,impersonate="chrome110",timeout=timeout, stream=True) + for line in response.iter_lines(): + data_string = line.decode("utf-8") + if not data_string.startswith('data:'): + continue + json_str = data_string[6:].strip() + data = json.loads(json_str) + if 'completion' in data: + yield data['completion'] + + def _non_stream_query( + self, + prompt, + conversation_id, + attachment=None, + timeout=500, + ) -> str: + completion = "" + + for data in self._query_stream( + prompt=prompt, + conversation_id=conversation_id, + attachment=attachment, + timeout=timeout, + ): + completion += data + + return completion + + def query( + self, + prompt, + conversation_id, + attachment=None, + timeout=500, + stream=False, + ) -> typing.Union[str, typing.Generator[str, None, None]]: + if not stream: + return self.send_message( + prompt=prompt, + conversation_id=conversation_id, + attachment=attachment, + timeout=timeout, + ) + else: + return self._query_stream( + prompt=prompt, + conversation_id=conversation_id, + attachment=attachment, + timeout=timeout, + ) + # Deletes the conversation def delete_conversation(self, conversation_id): From 0dbf08b65cadec6f80882bac2593ec20935ac52b Mon Sep 17 00:00:00 2001 From: Junyan Qin <1010553892@qq.com> Date: Sat, 7 Oct 2023 03:58:13 +0000 Subject: [PATCH 2/3] feat: make `send_message()` a wrapper of `_non_stream_query()` --- claude-api/claude_api.py | 65 ++++------------------------------------ 1 file changed, 6 insertions(+), 59 deletions(-) diff --git a/claude-api/claude_api.py b/claude-api/claude_api.py index a3070d2..7e4a753 100644 --- a/claude-api/claude_api.py +++ b/claude-api/claude_api.py @@ -76,65 +76,12 @@ def list_all_conversations(self): # Send Message to Claude def send_message(self, prompt, conversation_id, attachment=None,timeout=500): - url = "https://claude.ai/api/append_message" - - # Upload attachment if provided - attachments = [] - if attachment: - attachment_response = self.upload_attachment(attachment) - if attachment_response: - attachments = [attachment_response] - else: - return {"Error: Invalid file format. Please try again."} - - # Ensure attachments is an empty list when no attachment is provided - if not attachment: - attachments = [] - - payload = json.dumps({ - "completion": { - "prompt": f"{prompt}", - "timezone": "Asia/Kolkata", - "model": "claude-2" - }, - "organization_uuid": f"{self.organization_id}", - "conversation_uuid": f"{conversation_id}", - "text": f"{prompt}", - "attachments": attachments - }) - - headers = { - 'User-Agent': - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0', - 'Accept': 'text/event-stream, text/event-stream', - 'Accept-Language': 'en-US,en;q=0.5', - 'Referer': 'https://claude.ai/chats', - 'Content-Type': 'application/json', - 'Origin': 'https://claude.ai', - 'DNT': '1', - 'Connection': 'keep-alive', - 'Cookie': f'{self.cookie}', - 'Sec-Fetch-Dest': 'empty', - 'Sec-Fetch-Mode': 'cors', - 'Sec-Fetch-Site': 'same-origin', - 'TE': 'trailers' - } - - response = requests.post( url, headers=headers, data=payload,impersonate="chrome110",timeout=500) - decoded_data = response.content.decode("utf-8") - decoded_data = re.sub('\n+', '\n', decoded_data).strip() - data_strings = decoded_data.split('\n') - completions = [] - for data_string in data_strings: - json_str = data_string[6:].strip() - data = json.loads(json_str) - if 'completion' in data: - completions.append(data['completion']) - - answer = ''.join(completions) - - # Returns answer - return answer + return self._non_stream_query( + prompt=prompt, + conversation_id=conversation_id, + attachment=attachment, + timeout=timeout, + ) def _query_stream( self, From 4feab4d1c154b4d561f198e8133a072c67742e5d Mon Sep 17 00:00:00 2001 From: Junyan Qin <1010553892@qq.com> Date: Sat, 7 Oct 2023 04:01:30 +0000 Subject: [PATCH 3/3] doc: add desc of query() --- README.md | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index c9e4f55..2e365cb 100644 --- a/README.md +++ b/README.md @@ -86,30 +86,35 @@ To list all the conversation Id's you had with Claude , you can use the list_all conversation_id = conversation['uuid'] print(conversation_id) -## Send Message +## Query -To send a message to Claude, you can use the send_message method. You need to provide the prompt and the conversation ID: +To query Claude, you can use the query method. You need to provide the prompt and the conversation ID: + prompt = "Hello, Claude!" + conversation_id = "" or claude_api.create_new_chat()['uuid'] + response = claude_api.query(prompt, conversation_id) + print(response) +Also supported stream mode: prompt = "Hello, Claude!" conversation_id = "" or claude_api.create_new_chat()['uuid'] - response = claude_api.send_message(prompt, conversation_id) - print(response) + response = claude_api.query(prompt, conversation_id, stream=True) + for r in response: + print(r, end="") -## Send Message with attachment +## Query with attachment -You can send any type of attachment to claude to get responses using attachment argument in send_message(). +You can send any type of attachment to claude to get responses using attachment argument in query(). Note: Claude currently supports only some file types. { You can also add timeout if you need ,using timeout parameter[default set to 500] } prompt = "Hey,Summarize me this document.!" conversation_id = "" or claude_api.create_new_chat()['uuid'] - response = claude_api.send_message(prompt, conversation_id,attachment="path/to/file.pdf",timeout=600) + response = claude_api.query(prompt, conversation_id,attachment="path/to/file.pdf",timeout=600) print(response) - ## Delete Conversation To delete a conversation, you can use the delete_conversation method: