Skip to content

Commit a198f1e

Browse files
committed
Update python sdk to v0.3
1 parent 880b6ca commit a198f1e

9 files changed

Lines changed: 135 additions & 43 deletions

File tree

sdk/python/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ generate:
77
mv agent_protocol/main.py agent_protocol/server.py
88
rm -rf agent_protocol/routers
99
rm agent_protocol/dependencies.py
10+
black .

sdk/python/agent_protocol/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
from .agent import Agent, StepHandler, TaskHandler, base_router as router
2-
from .models import Artifact, StepRequestBody, TaskRequestBody
2+
from .models import Artifact, Status, StepRequestBody, TaskRequestBody
33
from .db import Step, Task, TaskDB
44

55

66
__all__ = [
77
"Agent",
88
"Artifact",
9+
"Status",
910
"Step",
1011
"StepHandler",
12+
"StepRequestBody",
1113
"Task",
1214
"TaskDB",
13-
"StepRequestBody",
1415
"TaskHandler",
1516
"TaskRequestBody",
1617
"router",

sdk/python/agent_protocol/agent.py

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,26 @@
11
import asyncio
22
import os
3+
from uuid import uuid4
34

45
import aiofiles
56
from fastapi import APIRouter, UploadFile, Form, File
67
from fastapi.responses import FileResponse
78
from hypercorn.asyncio import serve
89
from hypercorn.config import Config
9-
from typing import Awaitable, Callable, List, Optional, Annotated
10+
from typing import Callable, List, Optional, Annotated, Coroutine, Any
1011

11-
from .db import InMemoryTaskDB, TaskDB
12+
from .db import InMemoryTaskDB, Task, TaskDB, Step
1213
from .server import app
1314
from .models import (
1415
TaskRequestBody,
15-
Step,
1616
StepRequestBody,
1717
Artifact,
18-
Task,
1918
Status,
2019
)
2120

2221

23-
StepHandler = Callable[[Step], Awaitable[Step]]
24-
TaskHandler = Callable[[Task], Awaitable[None]]
22+
StepHandler = Callable[[Step], Coroutine[Any, Any, Step]]
23+
TaskHandler = Callable[[Task], Coroutine[Any, Any, None]]
2524

2625

2726
_task_handler: Optional[TaskHandler]
@@ -89,12 +88,17 @@ async def execute_agent_task_step(
8988
"""
9089
Execute a step in the specified agent task.
9190
"""
91+
if not _step_handler:
92+
raise Exception("Step handler not defined")
93+
9294
task = await Agent.db.get_task(task_id)
9395
step = next(filter(lambda x: x.status == Status.created, task.steps), None)
9496

9597
if not step:
9698
raise Exception("No steps to execute")
9799

100+
step.status = Status.running
101+
98102
step.input = body.input if body else None
99103
step.additional_input = body.additional_input if body else None
100104

@@ -109,7 +113,7 @@ async def execute_agent_task_step(
109113
response_model=Step,
110114
tags=["agent"],
111115
)
112-
async def get_agent_task_step(task_id: str, step_id: str = ...) -> Step:
116+
async def get_agent_task_step(task_id: str, step_id: str) -> Step:
113117
"""
114118
Get details about a specified task step.
115119
"""
@@ -142,14 +146,15 @@ async def upload_agent_task_artifacts(
142146
"""
143147
Upload an artifact for the specified task.
144148
"""
149+
file_name = file.filename or str(uuid4())
145150
await Agent.db.get_task(task_id)
146-
artifact = await Agent.db.create_artifact(task_id, file.filename, relative_path)
151+
artifact = await Agent.db.create_artifact(task_id, file_name, relative_path)
147152

148153
path = Agent.get_artifact_folder(task_id, artifact)
149154
if not os.path.exists(path):
150155
os.makedirs(path)
151156

152-
async with aiofiles.open(os.path.join(path, file.filename), "wb") as f:
157+
async with aiofiles.open(os.path.join(path, file_name), "wb") as f:
153158
while content := await file.read(1024 * 1024): # async read chunk ~1MiB
154159
await f.write(content)
155160

sdk/python/agent_protocol/db.py

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,32 @@ class Task(APITask):
1212
steps: List[Step] = []
1313

1414

15+
class NotFoundException(Exception):
16+
"""
17+
Exception raised when a resource is not found.
18+
"""
19+
20+
def __init__(self, item_name: str, item_id: str):
21+
self.item_name = item_name
22+
self.item_id = item_id
23+
super().__init__(f"{item_name} with {item_id} not found.")
24+
25+
1526
class TaskDB(ABC):
1627
async def create_task(
1728
self,
1829
input: Optional[str],
19-
additional_input: Optional[str] = None,
20-
artifacts: List[Artifact] = None,
21-
steps: List[Step] = None,
30+
additional_input: Any = None,
31+
artifacts: Optional[List[Artifact]] = None,
32+
steps: Optional[List[Step]] = None,
2233
) -> Task:
2334
raise NotImplementedError
2435

2536
async def create_step(
2637
self,
2738
task_id: str,
2839
name: Optional[str] = None,
40+
input: Optional[str] = None,
2941
is_last: bool = False,
3042
additional_properties: Optional[Dict[str, str]] = None,
3143
) -> Step:
@@ -52,7 +64,9 @@ async def get_artifact(self, task_id: str, artifact_id: str) -> Artifact:
5264
async def list_tasks(self) -> List[Task]:
5365
raise NotImplementedError
5466

55-
async def list_steps(self, task_id: str) -> List[Step]:
67+
async def list_steps(
68+
self, task_id: str, status: Optional[Status] = None
69+
) -> List[Step]:
5670
raise NotImplementedError
5771

5872

@@ -62,9 +76,9 @@ class InMemoryTaskDB(TaskDB):
6276
async def create_task(
6377
self,
6478
input: Optional[str],
65-
additional_input: Optional[str] = None,
66-
artifacts: List[Artifact] = None,
67-
steps: List[Step] = None,
79+
additional_input: Any = None,
80+
artifacts: Optional[List[Artifact]] = None,
81+
steps: Optional[List[Step]] = None,
6882
) -> Task:
6983
if not steps:
7084
steps = []
@@ -85,14 +99,16 @@ async def create_step(
8599
self,
86100
task_id: str,
87101
name: Optional[str] = None,
102+
input: Optional[str] = None,
88103
is_last=False,
89-
additional_properties: Dict[str, Any] = None,
104+
additional_properties: Optional[Dict[str, Any]] = None,
90105
) -> Step:
91106
step_id = str(uuid.uuid4())
92107
step = Step(
93108
task_id=task_id,
94109
step_id=step_id,
95110
name=name,
111+
input=input,
96112
status=Status.created,
97113
is_last=is_last,
98114
additional_properties=additional_properties,
@@ -104,14 +120,14 @@ async def create_step(
104120
async def get_task(self, task_id: str) -> Task:
105121
task = self._tasks.get(task_id, None)
106122
if not task:
107-
raise Exception(f"Task with id {task_id} not found")
123+
raise NotFoundException("Task", task_id)
108124
return task
109125

110126
async def get_step(self, task_id: str, step_id: str) -> Step:
111127
task = await self.get_task(task_id)
112128
step = next(filter(lambda s: s.task_id == task_id, task.steps), None)
113129
if not step:
114-
raise Exception(f"Step with id {step_id} not found")
130+
raise NotFoundException("Step", step_id)
115131
return step
116132

117133
async def get_artifact(self, task_id: str, artifact_id: str) -> Artifact:
@@ -120,7 +136,7 @@ async def get_artifact(self, task_id: str, artifact_id: str) -> Artifact:
120136
filter(lambda a: a.artifact_id == artifact_id, task.artifacts), None
121137
)
122138
if not artifact:
123-
raise Exception(f"Artifact with id {artifact_id} not found")
139+
raise NotFoundException("Artifact", artifact_id)
124140
return artifact
125141

126142
async def create_artifact(
@@ -146,6 +162,11 @@ async def create_artifact(
146162
async def list_tasks(self) -> List[Task]:
147163
return [task for task in self._tasks.values()]
148164

149-
async def list_steps(self, task_id: str) -> List[Step]:
165+
async def list_steps(
166+
self, task_id: str, status: Optional[Status] = None
167+
) -> List[Step]:
150168
task = await self.get_task(task_id)
151-
return [step for step in task.steps]
169+
steps = task.steps
170+
if status:
171+
steps = list(filter(lambda s: s.status == status, steps))
172+
return steps
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from fastapi import Request
2+
from fastapi.responses import PlainTextResponse
3+
4+
from agent_protocol.db import NotFoundException
5+
6+
7+
async def not_found_exception_handler(
8+
request: Request, exc: NotFoundException
9+
) -> PlainTextResponse:
10+
return PlainTextResponse(
11+
str(exc),
12+
status_code=404,
13+
)

sdk/python/agent_protocol/models.py

Lines changed: 62 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# generated by fastapi-codegen:
22
# filename: ../../openapi.yml
3-
# timestamp: 2023-08-07T12:14:43+00:00
3+
# timestamp: 2023-08-11T14:24:22+00:00
44

55
from __future__ import annotations
66

@@ -12,65 +12,111 @@
1212

1313
class TaskInput(BaseModel):
1414
__root__: Any = Field(
15-
..., description="Input parameters for the task. Any value is allowed."
15+
...,
16+
description="Input parameters for the task. Any value is allowed.",
17+
example='{\n"debug": false,\n"mode": "benchmarks"\n}',
1618
)
1719

1820

1921
class Artifact(BaseModel):
20-
artifact_id: str = Field(..., description="ID of the artifact.")
21-
file_name: str = Field(..., description="Filename of the artifact.")
22+
artifact_id: str = Field(
23+
...,
24+
description="ID of the artifact.",
25+
example="b225e278-8b4c-4f99-a696-8facf19f0e56",
26+
)
27+
file_name: str = Field(
28+
..., description="Filename of the artifact.", example="main.py"
29+
)
2230
relative_path: Optional[str] = Field(
23-
None, description="Relative path of the artifact in the agent's workspace."
31+
None,
32+
description="Relative path of the artifact in the agent's workspace.",
33+
example="python/code/",
2434
)
2535

2636

2737
class ArtifactUpload(BaseModel):
2838
file: bytes = Field(..., description="File to upload.")
2939
relative_path: Optional[str] = Field(
30-
None, description="Relative path of the artifact in the agent's workspace."
40+
None,
41+
description="Relative path of the artifact in the agent's workspace.",
42+
example="python/code",
3143
)
3244

3345

3446
class StepInput(BaseModel):
3547
__root__: Any = Field(
36-
..., description="Input parameters for the task step. Any value is allowed."
48+
...,
49+
description="Input parameters for the task step. Any value is allowed.",
50+
example='{\n"file_to_refactor": "models.py"\n}',
3751
)
3852

3953

4054
class StepOutput(BaseModel):
4155
__root__: Any = Field(
42-
..., description="Output that the task step has produced. Any value is allowed."
56+
...,
57+
description="Output that the task step has produced. Any value is allowed.",
58+
example='{\n"tokens": 7894,\n"estimated_cost": "0,24$"\n}',
4359
)
4460

4561

4662
class TaskRequestBody(BaseModel):
47-
input: Optional[str] = Field(None, description="Input prompt for the task.")
63+
input: Optional[str] = Field(
64+
None,
65+
description="Input prompt for the task.",
66+
example="Write the words you receive to the file 'output.txt'.",
67+
)
4868
additional_input: Optional[TaskInput] = None
4969

5070

5171
class Task(TaskRequestBody):
52-
task_id: str = Field(..., description="The ID of the task.")
72+
task_id: str = Field(
73+
...,
74+
description="The ID of the task.",
75+
example="50da533e-3904-4401-8a07-c49adf88b5eb",
76+
)
5377
artifacts: List[Artifact] = Field(
54-
[], description="A list of artifacts that the task has produced."
78+
[],
79+
description="A list of artifacts that the task has produced.",
80+
example=[
81+
"7a49f31c-f9c6-4346-a22c-e32bc5af4d8e",
82+
"ab7b4091-2560-4692-a4fe-d831ea3ca7d6",
83+
],
5584
)
5685

5786

5887
class StepRequestBody(BaseModel):
59-
input: Optional[str] = Field(None, description="Input prompt for the step.")
88+
input: Optional[str] = Field(
89+
None, description="Input prompt for the step.", example="Washington"
90+
)
6091
additional_input: Optional[StepInput] = None
6192

6293

6394
class Status(Enum):
6495
created = "created"
96+
running = "running"
6597
completed = "completed"
6698

6799

68100
class Step(StepRequestBody):
69-
task_id: str = Field(..., description="The ID of the task this step belongs to.")
70-
step_id: str = Field(..., description="The ID of the task step.")
71-
name: Optional[str] = Field(None, description="The name of the task step.")
101+
task_id: str = Field(
102+
...,
103+
description="The ID of the task this step belongs to.",
104+
example="50da533e-3904-4401-8a07-c49adf88b5eb",
105+
)
106+
step_id: str = Field(
107+
...,
108+
description="The ID of the task step.",
109+
example="6bb1801a-fd80-45e8-899a-4dd723cc602e",
110+
)
111+
name: Optional[str] = Field(
112+
None, description="The name of the task step.", example="Write to file"
113+
)
72114
status: Status = Field(..., description="The status of the task step.")
73-
output: Optional[str] = Field(None, description="Output of the task step.")
115+
output: Optional[str] = Field(
116+
None,
117+
description="Output of the task step.",
118+
example="I am going to use the write_to_file command and write Washington to a file called output.txt <write_to_file('output.txt', 'Washington')",
119+
)
74120
additional_output: Optional[StepOutput] = None
75121
artifacts: List[Artifact] = Field(
76122
[], description="A list of artifacts that the step has produced."

sdk/python/agent_protocol/server.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,13 @@
66

77
from fastapi import FastAPI
88

9+
from agent_protocol.db import NotFoundException
10+
from agent_protocol.middlewares import not_found_exception_handler
11+
912
app = FastAPI(
1013
title="Agent Communication Protocol",
1114
description="Specification of the API protocol for communication with an agent.",
12-
version="v0.2",
15+
version="v0.3",
1316
)
17+
18+
app.add_exception_handler(NotFoundException, not_found_exception_handler)

sdk/python/examples/smol_developer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ async def task_handler(task: Task) -> None:
8080
await Agent.db.create_step(task.task_id, StepTypes.PLAN)
8181

8282

83-
async def step_handler(step: Step):
83+
async def step_handler(step: Step) -> Step:
8484
task = await Agent.db.get_task(step.task_id)
8585
if step.name == StepTypes.PLAN:
8686
return await _generate_shared_deps(step)

0 commit comments

Comments
 (0)