Skip to content

Commit

Permalink
Improve documentation (#71)
Browse files Browse the repository at this point in the history
- Format code
- Improve documentation
- Expand testing
  • Loading branch information
vsakkas authored Oct 16, 2023
1 parent 5251361 commit 2a15294
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 30 deletions.
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Sydney.py

[![Latest Release](https://img.shields.io/github/v/release/vsakkas/sydney.py.svg)](https://github.com/vsakkas/sydney.py/releases/tag/v0.15.2)
[![Latest Release](https://img.shields.io/github/v/release/vsakkas/sydney.py.svg)](https://github.com/vsakkas/sydney.py/releases/tag/v0.16.0)
[![Python](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)
[![MIT License](https://img.shields.io/badge/license-MIT-blue)](https://github.com/vsakkas/sydney.py/blob/master/LICENSE)

Expand All @@ -16,6 +16,7 @@ Python Client for Bing Chat, also known as Sydney.
- Compose content in various formats and tones.
- Stream response tokens for real-time communication.
- Retrieve citations and suggested user responses.
- Provide images as input to enhance your prompts.
- Use asyncio for efficient and non-blocking I/O operations.

## Requirements
Expand Down Expand Up @@ -158,13 +159,24 @@ async with SydneyClient() as sydney:

Both versions of the `ask` method support the same parameters.


### Attachment

It is also possible to provide a URL to an image as an attachment, which will be used as input together with the prompt:

```python
async with SydneyClient() as sydney:
response = await sydney.ask("What does this picture show?", attachment=URL)
print(response)
```

### Compose

You can ask Bing Chat to compose different types of content, such emails, articles, ideas and more:

```python
async with SydneyClient() as sydney:
response = sydney.compose("Why Python is a great language", format="ideas")
response = await sydney.compose("Why Python is a great language", format="ideas")
print(response)
```

Expand Down
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
[tool.poetry]
name = "sydney-py"
version = "0.15.2"
version = "0.16.0"
description = "Python Client for Bing Chat, also known as Sydney."
authors = ["vsakkas <[email protected]>"]
license = "MIT"
readme = "README.md"
packages = [{include = "sydney"}]
packages = [{ include = "sydney" }]

[tool.poetry.dependencies]
python = "^3.9"
Expand All @@ -18,7 +18,7 @@ mypy = "^1.5.1"
pytest = "^7.4.0"
pytest-asyncio = "^0.21.1"
pytest-cov = "^4.1.0"
thefuzz = {extras = ["speedup"], version = "^0.19.0"}
thefuzz = { extras = ["speedup"], version = "^0.19.0" }

[build-system]
requires = ["poetry-core"]
Expand Down
62 changes: 37 additions & 25 deletions sydney/sydney.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
BING_BLOB_URL,
DELIMETER,
CHAT_HEADERS,
KBLOB_HEADERS
KBLOB_HEADERS,
)
from sydney.enums import (
ComposeFormat,
Expand Down Expand Up @@ -107,7 +107,9 @@ async def _get_session(self, force_close: bool = False) -> ClientSession:

return self.session

def _build_ask_arguments(self, prompt: str, attachment_info: dict | None = None) -> dict:
def _build_ask_arguments(
self, prompt: str, attachment_info: dict | None = None
) -> dict:
style_options = self.conversation_style.value.split(",")
options_sets = [
"nlu_direct_response_filter",
Expand All @@ -131,15 +133,14 @@ def _build_ask_arguments(self, prompt: str, attachment_info: dict | None = None)
]
for style in style_options:
options_sets.append(style.strip())
blob_data = {
"blobId": None,
"processedBlobId": None
}

blob_data = {"blobId": None, "processedBlobId": None}
if attachment_info:
blob_data = {
"blobId": BING_BLOB_URL + attachment_info['blobId'],
"processedBlobId": BING_BLOB_URL + attachment_info['processedBlobId']
"blobId": BING_BLOB_URL + attachment_info["blobId"],
"processedBlobId": BING_BLOB_URL + attachment_info["processedBlobId"],
}

return {
"arguments": [
{
Expand All @@ -152,7 +153,7 @@ def _build_ask_arguments(self, prompt: str, attachment_info: dict | None = None)
"text": prompt,
"messageType": MessageType.CHAT.value,
"imageUrl": blob_data["processedBlobId"],
"originalImageUrl": blob_data["blobId"]
"originalImageUrl": blob_data["blobId"],
},
"conversationSignature": self.conversation_signature,
"participant": {
Expand Down Expand Up @@ -206,23 +207,20 @@ def _build_compose_arguments(
"type": 4,
}

async def _uploadAttachment(
self,
attachment: str
):
async def _upload_attachment(self, attachment: str) -> dict:
"""
Uploads a image to Bing's servers.
Upload an image to Bing Chat.
Parameters
----------
attachment : str
The attachment to be uploaded.
The URL to the attachment image to be uploaded.
Returns
-------
dict
The response from Bing. "blobId" and "processedBlobId" are parameters that can be passed
to https://www.bing.com/images/blob?bcid=[ID] and can obtain the uploaded image on Bing's servers.
The response from Bing Chat. "blobId" and "processedBlobId" are parameters that can be passed
to https://www.bing.com/images/blob?bcid=[ID] and can obtain the uploaded image from Bing Chat.
"""
cookies = {"_U": self.bing_u_cookie}

Expand All @@ -234,22 +232,36 @@ async def _uploadAttachment(
if self.use_proxy
else None, # Resolve HTTPS issue when proxy support is enabled.
)
data = "--\r\nContent-Disposition: form-data; name=\"knowledgeRequest\"\r\n\r\n{\"imageInfo\":{\"url\":\"%s\"},\"knowledgeRequest\":{\"invokedSkills\":[\"ImageById\"],\"subscriptionId\":\"Bing.Chat.Multimodal\",\"invokedSkillsRequestData\":{\"enableFaceBlur\":true},\"convoData\":{\"convoid\":\"%s\",\"convotone\":\"%s\"}}}\r\n--\r\n" % (attachment, self.conversation_id, str(self.conversation_style))

data = (
'--\r\nContent-Disposition: form-data; name="knowledgeRequest"\r\n\r\n{"imageInfo":{"url":"%s"},"knowledgeRequest":{"invokedSkills":["ImageById"],"subscriptionId":"Bing.Chat.Multimodal","invokedSkillsRequestData":{"enableFaceBlur":true},"convoData":{"convoid":"%s","convotone":"%s"}}}\r\n--\r\n'
% (attachment, self.conversation_id, str(self.conversation_style))
)

async with session.post(BING_KBLOB_URL, data=data) as response:
if response.status != 200:
raise ImageUploadException(
f"Failed to upload image, received status: {response.status}"
)

response_dict = await response.json()
if response_dict["blobId"] == None or response_dict["processedBlobId"] == None:
if (
response_dict["blobId"] == None
or response_dict["processedBlobId"] == None
):
raise ImageUploadException(
f"Failed to upload image, Bing rejected uploading it"
f"Failed to upload image, Bing Chat rejected uploading it"
)
elif len(response_dict["blobId"]) == 0 or len(response_dict["processedBlobId"]) == 0:
elif (
len(response_dict["blobId"]) == 0
or len(response_dict["processedBlobId"]) == 0
):
raise ImageUploadException(
f"Failed to upload image, received empty image info from Bing"
)
f"Failed to upload image, received empty image info from Bing Chat"
)

await session.close()

return response_dict

async def _ask(
Expand Down Expand Up @@ -282,10 +294,10 @@ async def _ask(
)
await self.wss_client.send(as_json({"protocol": "json", "version": 1}))
await self.wss_client.recv()

attachment_info = None
if attachment:
attachment_info = await self._uploadAttachment(attachment)
attachment_info = await self._upload_attachment(attachment)

if compose:
request = self._build_compose_arguments(prompt, tone, format, length) # type: ignore
Expand Down
8 changes: 8 additions & 0 deletions tests/test_ask.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
from sydney import SydneyClient
from thefuzz import fuzz

URL = "https://hips.hearstapps.com/hmg-prod/images/dog-puppy-on-garden-royalty-free-image-1586966191.jpg?crop=0.752xw:1.00xh;0.175xw,0&resize=1200:*"


@pytest.mark.asyncio
async def test_ask_precise() -> bool:
Expand Down Expand Up @@ -191,3 +193,9 @@ async def test_ask_raw_suggestions_citations() -> None:
_ = await sydney.ask(
"When was Bing Chat released?", suggestions=True, citations=True, raw=True
)


@pytest.mark.asyncio
async def test_ask_attachment() -> None:
async with SydneyClient() as sydney:
_ = await sydney.ask("What does this image show?", attachment=URL)

0 comments on commit 2a15294

Please sign in to comment.