diff --git a/poetry.lock b/poetry.lock index 07198098b..c67ba3a93 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,20 @@ # This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. +[[package]] +name = "agent-protocol" +version = "0.1.1" +description = "API for interacting with Agent" +optional = false +python-versions = ">=3.7,<4.0.0" +files = [ + {file = "agent_protocol-0.1.1-py3-none-any.whl", hash = "sha256:5fb6a4e41bbbd63dea2aeef1aaabe81b6d474d03e893361ddcb15111514e1cbf"}, + {file = "agent_protocol-0.1.1.tar.gz", hash = "sha256:8a7525d1d4798800a767f1c4b75f47328b24dac01fb4cdc054fde3633425a4a0"}, +] + +[package.dependencies] +fastapi = ">=0.100.0,<0.101.0" +hypercorn = ">=0.14.4,<0.15.0" + [[package]] name = "aiohttp" version = "3.8.4" @@ -122,6 +137,26 @@ files = [ [package.dependencies] frozenlist = ">=1.1.0" +[[package]] +name = "anyio" +version = "3.7.1" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +optional = false +python-versions = ">=3.7" +files = [ + {file = "anyio-3.7.1-py3-none-any.whl", hash = "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"}, + {file = "anyio-3.7.1.tar.gz", hash = "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780"}, +] + +[package.dependencies] +idna = ">=2.8" +sniffio = ">=1.1" + +[package.extras] +doc = ["Sphinx", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-jquery"] +test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (<0.22)"] + [[package]] name = "async-timeout" version = "4.0.2" @@ -257,6 +292,25 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "fastapi" +version = "0.100.0" +description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +optional = false +python-versions = ">=3.7" +files = [ + {file = "fastapi-0.100.0-py3-none-any.whl", hash = "sha256:271662daf986da8fa98dc2b7c7f61c4abdfdccfb4786d79ed8b2878f172c6d5f"}, + {file = "fastapi-0.100.0.tar.gz", hash = "sha256:acb5f941ea8215663283c10018323ba7ea737c571b67fc7e88e9469c7eb1d12e"}, +] + +[package.dependencies] +pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<3.0.0" +starlette = ">=0.27.0,<0.28.0" +typing-extensions = ">=4.5.0" + +[package.extras] +all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.5)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] + [[package]] name = "frozenlist" version = "1.3.3" @@ -340,6 +394,77 @@ files = [ {file = "frozenlist-1.3.3.tar.gz", hash = "sha256:58bcc55721e8a90b88332d6cd441261ebb22342e238296bb330968952fbb3a6a"}, ] +[[package]] +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.7" +files = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] + +[[package]] +name = "h2" +version = "4.1.0" +description = "HTTP/2 State-Machine based protocol implementation" +optional = false +python-versions = ">=3.6.1" +files = [ + {file = "h2-4.1.0-py3-none-any.whl", hash = "sha256:03a46bcf682256c95b5fd9e9a99c1323584c3eec6440d379b9903d709476bc6d"}, + {file = "h2-4.1.0.tar.gz", hash = "sha256:a83aca08fbe7aacb79fec788c9c0bac936343560ed9ec18b82a13a12c28d2abb"}, +] + +[package.dependencies] +hpack = ">=4.0,<5" +hyperframe = ">=6.0,<7" + +[[package]] +name = "hpack" +version = "4.0.0" +description = "Pure-Python HPACK header compression" +optional = false +python-versions = ">=3.6.1" +files = [ + {file = "hpack-4.0.0-py3-none-any.whl", hash = "sha256:84a076fad3dc9a9f8063ccb8041ef100867b1878b25ef0ee63847a5d53818a6c"}, + {file = "hpack-4.0.0.tar.gz", hash = "sha256:fc41de0c63e687ebffde81187a948221294896f6bdc0ae2312708df339430095"}, +] + +[[package]] +name = "hypercorn" +version = "0.14.4" +description = "A ASGI Server based on Hyper libraries and inspired by Gunicorn" +optional = false +python-versions = ">=3.7" +files = [ + {file = "hypercorn-0.14.4-py3-none-any.whl", hash = "sha256:f956200dbf8677684e6e976219ffa6691d6cf795281184b41dbb0b135ab37b8d"}, + {file = "hypercorn-0.14.4.tar.gz", hash = "sha256:3fa504efc46a271640023c9b88c3184fd64993f47a282e8ae1a13ccb285c2f67"}, +] + +[package.dependencies] +h11 = "*" +h2 = ">=3.1.0" +priority = "*" +wsproto = ">=0.14.0" + +[package.extras] +docs = ["pydata_sphinx_theme"] +h3 = ["aioquic (>=0.9.0,<1.0)"] +trio = ["exceptiongroup (>=1.1.0)", "trio (>=0.22.0)"] +uvloop = ["uvloop"] + +[[package]] +name = "hyperframe" +version = "6.0.1" +description = "HTTP/2 framing layer for Python" +optional = false +python-versions = ">=3.6.1" +files = [ + {file = "hyperframe-6.0.1-py3-none-any.whl", hash = "sha256:0ec6bafd80d8ad2195c4f03aacba3a8265e57bc4cff261e802bf39970ed02a15"}, + {file = "hyperframe-6.0.1.tar.gz", hash = "sha256:ae510046231dc8e9ecb1a6586f63d2347bf4c8905914aa84ba585ae85f28a914"}, +] + [[package]] name = "idna" version = "3.4" @@ -471,6 +596,17 @@ files = [ openai = ">=0.27.8,<0.28.0" pydantic = ">=1.10.9,<2.0.0" +[[package]] +name = "priority" +version = "2.0.0" +description = "A pure-Python implementation of the HTTP/2 priority tree" +optional = false +python-versions = ">=3.6.1" +files = [ + {file = "priority-2.0.0-py3-none-any.whl", hash = "sha256:6f8eefce5f3ad59baf2c080a664037bb4725cd0a790d53d59ab4059288faf6aa"}, + {file = "priority-2.0.0.tar.gz", hash = "sha256:c965d54f1b8d0d0b19479db3924c7c36cf672dbf2aec92d43fbdaf4492ba18c0"}, +] + [[package]] name = "pydantic" version = "1.10.11" @@ -544,6 +680,34 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "sniffio" +version = "1.3.0" +description = "Sniff out which async library your code is running under" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, + {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, +] + +[[package]] +name = "starlette" +version = "0.27.0" +description = "The little ASGI library that shines." +optional = false +python-versions = ">=3.7" +files = [ + {file = "starlette-0.27.0-py3-none-any.whl", hash = "sha256:918416370e846586541235ccd38a474c08b80443ed31c578a418e2209b3eef91"}, + {file = "starlette-0.27.0.tar.gz", hash = "sha256:6a6b0d042acb8d469a01eba54e9cda6cbd24ac602c4cd016723117d6a7e73b75"}, +] + +[package.dependencies] +anyio = ">=3.4.0,<5" + +[package.extras] +full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyaml"] + [[package]] name = "tenacity" version = "8.2.2" @@ -606,6 +770,20 @@ secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17. socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] +[[package]] +name = "wsproto" +version = "1.2.0" +description = "WebSockets state-machine based protocol implementation" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736"}, + {file = "wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065"}, +] + +[package.dependencies] +h11 = ">=0.9.0,<1" + [[package]] name = "yarl" version = "1.9.2" @@ -696,4 +874,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "ed1cea01eb818f87aad6bc33102997b471c6d292e9c9b0aa6968a6e9ebe07d09" +content-hash = "4cfc30a669f412676381ee10eeedaa227539d82068bd6d6ab2e49f9fe9234cbb" diff --git a/pyproject.toml b/pyproject.toml index b22a23dc1..a6da4f2ab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,7 @@ python = "^3.11" openai = "^0.27.8" openai-function-call = "^0.0.5" tenacity = "^8.2.2" +agent-protocol = "^0.1.1" [build-system] requires = ["poetry-core"] @@ -19,6 +20,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry.scripts] src = "src.__main__:main" +api = "smol_dev.api:main" [project.urls] "Homepage" = "https://github.com/smol-ai/developer" diff --git a/readme.md b/readme.md index ff2916c53..75bc479f6 100644 --- a/readme.md +++ b/readme.md @@ -99,7 +99,63 @@ for file_path in file_paths: # there is also an async `generate_code()` version of this ``` +### In API mode (via [e2b](https://www.e2b.dev/)) +To start the server run: +```bash +poetry run api +``` +or +```bash +python smol_dev/api.py +``` + +and then you can call the API using either the following commands: + +To **create a task** run: +```bash +curl --request POST \ + --url http://localhost:8000/agent/tasks \ + --header 'Content-Type: application/json' \ + --data '{ + "input": "Write simple script in Python. It should write '\''Hello world!'\'' to hi.txt" +}' +``` + +You will get a response like this: +```json +{"input":"Write simple script in Python. It should write 'Hello world!' to hi.txt","task_id":"d2c4e543-ae08-4a97-9ac5-5f9a4459cb19","artifacts":[]} +``` + +Then to **execute one step of the task** copy the `task_id` you got from the previous request and run: +```bash +curl --request POST \ + --url http://localhost:8000/agent/tasks//steps +``` + +or you can use [Python client library](https://github.com/e2b-dev/agent-protocol/tree/main/agent_client/python): + +```python +from agent_protocol_client import AgentApi, ApiClient, TaskRequestBody + +... + +prompt = "Write simple script in Python. It should write 'Hello world!' to hi.txt" + +async with ApiClient() as api_client: + # Create an instance of the API class + api_instance = AgentApi(api_client) + task_request_body = TaskRequestBody(input=prompt) + + task = await api_instance.create_agent_task( + task_request_body=task_request_body + ) + task_id = task.task_id + response = await api_instance.execute_agent_task_step(task_id=task_id) + +... + +``` ## examples/prompt gallery diff --git a/smol_dev/api.py b/smol_dev/api.py new file mode 100644 index 000000000..c00cf70b8 --- /dev/null +++ b/smol_dev/api.py @@ -0,0 +1,37 @@ +from smol_dev.prompts import plan, specify_file_paths, generate_code + +from agent_protocol import ( + Agent, + StepResult, + StepHandler, +) + + +async def smol_developer(prompt: str): + shared_deps = plan(prompt) + yield shared_deps + + file_paths = specify_file_paths(prompt, shared_deps) + yield file_paths + + for file_path in file_paths: + code = await generate_code(prompt, shared_deps, file_path) + yield code + + +async def task_handler(task_input) -> StepHandler: + if not task_input: + raise Exception("No task prompt") + + smol_developer_loop = smol_developer(prompt=task_input) + + async def step_handler(step_input): + result = await anext(smol_developer_loop, None) + if result is None: + return StepResult(is_last=True) + return StepResult(output=result) + + return step_handler + + +Agent.handle_task(task_handler).start()