Skip to content

feat: add variables and API Key support #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install build requests sseclient-py python-dotenv pytest
pip install build requests sseclient-py pytest
- name: Build package
run: python -m build
- name: Run Tests
Expand Down
40 changes: 0 additions & 40 deletions .github/workflows/python-publish.yml

This file was deleted.

14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,24 @@ pip install commonbase

## Usage

A project ID is required for all Commonbase requests. You can find your project ID
in the [Commonbase Dashboard](https://commonbase.com/).
A Project ID and API Key are required for all Commonbase requests. You can find your project ID and generate an API key in the [Commonbase Dashboard](https://commonbase.com/).

To create text and chat completions, use `commonbase.Completion.create`:
To create a completion, provide your Project ID, API Key, and prompt to `Completion.create`.

```py
import commonbase

project_id=

result = commonbase.Completion.create(
project_id="<your_project_id>",
api_key="YOUR_API_KEY",
project_id="YOUR_PROJECT_ID",
prompt="Hello!"
)

print(result.choices[0].text)
print(result.best_result)
```

To stream a completion as it is generated, use `commonbase.Completion.stream`.
To stream a completion as it is generated, use `Completion.stream`.

For more examples, see [/examples](https://github.com/commonbaseapp/commonbase-python/tree/main/examples).
For more examples, see [/examples](https://github.com/commonbaseapp/commonbase-python/tree/main/examples) or check out our [Docs](https://docs.commonbase.com/quickstart/python).
3 changes: 2 additions & 1 deletion commonbase/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from commonbase.completion import Completion
from commonbase.exceptions import CommonbaseException
from commonbase.exceptions import CommonbaseApiException, CommonbaseException
from commonbase.chat_context import ChatContext, ChatMessage
from commonbase.provider_config import ProviderConfig, OpenAIParams, AnthropicParams

__all__: [
"Completion",
"CommonbaseException",
"CommonbaseApiException",
"ChatContext",
"ChatMessage",
"ProviderConfig",
Expand Down
2 changes: 1 addition & 1 deletion commonbase/chat_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ class ChatMessage:
class ChatContext:
messages: Sequence[ChatMessage]

def _to_dict(self) -> dict:
def _as_json(self) -> dict:
return asdict(self)
184 changes: 111 additions & 73 deletions commonbase/completion.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,85 +3,138 @@
import requests
import sseclient
from commonbase.completion_response import CompletionResponse
from commonbase.exceptions import CommonbaseException
from commonbase.exceptions import CommonbaseApiException, CommonbaseException
from commonbase.chat_context import ChatContext
from commonbase.provider_config import ProviderConfig
from commonbase.truncation_config import TruncationConfig
from importlib.metadata import version, PackageNotFoundError


class Completion:
@classmethod
def _send_completion_request(
cls,
project_id: str,
prompt: str,
api_key: Optional[str] = None,
chat_context: Optional[ChatContext] = None,
user_id: Optional[str] = None,
truncate_variable: Optional[bool] = None,
provider_config: Optional[ProviderConfig] = None,
stream: bool = False,
) -> requests.Response:
assert project_id is not None
assert prompt is not None

data = {
"projectId": project_id,
"prompt": prompt,
"apiKey": api_key,
"context": chat_context._to_dict() if chat_context is not None else None,
"userId": user_id,
"truncateVariable": truncate_variable,
"providerConfig": provider_config._to_dict()
if provider_config is not None
else None,
"stream": stream,
}
data = {k: v for k, v in data.items() if v is not None}
return requests.post(
"https://api.commonbase.com/completions",
stream=stream,
json=data,
headers={"Accept": "text/event-stream"} if stream else None,
def _get_sdk_version() -> str:
try:
return version("commonbase")
except PackageNotFoundError:
# This error is thrown during testing.
return "0.0.0"


def _format_body(
project_id: str,
prompt: Optional[str] = None,
variables: Optional[dict[str, str]] = None,
chat_context: Optional[ChatContext] = None,
user_id: Optional[str] = None,
truncate_variable: Optional[TruncationConfig] = None,
provider_config: Optional[ProviderConfig] = None,
stream: bool = False,
):
data = {
"projectId": project_id,
"prompt": prompt,
"variables": variables,
"context": chat_context._as_json() if chat_context is not None else None,
"userId": user_id,
"truncateVariable": truncate_variable._as_json()
if truncate_variable is not None
else None,
"providerConfig": provider_config._as_json()
if provider_config is not None
else None,
"stream": stream,
}
return {k: v for k, v in data.items() if v is not None}


def _send_completion_request(
api_key: str,
project_id: str,
prompt: Optional[str],
variables: Optional[dict[str, str]],
chat_context: Optional[ChatContext],
user_id: Optional[str],
truncate_variable: Optional[TruncationConfig],
provider_config: Optional[ProviderConfig],
stream: bool,
) -> requests.Response:
if api_key is None:
raise CommonbaseException(
"A Commonbase API key must be provided with every request."
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add link to where you can generate a new key?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will open an issue to add this once we merge the API Key PR in the monorepo

)

data = _format_body(
project_id=project_id,
prompt=prompt,
variables=variables,
chat_context=chat_context,
user_id=user_id,
truncate_variable=truncate_variable,
provider_config=provider_config,
stream=stream,
)

headers = {
"Authorization": api_key,
"User-Agent": f"commonbase-python/{_get_sdk_version()}",
}

if stream:
headers["Accept"] = "text/event-stream"

return requests.post(
"https://api.commonbase.com/completions",
stream=stream,
json=data,
headers=headers,
)


class Completion:
@classmethod
def create(
cls,
api_key: str,
project_id: str,
prompt: str,
api_key: Optional[str] = None,
prompt: Optional[str] = None,
variables: Optional[dict[str, str]] = None,
chat_context: Optional[ChatContext] = None,
user_id: Optional[str] = None,
truncate_variable: Optional[bool] = None,
truncate_variable: Optional[TruncationConfig] = None,
provider_config: Optional[ProviderConfig] = None,
) -> CompletionResponse:
"""Creates a completion for the given prompt.

Parameters
----------
api_key : str
The Commonbase API key used to authenticate the request.
project_id : str
The ID of your Commonbase project.
prompt : str
The ID of the Commonbase project.
prompt : str, optional
The prompt for which a completion is generated.
api_key : str, optional
The API key used for authentication. Currently not necessary.
variables : dict[str, str], optional
The list of variables to use with Commonbase managed prompts.
chat_context : ChatContext, optional
The list of chat messages in a conversation
user_id : str, optional
The User ID that will be logged for the invocation.
truncate_variable : bool, optional
A flag to toggle redaction variable truncation.
truncate_variable : TruncationConfig, optional
Configures variable truncation.
provider_config : ProviderConfig, optional
Configures the completion provider to use, currently OpenAI or Anthropic.

Raises
------
CommonbaseException
If the request is malformed or there is an API error.
If the request parameters are invalid.
CommonbaseApiException
If there is an API error.
"""

response = Completion._send_completion_request(
response = _send_completion_request(
api_key=api_key,
project_id=project_id,
prompt=prompt,
api_key=api_key,
variables=variables,
chat_context=chat_context,
user_id=user_id,
truncate_variable=truncate_variable,
Expand All @@ -92,47 +145,32 @@ def create(
json = response.json()

if response.status_code >= 400 or "error" in json:
raise CommonbaseException(json)
raise CommonbaseApiException(json)

return CompletionResponse(response.json())

@classmethod
def stream(
cls,
api_key: str,
project_id: str,
prompt: str,
api_key: Optional[str] = None,
prompt: Optional[str] = None,
variables: Optional[dict[str, str]] = None,
chat_context: Optional[ChatContext] = None,
user_id: Optional[str] = None,
truncate_variable: Optional[bool] = None,
truncate_variable: Optional[TruncationConfig] = None,
provider_config: Optional[ProviderConfig] = None,
) -> Generator[CompletionResponse, None, None]:
"""Creates a completion stream for the given prompt.

Parameters
----------
project_id : str
The ID of your Commonbase project.
prompt : str
The prompt for which a completion is generated.
api_key : str, optional
The API key used for authentication. Currently not necessary.
chat_context : ChatContext, optional
The list of chat messages in a conversation
user_id : str, optional
The User ID that will be logged for the invocation.
truncate_variable : bool, optional
A flag to toggle redaction variable truncation.

Raises
------
CommonbaseException
If the request is malformed or there is an API error.
This method is identical to Completion.create(), except it returns
a stream of completion responses.
"""
response = Completion._send_completion_request(
response = _send_completion_request(
api_key=api_key,
project_id=project_id,
prompt=prompt,
api_key=api_key,
variables=variables,
chat_context=chat_context,
user_id=user_id,
truncate_variable=truncate_variable,
Expand All @@ -141,7 +179,7 @@ def stream(
)

if response.status_code >= 400:
raise CommonbaseException(response.json())
raise CommonbaseApiException(response.json())

client = sseclient.SSEClient(response)
for event in client.events():
Expand Down
4 changes: 4 additions & 0 deletions commonbase/completion_response.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,7 @@ def model(self) -> str:
@property
def choices(self) -> list[CompletionChoice]:
return [CompletionChoice(choice) for choice in self.json["choices"]]

@property
def best_result(self) -> str:
return self.choices[0].text
7 changes: 6 additions & 1 deletion commonbase/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
class CommonbaseException(Exception):
class CommonbaseApiException(Exception):
def __init__(self, json: dict) -> None:
self.response = json
super().__init__(json["error"] if "error" in json else "Commonbase Error")


class CommonbaseException(Exception):
def __init__(self, message: str) -> None:
super().__init__(message)
2 changes: 1 addition & 1 deletion commonbase/provider_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class ProviderConfig:
provider: Literal["openai", "cb-openai-eu", "anthropic"]
params: Union[OpenAIParams, AnthropicParams]

def _to_dict(self) -> dict:
def _as_json(self) -> dict:
return asdict(
self, dict_factory=lambda x: {k: v for (k, v) in x if v is not None}
)
3 changes: 0 additions & 3 deletions commonbase/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +0,0 @@
from dotenv import load_dotenv

load_dotenv()
Loading