aidial-client 0.17.0.dev6__tar.gz → 0.17.0.dev7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/PKG-INFO +227 -1
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/README.md +226 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/_client.py +17 -2
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/_constants.py +7 -3
- aidial_client-0.17.0.dev7/aidial_client/helpers/storage_resource.py +361 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/resources/__init__.py +10 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/resources/bucket.py +3 -3
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/resources/files.py +27 -42
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/resources/metadata.py +7 -7
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/resources/prompts.py +20 -37
- aidial_client-0.17.0.dev7/aidial_client/resources/skills.py +495 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/types/metadata.py +38 -1
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/pyproject.toml +1 -1
- aidial_client-0.17.0.dev6/aidial_client/helpers/storage_resource.py +0 -202
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/LICENSE +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/__init__.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/_auth.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/_client_pool.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/_compatibility/__init__.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/_compatibility/openai.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/_compatibility/pydantic.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/_compatibility/pydantic_v1.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/_exception.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/_http_client/__init__.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/_http_client/_async.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/_http_client/_base.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/_http_client/_sse.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/_http_client/_sync.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/_internal_types/__init__.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/_internal_types/_defaults.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/_internal_types/_generic.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/_internal_types/_http_request.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/_internal_types/_json_rpc.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/_internal_types/_model.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/_log.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/_utils/__init__.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/_utils/_alias.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/_utils/_dict.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/_utils/_openai.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/_utils/_response_processing.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/_utils/_type_guard.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/helpers/__init__.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/helpers/_url.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/py.typed +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/resources/application.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/resources/base.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/resources/chat/__init__.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/resources/chat/completions.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/resources/client_channel.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/resources/deployments.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/resources/model.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/resources/resource_permissions.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/resources/toolset.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/resources/user.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/types/__init__.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/types/application.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/types/bucket.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/types/chat/__init__.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/types/chat/cache.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/types/chat/function.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/types/chat/legacy/__init__.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/types/chat/legacy/application_request.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/types/chat/legacy/chat_completion.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/types/chat/request.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/types/chat/request_param.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/types/chat/response.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/types/chat/tool.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/types/client_channel.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/types/deployment.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/types/file.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/types/model.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/types/prompt.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/types/toolset.py +0 -0
- {aidial_client-0.17.0.dev6 → aidial_client-0.17.0.dev7}/aidial_client/types/user.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aidial-client
|
|
3
|
-
Version: 0.17.0.
|
|
3
|
+
Version: 0.17.0.dev7
|
|
4
4
|
Summary: A Python client library for the AI DIAL API
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
License-File: LICENSE
|
|
@@ -59,6 +59,11 @@ Description-Content-Type: text/markdown
|
|
|
59
59
|
- [Get Prompt](#get-prompt)
|
|
60
60
|
- [Get Prompt Metadata](#get-prompt-metadata)
|
|
61
61
|
- [Delete Prompt](#delete-prompt)
|
|
62
|
+
- [Skills](#skills)
|
|
63
|
+
- [Listing Skills](#listing-skills)
|
|
64
|
+
- [Listing Files in a Skill](#listing-files-in-a-skill)
|
|
65
|
+
- [Reading a File from a Skill](#reading-a-file-from-a-skill)
|
|
66
|
+
- [Downloading a Skill](#downloading-a-skill)
|
|
62
67
|
- [Applications](#applications)
|
|
63
68
|
- [List Applications](#list-applications)
|
|
64
69
|
- [Get Application by Id](#get-application-by-id)
|
|
@@ -817,6 +822,227 @@ client.prompts.delete("prompts/my-bucket/my-folder/my-prompt")
|
|
|
817
822
|
await async_client.prompts.delete("prompts/my-bucket/my-folder/my-prompt")
|
|
818
823
|
```
|
|
819
824
|
|
|
825
|
+
### Skills
|
|
826
|
+
|
|
827
|
+
A DIAL *skill* is a folder-shaped resource served by DIAL Core's `/v2/skills`
|
|
828
|
+
API: a mandatory `SKILL.md` manifest plus an arbitrary hierarchy of bundled
|
|
829
|
+
files, addressed as a unit at `skills/{bucket}/{path}`.
|
|
830
|
+
|
|
831
|
+
> [!NOTE]
|
|
832
|
+
> The `/v2/skills` endpoints are marked as preview in DIAL Core, so their
|
|
833
|
+
> contract may still change. The client currently supports the read
|
|
834
|
+
> operations; writes are tracked separately.
|
|
835
|
+
|
|
836
|
+
Unlike the other resources, `client.skills` is not called with a URL you build
|
|
837
|
+
yourself. It is a *reference* that you narrow step by step, and each step
|
|
838
|
+
returns a new reference:
|
|
839
|
+
|
|
840
|
+
```python
|
|
841
|
+
skill = client.skills / "writing" / "tone-of-voice"
|
|
842
|
+
# equivalently: client.skills(path="writing/tone-of-voice")
|
|
843
|
+
```
|
|
844
|
+
|
|
845
|
+
References are immutable, validate every path segment as they are built, and
|
|
846
|
+
issue no request until a terminal call (`list()`, `read()`, `download()`,
|
|
847
|
+
`stream()`, `stream_download()`). Building one is identical for the sync and
|
|
848
|
+
async clients — only the terminal call is awaited.
|
|
849
|
+
|
|
850
|
+
A reference points at your own bucket unless told otherwise. Use `bucket=` for
|
|
851
|
+
a shared bucket such as `public`, and `url=` to follow an entry returned by a
|
|
852
|
+
listing:
|
|
853
|
+
|
|
854
|
+
```python
|
|
855
|
+
client.skills(bucket="public") / "demo" / "azure-resource-visualizer"
|
|
856
|
+
|
|
857
|
+
appdata = client.my_appdata()
|
|
858
|
+
client.skills(bucket=appdata.user_bucket, path=f"appdata/{appdata.app_name}")
|
|
859
|
+
```
|
|
860
|
+
|
|
861
|
+
#### Listing Skills
|
|
862
|
+
|
|
863
|
+
`list()` returns the skills and grouping folders at the reference. With no
|
|
864
|
+
narrowing it lists your bucket root:
|
|
865
|
+
|
|
866
|
+
```python
|
|
867
|
+
# Sync
|
|
868
|
+
listing = client.skills.list()
|
|
869
|
+
# Async
|
|
870
|
+
listing = await async_client.skills.list()
|
|
871
|
+
|
|
872
|
+
for item in listing.items or []:
|
|
873
|
+
# "ITEM" is a skill, "FOLDER" is a grouping folder
|
|
874
|
+
print(item.node_type, item.url)
|
|
875
|
+
|
|
876
|
+
# Follow either one with url=
|
|
877
|
+
nested = client.skills(url=item.url)
|
|
878
|
+
```
|
|
879
|
+
|
|
880
|
+
Narrow first to list a grouping folder, and pass the listing options to the
|
|
881
|
+
terminal call:
|
|
882
|
+
|
|
883
|
+
```python
|
|
884
|
+
page = (client.skills / "writing").list(recursive=True, limit=1000)
|
|
885
|
+
```
|
|
886
|
+
|
|
887
|
+
Example of the response:
|
|
888
|
+
|
|
889
|
+
```python
|
|
890
|
+
SkillMetadata(
|
|
891
|
+
name="writing",
|
|
892
|
+
parent_path=None,
|
|
893
|
+
bucket="my-bucket",
|
|
894
|
+
url="skills/my-bucket/writing/",
|
|
895
|
+
node_type="FOLDER",
|
|
896
|
+
resource_type="SKILL",
|
|
897
|
+
next_token=None,
|
|
898
|
+
items=[
|
|
899
|
+
SkillItem(
|
|
900
|
+
name="tone-of-voice",
|
|
901
|
+
parent_path="writing",
|
|
902
|
+
bucket="my-bucket",
|
|
903
|
+
url="skills/my-bucket/writing/tone-of-voice",
|
|
904
|
+
node_type="ITEM",
|
|
905
|
+
resource_type="SKILL",
|
|
906
|
+
created_at=1724836229736,
|
|
907
|
+
updated_at=1724836248936,
|
|
908
|
+
author="user@example.com",
|
|
909
|
+
etag=None,
|
|
910
|
+
)
|
|
911
|
+
],
|
|
912
|
+
)
|
|
913
|
+
```
|
|
914
|
+
|
|
915
|
+
> [!NOTE]
|
|
916
|
+
> DIAL Core builds this listing without reading each skill's marker, so
|
|
917
|
+
> `etag` is always `None` here and the skill's `name`/`description` from
|
|
918
|
+
> `SKILL.md` are not included. Read `SKILL.md` itself if you need them.
|
|
919
|
+
|
|
920
|
+
#### Listing Files in a Skill
|
|
921
|
+
|
|
922
|
+
`skill.files` is a reference to the skill's bundled files. A page may hold
|
|
923
|
+
fewer entries than `limit`, so follow `next_token` until it is `None` —
|
|
924
|
+
building the reference once and varying only the token:
|
|
925
|
+
|
|
926
|
+
```python
|
|
927
|
+
skill = client.skills / "writing" / "tone-of-voice"
|
|
928
|
+
files = skill.files
|
|
929
|
+
|
|
930
|
+
token = None
|
|
931
|
+
while True:
|
|
932
|
+
page = files.list(recursive=True, limit=1000, token=token)
|
|
933
|
+
for item in page.items or []:
|
|
934
|
+
print(item.node_type, item.url)
|
|
935
|
+
token = page.next_token
|
|
936
|
+
if token is None:
|
|
937
|
+
break
|
|
938
|
+
```
|
|
939
|
+
|
|
940
|
+
Narrow to a subfolder the same way as anywhere else:
|
|
941
|
+
|
|
942
|
+
```python
|
|
943
|
+
page = await (async_skill.files / "references").list()
|
|
944
|
+
```
|
|
945
|
+
|
|
946
|
+
Example of the response:
|
|
947
|
+
|
|
948
|
+
```python
|
|
949
|
+
SkillFileMetadata(
|
|
950
|
+
name="files",
|
|
951
|
+
parent_path="writing/tone-of-voice",
|
|
952
|
+
bucket="my-bucket",
|
|
953
|
+
url="skills/my-bucket/writing/tone-of-voice/files/",
|
|
954
|
+
node_type="FOLDER",
|
|
955
|
+
resource_type="SKILL",
|
|
956
|
+
next_token=None,
|
|
957
|
+
items=[
|
|
958
|
+
SkillFileItem(
|
|
959
|
+
name="SKILL.md",
|
|
960
|
+
parent_path="writing/tone-of-voice/files",
|
|
961
|
+
bucket="my-bucket",
|
|
962
|
+
url="skills/my-bucket/writing/tone-of-voice/files/SKILL.md",
|
|
963
|
+
node_type="ITEM",
|
|
964
|
+
resource_type="SKILL",
|
|
965
|
+
updated_at=1724836248936,
|
|
966
|
+
),
|
|
967
|
+
SkillFileItem(
|
|
968
|
+
# A subfolder, as returned by a non-recursive listing.
|
|
969
|
+
name="references",
|
|
970
|
+
parent_path="writing/tone-of-voice/files",
|
|
971
|
+
bucket="my-bucket",
|
|
972
|
+
url="skills/my-bucket/writing/tone-of-voice/files/references/",
|
|
973
|
+
node_type="FOLDER",
|
|
974
|
+
resource_type="SKILL",
|
|
975
|
+
),
|
|
976
|
+
],
|
|
977
|
+
)
|
|
978
|
+
```
|
|
979
|
+
|
|
980
|
+
> [!NOTE]
|
|
981
|
+
> The two modes answer different questions. `recursive=True` flattens the
|
|
982
|
+
> tree: every file at every depth, no folder entries at all, with
|
|
983
|
+
> `parent_path` showing where each file sits. A non-recursive listing returns
|
|
984
|
+
> the immediate children, folders included, distinguished by
|
|
985
|
+
> `node_type == "FOLDER"`. Empty folders never appear in either mode.
|
|
986
|
+
|
|
987
|
+
Unlike the `/v1` files listing, these entries are sparse: no
|
|
988
|
+
`content_length`, no `content_type`, and in observed responses no `etag`
|
|
989
|
+
either. Folder entries carry no `updated_at`. Treat every field except
|
|
990
|
+
`name`, `url`, `node_type` and `resource_type` as optional here.
|
|
991
|
+
|
|
992
|
+
#### Reading a File from a Skill
|
|
993
|
+
|
|
994
|
+
Name the file with `path=`, relative to the skill root, then `read()`:
|
|
995
|
+
|
|
996
|
+
```python
|
|
997
|
+
# Sync
|
|
998
|
+
manifest = skill.files(path="SKILL.md").read()
|
|
999
|
+
print(manifest.get_content().decode())
|
|
1000
|
+
|
|
1001
|
+
# Async
|
|
1002
|
+
manifest = await async_skill.files(path="SKILL.md").read()
|
|
1003
|
+
schema = await async_skill.files(path="references/api-schema.md").read()
|
|
1004
|
+
await schema.awrite_to("api-schema.md")
|
|
1005
|
+
```
|
|
1006
|
+
|
|
1007
|
+
An entry from a files listing can be followed directly, without slicing its
|
|
1008
|
+
url apart:
|
|
1009
|
+
|
|
1010
|
+
```python
|
|
1011
|
+
for item in files.list().items or []:
|
|
1012
|
+
if item.node_type == "ITEM":
|
|
1013
|
+
content = skill.files(url=item.url).read()
|
|
1014
|
+
```
|
|
1015
|
+
|
|
1016
|
+
The async client can stream instead, which avoids holding the file in memory:
|
|
1017
|
+
|
|
1018
|
+
```python
|
|
1019
|
+
async with async_skill.files(path="assets/logo.png").stream() as file:
|
|
1020
|
+
await file.awrite_to("logo.png")
|
|
1021
|
+
```
|
|
1022
|
+
|
|
1023
|
+
#### Downloading a Skill
|
|
1024
|
+
|
|
1025
|
+
`download()` fetches the whole skill as a ZIP archive:
|
|
1026
|
+
|
|
1027
|
+
```python
|
|
1028
|
+
# Sync
|
|
1029
|
+
archive = skill.download()
|
|
1030
|
+
archive.write_to("tone-of-voice.zip")
|
|
1031
|
+
|
|
1032
|
+
# Async, streamed
|
|
1033
|
+
async with async_skill.stream_download() as archive:
|
|
1034
|
+
await archive.awrite_to("tone-of-voice.zip")
|
|
1035
|
+
```
|
|
1036
|
+
|
|
1037
|
+
DIAL Core sends no `Content-Disposition` for this endpoint, so `filename`
|
|
1038
|
+
defaults to the skill name with a `.zip` suffix (`tone-of-voice.zip` above).
|
|
1039
|
+
The response's `ETag` header carries the skill's aggregate etag:
|
|
1040
|
+
|
|
1041
|
+
```python
|
|
1042
|
+
etag = archive.headers["etag"]
|
|
1043
|
+
```
|
|
1044
|
+
|
|
1045
|
+
|
|
820
1046
|
### Applications
|
|
821
1047
|
|
|
822
1048
|
#### List Applications
|
|
@@ -37,6 +37,11 @@
|
|
|
37
37
|
- [Get Prompt](#get-prompt)
|
|
38
38
|
- [Get Prompt Metadata](#get-prompt-metadata)
|
|
39
39
|
- [Delete Prompt](#delete-prompt)
|
|
40
|
+
- [Skills](#skills)
|
|
41
|
+
- [Listing Skills](#listing-skills)
|
|
42
|
+
- [Listing Files in a Skill](#listing-files-in-a-skill)
|
|
43
|
+
- [Reading a File from a Skill](#reading-a-file-from-a-skill)
|
|
44
|
+
- [Downloading a Skill](#downloading-a-skill)
|
|
40
45
|
- [Applications](#applications)
|
|
41
46
|
- [List Applications](#list-applications)
|
|
42
47
|
- [Get Application by Id](#get-application-by-id)
|
|
@@ -795,6 +800,227 @@ client.prompts.delete("prompts/my-bucket/my-folder/my-prompt")
|
|
|
795
800
|
await async_client.prompts.delete("prompts/my-bucket/my-folder/my-prompt")
|
|
796
801
|
```
|
|
797
802
|
|
|
803
|
+
### Skills
|
|
804
|
+
|
|
805
|
+
A DIAL *skill* is a folder-shaped resource served by DIAL Core's `/v2/skills`
|
|
806
|
+
API: a mandatory `SKILL.md` manifest plus an arbitrary hierarchy of bundled
|
|
807
|
+
files, addressed as a unit at `skills/{bucket}/{path}`.
|
|
808
|
+
|
|
809
|
+
> [!NOTE]
|
|
810
|
+
> The `/v2/skills` endpoints are marked as preview in DIAL Core, so their
|
|
811
|
+
> contract may still change. The client currently supports the read
|
|
812
|
+
> operations; writes are tracked separately.
|
|
813
|
+
|
|
814
|
+
Unlike the other resources, `client.skills` is not called with a URL you build
|
|
815
|
+
yourself. It is a *reference* that you narrow step by step, and each step
|
|
816
|
+
returns a new reference:
|
|
817
|
+
|
|
818
|
+
```python
|
|
819
|
+
skill = client.skills / "writing" / "tone-of-voice"
|
|
820
|
+
# equivalently: client.skills(path="writing/tone-of-voice")
|
|
821
|
+
```
|
|
822
|
+
|
|
823
|
+
References are immutable, validate every path segment as they are built, and
|
|
824
|
+
issue no request until a terminal call (`list()`, `read()`, `download()`,
|
|
825
|
+
`stream()`, `stream_download()`). Building one is identical for the sync and
|
|
826
|
+
async clients — only the terminal call is awaited.
|
|
827
|
+
|
|
828
|
+
A reference points at your own bucket unless told otherwise. Use `bucket=` for
|
|
829
|
+
a shared bucket such as `public`, and `url=` to follow an entry returned by a
|
|
830
|
+
listing:
|
|
831
|
+
|
|
832
|
+
```python
|
|
833
|
+
client.skills(bucket="public") / "demo" / "azure-resource-visualizer"
|
|
834
|
+
|
|
835
|
+
appdata = client.my_appdata()
|
|
836
|
+
client.skills(bucket=appdata.user_bucket, path=f"appdata/{appdata.app_name}")
|
|
837
|
+
```
|
|
838
|
+
|
|
839
|
+
#### Listing Skills
|
|
840
|
+
|
|
841
|
+
`list()` returns the skills and grouping folders at the reference. With no
|
|
842
|
+
narrowing it lists your bucket root:
|
|
843
|
+
|
|
844
|
+
```python
|
|
845
|
+
# Sync
|
|
846
|
+
listing = client.skills.list()
|
|
847
|
+
# Async
|
|
848
|
+
listing = await async_client.skills.list()
|
|
849
|
+
|
|
850
|
+
for item in listing.items or []:
|
|
851
|
+
# "ITEM" is a skill, "FOLDER" is a grouping folder
|
|
852
|
+
print(item.node_type, item.url)
|
|
853
|
+
|
|
854
|
+
# Follow either one with url=
|
|
855
|
+
nested = client.skills(url=item.url)
|
|
856
|
+
```
|
|
857
|
+
|
|
858
|
+
Narrow first to list a grouping folder, and pass the listing options to the
|
|
859
|
+
terminal call:
|
|
860
|
+
|
|
861
|
+
```python
|
|
862
|
+
page = (client.skills / "writing").list(recursive=True, limit=1000)
|
|
863
|
+
```
|
|
864
|
+
|
|
865
|
+
Example of the response:
|
|
866
|
+
|
|
867
|
+
```python
|
|
868
|
+
SkillMetadata(
|
|
869
|
+
name="writing",
|
|
870
|
+
parent_path=None,
|
|
871
|
+
bucket="my-bucket",
|
|
872
|
+
url="skills/my-bucket/writing/",
|
|
873
|
+
node_type="FOLDER",
|
|
874
|
+
resource_type="SKILL",
|
|
875
|
+
next_token=None,
|
|
876
|
+
items=[
|
|
877
|
+
SkillItem(
|
|
878
|
+
name="tone-of-voice",
|
|
879
|
+
parent_path="writing",
|
|
880
|
+
bucket="my-bucket",
|
|
881
|
+
url="skills/my-bucket/writing/tone-of-voice",
|
|
882
|
+
node_type="ITEM",
|
|
883
|
+
resource_type="SKILL",
|
|
884
|
+
created_at=1724836229736,
|
|
885
|
+
updated_at=1724836248936,
|
|
886
|
+
author="user@example.com",
|
|
887
|
+
etag=None,
|
|
888
|
+
)
|
|
889
|
+
],
|
|
890
|
+
)
|
|
891
|
+
```
|
|
892
|
+
|
|
893
|
+
> [!NOTE]
|
|
894
|
+
> DIAL Core builds this listing without reading each skill's marker, so
|
|
895
|
+
> `etag` is always `None` here and the skill's `name`/`description` from
|
|
896
|
+
> `SKILL.md` are not included. Read `SKILL.md` itself if you need them.
|
|
897
|
+
|
|
898
|
+
#### Listing Files in a Skill
|
|
899
|
+
|
|
900
|
+
`skill.files` is a reference to the skill's bundled files. A page may hold
|
|
901
|
+
fewer entries than `limit`, so follow `next_token` until it is `None` —
|
|
902
|
+
building the reference once and varying only the token:
|
|
903
|
+
|
|
904
|
+
```python
|
|
905
|
+
skill = client.skills / "writing" / "tone-of-voice"
|
|
906
|
+
files = skill.files
|
|
907
|
+
|
|
908
|
+
token = None
|
|
909
|
+
while True:
|
|
910
|
+
page = files.list(recursive=True, limit=1000, token=token)
|
|
911
|
+
for item in page.items or []:
|
|
912
|
+
print(item.node_type, item.url)
|
|
913
|
+
token = page.next_token
|
|
914
|
+
if token is None:
|
|
915
|
+
break
|
|
916
|
+
```
|
|
917
|
+
|
|
918
|
+
Narrow to a subfolder the same way as anywhere else:
|
|
919
|
+
|
|
920
|
+
```python
|
|
921
|
+
page = await (async_skill.files / "references").list()
|
|
922
|
+
```
|
|
923
|
+
|
|
924
|
+
Example of the response:
|
|
925
|
+
|
|
926
|
+
```python
|
|
927
|
+
SkillFileMetadata(
|
|
928
|
+
name="files",
|
|
929
|
+
parent_path="writing/tone-of-voice",
|
|
930
|
+
bucket="my-bucket",
|
|
931
|
+
url="skills/my-bucket/writing/tone-of-voice/files/",
|
|
932
|
+
node_type="FOLDER",
|
|
933
|
+
resource_type="SKILL",
|
|
934
|
+
next_token=None,
|
|
935
|
+
items=[
|
|
936
|
+
SkillFileItem(
|
|
937
|
+
name="SKILL.md",
|
|
938
|
+
parent_path="writing/tone-of-voice/files",
|
|
939
|
+
bucket="my-bucket",
|
|
940
|
+
url="skills/my-bucket/writing/tone-of-voice/files/SKILL.md",
|
|
941
|
+
node_type="ITEM",
|
|
942
|
+
resource_type="SKILL",
|
|
943
|
+
updated_at=1724836248936,
|
|
944
|
+
),
|
|
945
|
+
SkillFileItem(
|
|
946
|
+
# A subfolder, as returned by a non-recursive listing.
|
|
947
|
+
name="references",
|
|
948
|
+
parent_path="writing/tone-of-voice/files",
|
|
949
|
+
bucket="my-bucket",
|
|
950
|
+
url="skills/my-bucket/writing/tone-of-voice/files/references/",
|
|
951
|
+
node_type="FOLDER",
|
|
952
|
+
resource_type="SKILL",
|
|
953
|
+
),
|
|
954
|
+
],
|
|
955
|
+
)
|
|
956
|
+
```
|
|
957
|
+
|
|
958
|
+
> [!NOTE]
|
|
959
|
+
> The two modes answer different questions. `recursive=True` flattens the
|
|
960
|
+
> tree: every file at every depth, no folder entries at all, with
|
|
961
|
+
> `parent_path` showing where each file sits. A non-recursive listing returns
|
|
962
|
+
> the immediate children, folders included, distinguished by
|
|
963
|
+
> `node_type == "FOLDER"`. Empty folders never appear in either mode.
|
|
964
|
+
|
|
965
|
+
Unlike the `/v1` files listing, these entries are sparse: no
|
|
966
|
+
`content_length`, no `content_type`, and in observed responses no `etag`
|
|
967
|
+
either. Folder entries carry no `updated_at`. Treat every field except
|
|
968
|
+
`name`, `url`, `node_type` and `resource_type` as optional here.
|
|
969
|
+
|
|
970
|
+
#### Reading a File from a Skill
|
|
971
|
+
|
|
972
|
+
Name the file with `path=`, relative to the skill root, then `read()`:
|
|
973
|
+
|
|
974
|
+
```python
|
|
975
|
+
# Sync
|
|
976
|
+
manifest = skill.files(path="SKILL.md").read()
|
|
977
|
+
print(manifest.get_content().decode())
|
|
978
|
+
|
|
979
|
+
# Async
|
|
980
|
+
manifest = await async_skill.files(path="SKILL.md").read()
|
|
981
|
+
schema = await async_skill.files(path="references/api-schema.md").read()
|
|
982
|
+
await schema.awrite_to("api-schema.md")
|
|
983
|
+
```
|
|
984
|
+
|
|
985
|
+
An entry from a files listing can be followed directly, without slicing its
|
|
986
|
+
url apart:
|
|
987
|
+
|
|
988
|
+
```python
|
|
989
|
+
for item in files.list().items or []:
|
|
990
|
+
if item.node_type == "ITEM":
|
|
991
|
+
content = skill.files(url=item.url).read()
|
|
992
|
+
```
|
|
993
|
+
|
|
994
|
+
The async client can stream instead, which avoids holding the file in memory:
|
|
995
|
+
|
|
996
|
+
```python
|
|
997
|
+
async with async_skill.files(path="assets/logo.png").stream() as file:
|
|
998
|
+
await file.awrite_to("logo.png")
|
|
999
|
+
```
|
|
1000
|
+
|
|
1001
|
+
#### Downloading a Skill
|
|
1002
|
+
|
|
1003
|
+
`download()` fetches the whole skill as a ZIP archive:
|
|
1004
|
+
|
|
1005
|
+
```python
|
|
1006
|
+
# Sync
|
|
1007
|
+
archive = skill.download()
|
|
1008
|
+
archive.write_to("tone-of-voice.zip")
|
|
1009
|
+
|
|
1010
|
+
# Async, streamed
|
|
1011
|
+
async with async_skill.stream_download() as archive:
|
|
1012
|
+
await archive.awrite_to("tone-of-voice.zip")
|
|
1013
|
+
```
|
|
1014
|
+
|
|
1015
|
+
DIAL Core sends no `Content-Disposition` for this endpoint, so `filename`
|
|
1016
|
+
defaults to the skill name with a `.zip` suffix (`tone-of-voice.zip` above).
|
|
1017
|
+
The response's `ETag` header carries the skill's aggregate etag:
|
|
1018
|
+
|
|
1019
|
+
```python
|
|
1020
|
+
etag = archive.headers["etag"]
|
|
1021
|
+
```
|
|
1022
|
+
|
|
1023
|
+
|
|
798
1024
|
### Applications
|
|
799
1025
|
|
|
800
1026
|
#### List Applications
|
|
@@ -15,7 +15,8 @@ from aidial_client._auth import (
|
|
|
15
15
|
validate_auth,
|
|
16
16
|
)
|
|
17
17
|
from aidial_client._constants import (
|
|
18
|
-
|
|
18
|
+
API_PREFIX_V1,
|
|
19
|
+
API_PREFIX_V2,
|
|
19
20
|
DEFAULT_MAX_RETRIES,
|
|
20
21
|
DEFAULT_TIMEOUT,
|
|
21
22
|
OPENAI_PREFIX,
|
|
@@ -71,7 +72,11 @@ class BaseDialClient(Generic[_HttpClientT, AuthValueT], ABC):
|
|
|
71
72
|
|
|
72
73
|
@property
|
|
73
74
|
def api_url(self) -> str:
|
|
74
|
-
return urljoin(self._base_url,
|
|
75
|
+
return urljoin(self._base_url, API_PREFIX_V1)
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def api_url_v2(self) -> str:
|
|
79
|
+
return urljoin(self._base_url, API_PREFIX_V2)
|
|
75
80
|
|
|
76
81
|
@property
|
|
77
82
|
def base_url(self) -> str:
|
|
@@ -110,6 +115,11 @@ class Dial(BaseDialClient[SyncHTTPClient, SyncAuthValue]):
|
|
|
110
115
|
metadata=self.metadata,
|
|
111
116
|
dial_api_url=self.api_url,
|
|
112
117
|
)
|
|
118
|
+
self.skills = resources.SkillsRef(
|
|
119
|
+
http_client=self._http_client,
|
|
120
|
+
dial_api_url=self.api_url_v2,
|
|
121
|
+
resolve_bucket=self.my_bucket,
|
|
122
|
+
)
|
|
113
123
|
self.deployments = resources.Deployments(http_client=self._http_client)
|
|
114
124
|
self.application = resources.Application(http_client=self._http_client)
|
|
115
125
|
self.toolset = resources.Toolset(http_client=self._http_client)
|
|
@@ -211,6 +221,11 @@ class AsyncDial(BaseDialClient[AsyncHTTPClient, AsyncAuthValue]):
|
|
|
211
221
|
metadata=self.metadata,
|
|
212
222
|
dial_api_url=self.api_url,
|
|
213
223
|
)
|
|
224
|
+
self.skills = resources.AsyncSkillsRef(
|
|
225
|
+
http_client=self._http_client,
|
|
226
|
+
dial_api_url=self.api_url_v2,
|
|
227
|
+
resolve_bucket=self.my_bucket,
|
|
228
|
+
)
|
|
214
229
|
self.deployments = resources.AsyncDeployments(
|
|
215
230
|
http_client=self._http_client
|
|
216
231
|
)
|
|
@@ -9,9 +9,13 @@ DEFAULT_CONNECTION_LIMITS = httpx.Limits(
|
|
|
9
9
|
)
|
|
10
10
|
INITIAL_RETRY_DELAY = 0.5
|
|
11
11
|
MAX_RETRY_DELAY = 8.0
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
FILES_PREFIX = urljoin(
|
|
12
|
+
API_PREFIX_V1 = "v1/"
|
|
13
|
+
METADATA_PREFIX_V1 = urljoin(API_PREFIX_V1, "metadata/")
|
|
14
|
+
FILES_PREFIX = urljoin(API_PREFIX_V1, "files/")
|
|
15
|
+
|
|
16
|
+
# DIAL Core exposes folder-shaped resources (agent skills) under /v2.
|
|
17
|
+
API_PREFIX_V2 = "v2/"
|
|
18
|
+
METADATA_PREFIX_V2 = urljoin(API_PREFIX_V2, "metadata/")
|
|
15
19
|
|
|
16
20
|
|
|
17
21
|
OPENAI_PREFIX = "openai/"
|