Skip to content
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
54 changes: 54 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
- [Downloading Files](#downloading-files)
- [Deleting Files](#deleting-files)
- [Accessing Metadata](#accessing-metadata)
- [Prompts](#prompts)
- [Get Prompt](#get-prompt)
- [Get Prompt Metadata](#get-prompt-metadata)
- [Applications](#applications)
- [List Applications](#list-applications)
- [Get Application by Id](#get-application-by-id)
Expand Down Expand Up @@ -556,6 +559,57 @@ FileMetadata(
)
```

### Prompts

#### Get Prompt

Use `get()` to fetch a single prompt by its storage path:

```python
# Sync
prompt = client.prompts.get("prompts/my-bucket/my-folder/my-prompt")
# Async
prompt = await async_client.prompts.get("prompts/my-bucket/my-folder/my-prompt")
```

As a result, you will receive a `Prompt` object:

```python
Prompt(
id="prompts/my-bucket/my-folder/my-prompt",
name="my-prompt",
folder_id="my-folder",
content="You are a helpful assistant.",
)
```

#### Get Prompt Metadata

Use `get_metadata()` to access metadata of a prompt:

```python
# Sync
metadata = client.prompts.get_metadata("prompts/my-bucket/my-folder/my-prompt")
# Async
metadata = await async_client.prompts.get_metadata(
"prompts/my-bucket/my-folder/my-prompt"
)
```

As a result, you will receive a `PromptMetadata` object:

```python
PromptMetadata(
name="my-prompt",
parent_path="my-folder",
bucket="my-bucket",
url="prompts/my-bucket/my-folder/my-prompt",
node_type="ITEM",
resource_type="PROMPT",
items=[],
)
```

### Applications

#### List Applications
Expand Down
10 changes: 10 additions & 0 deletions aidial_client/_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@ def _init_resources(self) -> None:
metadata=self.metadata,
dial_api_url=self.api_url,
)
self.prompts = resources.Prompts(
http_client=self._http_client,
metadata=self.metadata,
dial_api_url=self.api_url,
)
self.deployments = resources.Deployments(http_client=self._http_client)
self.application = resources.Application(http_client=self._http_client)
self.toolset = resources.Toolset(http_client=self._http_client)
Expand Down Expand Up @@ -188,6 +193,11 @@ def _init_resources(self) -> None:
metadata=self.metadata,
dial_api_url=self.api_url,
)
self.prompts = resources.AsyncPrompts(
http_client=self._http_client,
metadata=self.metadata,
dial_api_url=self.api_url,
)
self.deployments = resources.AsyncDeployments(
http_client=self._http_client
)
Expand Down
3 changes: 3 additions & 0 deletions aidial_client/resources/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from .bucket import AsyncBucket, Bucket
from .chat import AsyncChat, Chat
from .files import AsyncFiles, Files
from .prompts import AsyncPrompts, Prompts

__all__ = [
"Chat",
Expand All @@ -19,6 +20,8 @@
"AsyncBucket",
"Files",
"AsyncFiles",
"Prompts",
"AsyncPrompts",
Comment thread
adubovik marked this conversation as resolved.
"AsyncDeployments",
"Deployments",
"AsyncMetadata",
Expand Down
70 changes: 70 additions & 0 deletions aidial_client/resources/prompts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
from pathlib import PurePosixPath
from typing import Optional, Union
from urllib.parse import urljoin

import httpx

from aidial_client._constants import API_PREFIX
from aidial_client._exception import DialException, ResourceNotFoundError
from aidial_client._internal_types._http_request import FinalRequestOptions
from aidial_client.helpers.storage_resource import DialStorageResourceMixin
from aidial_client.resources.base import AsyncResource, Resource
from aidial_client.resources.metadata import AsyncMetadata, Metadata
from aidial_client.types.metadata import PromptMetadata
from aidial_client.types.prompt import Prompt


def _prompts_error_processor(
http_status_error: httpx.HTTPStatusError,
) -> Optional[DialException]:
if http_status_error.response.status_code == 404:
return ResourceNotFoundError(
message=http_status_error.response.text,
)
return None


class Prompts(Resource, DialStorageResourceMixin):
metadata: Metadata
resource_type: str = "prompts"

def get(self, url: Union[str, PurePosixPath]) -> Prompt:
"""Fetch a single prompt by its storage path."""
return self.http_client.request(
cast_to=Prompt,
options=FinalRequestOptions(
method="GET",
url=urljoin(API_PREFIX, self.get_api_path(str(url))),
),
on_http_error=_prompts_error_processor,
)

def get_metadata(self, url: Union[str, PurePosixPath]) -> PromptMetadata:
return self.metadata.get(
resource="prompts",
relative_url=self.get_api_path(str(url)),
)


class AsyncPrompts(AsyncResource, DialStorageResourceMixin):
metadata: AsyncMetadata
resource_type: str = "prompts"

async def get(self, url: Union[str, PurePosixPath]) -> Prompt:
"""Fetch a single prompt by its storage path."""
return await self.http_client.request(
cast_to=Prompt,
options=FinalRequestOptions(
method="GET",
url=urljoin(API_PREFIX, self.get_api_path(str(url))),
),
on_http_error=_prompts_error_processor,
)

async def get_metadata(
self, url: Union[str, PurePosixPath]
) -> PromptMetadata:
return await self.metadata.get(
resource="prompts",
relative_url=self.get_api_path(str(url)),
)
25 changes: 25 additions & 0 deletions aidial_client/types/prompt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from typing import Optional

from aidial_client._compatibility.pydantic import PYDANTIC_V2
from aidial_client._internal_types._model import ExtraAllowModel
from aidial_client._utils._alias import to_camel


class Prompt(ExtraAllowModel):
"""A DIAL prompt resource."""

if PYDANTIC_V2:
model_config = {
"alias_generator": to_camel,
"populate_by_name": True,
}
else:

class Config:
alias_generator = to_camel
allow_population_by_field_name = True

id: str
name: str
folder_id: str
content: Optional[str] = None
148 changes: 148 additions & 0 deletions tests/resources/test_prompts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import pytest

from aidial_client._exception import DialException, ResourceNotFoundError
from aidial_client.types.metadata import PromptMetadata
from aidial_client.types.prompt import Prompt
from tests.client_mock import get_async_client_mock, get_client_mock

PROMPT_MOCK = {
"id": "prompts/test-bucket/my-folder/my-prompt",
"name": "my-prompt",
"folderId": "my-folder",
"content": "You are a helpful assistant.",
}

PROMPT_NO_CONTENT_MOCK = {
"id": "prompts/test-bucket/my-folder/my-prompt",
"name": "my-prompt",
"folderId": "my-folder",
}

PROMPT_METADATA_MOCK = {
"name": "my-prompt",
"parentPath": "my-folder",
"bucket": "test-bucket",
"url": "prompts/test-bucket/my-folder/my-prompt",
"nodeType": "ITEM",
"resourceType": "PROMPT",
"items": [],
}


# ---------------------------------------------------------------------------
# prompts.get()
# ---------------------------------------------------------------------------


def test_get_prompt():
client = get_client_mock(status_code=200, json_mock=PROMPT_MOCK)
result = client.prompts.get("prompts/test-bucket/my-folder/my-prompt")
assert isinstance(result, Prompt)
assert result.id == "prompts/test-bucket/my-folder/my-prompt"
assert result.name == "my-prompt"
assert result.folder_id == "my-folder"
assert result.content == "You are a helpful assistant."


@pytest.mark.asyncio
async def test_async_get_prompt():
client = get_async_client_mock(status_code=200, json_mock=PROMPT_MOCK)
result = await client.prompts.get("prompts/test-bucket/my-folder/my-prompt")
assert isinstance(result, Prompt)
assert result.id == "prompts/test-bucket/my-folder/my-prompt"
assert result.name == "my-prompt"
assert result.folder_id == "my-folder"
assert result.content == "You are a helpful assistant."


def test_get_prompt_no_content():
client = get_client_mock(status_code=200, json_mock=PROMPT_NO_CONTENT_MOCK)
result = client.prompts.get("prompts/test-bucket/my-folder/my-prompt")
assert isinstance(result, Prompt)
assert result.content is None


def test_get_prompt_not_found():
client = get_client_mock(
status_code=404,
json_mock={
"error": {
"message": "Not found",
"type": "not_found",
}
},
)
with pytest.raises(ResourceNotFoundError):
client.prompts.get("prompts/test-bucket/nonexistent/prompt")


@pytest.mark.asyncio
async def test_async_get_prompt_not_found():
client = get_async_client_mock(
status_code=404,
json_mock={
"error": {
"message": "Not found",
"type": "not_found",
}
},
)
with pytest.raises(ResourceNotFoundError):
await client.prompts.get("prompts/test-bucket/nonexistent/prompt")


def test_get_prompt_http_error():
client = get_client_mock(
status_code=401,
json_mock={
"error": {
"message": "Unauthorized",
"type": "auth_error",
}
},
)
with pytest.raises(DialException):
client.prompts.get("prompts/test-bucket/my-folder/my-prompt")


@pytest.mark.asyncio
async def test_async_get_prompt_http_error():
client = get_async_client_mock(
status_code=401,
json_mock={
"error": {
"message": "Unauthorized",
"type": "auth_error",
}
},
)
with pytest.raises(DialException):
await client.prompts.get("prompts/test-bucket/my-folder/my-prompt")


# ---------------------------------------------------------------------------
# prompts.get_metadata()
# ---------------------------------------------------------------------------


def test_get_prompt_metadata():
client = get_client_mock(status_code=200, json_mock=PROMPT_METADATA_MOCK)
result = client.prompts.get_metadata(
"prompts/test-bucket/my-folder/my-prompt"
)
assert isinstance(result, PromptMetadata)
assert result.node_type == "ITEM"
assert result.bucket == "test-bucket"


@pytest.mark.asyncio
async def test_async_get_prompt_metadata():
client = get_async_client_mock(
status_code=200, json_mock=PROMPT_METADATA_MOCK
)
result = await client.prompts.get_metadata(
"prompts/test-bucket/my-folder/my-prompt"
)
assert isinstance(result, PromptMetadata)
assert result.node_type == "ITEM"
assert result.bucket == "test-bucket"
Loading