Skip to content

Commit

Permalink
feat: create single function (#269)
Browse files Browse the repository at this point in the history
* feat: create single function

* update prompts to disable database stuff when no database schema is passed

* added abilty to test function endpoint

* feat: add write_function_sepec endpoint

* del tmp json file

* Updated endpoints

* changed function routes to a functions group

* Update codex/develop/agent.py

Co-authored-by: codiumai-pr-agent-pro[bot] <151058649+codiumai-pr-agent-pro[bot]@users.noreply.github.com>

* feat: add FunctionResponse return type to write_function endpoint

* unified endpoints

* fixed pr agents error

---------

Co-authored-by: codiumai-pr-agent-pro[bot] <151058649+codiumai-pr-agent-pro[bot]@users.noreply.github.com>
  • Loading branch information
Swiftyos and qodo-merge-pro[bot] authored May 11, 2024
1 parent da8cb90 commit dcbfe2d
Show file tree
Hide file tree
Showing 14 changed files with 304 additions and 30 deletions.
79 changes: 79 additions & 0 deletions codex/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,85 @@ def example(base_url: str, requirements_only: bool):
loop.run_until_complete(prisma_client.disconnect())


@cli.command()
@click.option(
"-u",
"--base-url",
default="http://127.0.0.1:8080/api/v1",
help="Base URL of the Codex server",
)
@click.option(
"-n",
"--name",
default="Greetings",
help="Name of the function",
)
@click.option(
"-d",
"--description",
default="Greets the user",
help="Description of the function",
)
@click.option(
"-i",
"--inputs",
default="Users name",
help="Inputs of the function",
)
@click.option(
"-o",
"--outputs",
default="The greeting",
help="Outputs of the function",
)
def write_function(base_url, name, description, inputs, outputs):
import aiohttp
from pydantic import ValidationError

from codex.api_model import FunctionRequest
from codex.develop.model import FunctionResponse

async def call_codex():
await prisma_client.connect()
headers: dict[str, str] = {"accept": "application/json"}

url = f"{base_url}/function/"

req = FunctionRequest(
name=name,
description=description,
inputs=inputs,
outputs=outputs,
)

try:
async with aiohttp.ClientSession() as session:
async with session.post(
url, headers=headers, json=req.model_dump()
) as response:
response.raise_for_status()

data = await response.json()
print(data)
func = FunctionResponse.model_validate(data)
return func

except aiohttp.ClientError as e:
logger.exception(f"Error getting user: {e}")
raise e
except ValidationError as e:
logger.exception(f"Error parsing user: {e}")
raise e
except Exception as e:
logger.exception(f"Unknown Error when write function: {e}")
raise e

loop = asyncio.new_event_loop()
ans = loop.run_until_complete(call_codex())
loop.run_until_complete(prisma_client.disconnect())
print(ans.code)


@cli.command()
def analytics():
"""
Expand Down
9 changes: 9 additions & 0 deletions codex/api_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,15 @@ class UsersListResponse(BaseModel):


####### APPS #######
class FunctionRequest(BaseModel):
"""
A request to generate a correctly formated function spec
"""

name: str
description: str
inputs: str
outputs: str


class ApplicationBase(BaseModel):
Expand Down
28 changes: 28 additions & 0 deletions codex/common/model.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import datetime
from typing import List, Optional, __all__

import prisma.enums
from prisma.models import Function, ObjectField, ObjectType
from pydantic import BaseModel, Field

Expand Down Expand Up @@ -272,3 +273,30 @@ class ResumePoint(BaseModel):
class ResumePointsList(BaseModel):
resume_points: List[ResumePoint]
pagination: Pagination


class APIRouteSpec(BaseModel):
"""
A Software Module for the application
"""

module_name: str
http_verb: prisma.enums.HTTPVerb
function_name: str
path: str
description: str
access_level: prisma.enums.AccessLevel
allowed_access_roles: list[str]
request_model: ObjectTypeModel
response_model: ObjectTypeModel


class FunctionSpec(BaseModel):
name: str
description: str
func_args: Optional[ObjectTypeModel] = Field(
description="The Function args", default=None
)
return_type: Optional[ObjectTypeModel] = Field(
description="The Function return tyep", default=None
)
16 changes: 16 additions & 0 deletions codex/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,22 @@ async def get_app_response_by_id(user_id: str, app_id: str) -> ApplicationRespon
)


async def create_app_db(user_id: str, app_data: ApplicationCreate) -> Application:
app = await Application.prisma().create(
data={
"name": app_data.name,
"description": app_data.description,
"userId": user_id,
},
include={"User": True},
)

if not app.userId:
raise AssertionError("Application not found")

return app


async def create_app(user_id: str, app_data: ApplicationCreate) -> ApplicationResponse:
app = await Application.prisma().create(
data={
Expand Down
48 changes: 43 additions & 5 deletions codex/develop/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
import os

import prisma
import prisma.enums
from langsmith import traceable
from prisma.enums import FunctionState
from prisma.models import (
Application,
CompiledRoute,
CompletedApp,
Function,
Expand All @@ -19,7 +22,7 @@
from codex.api_model import Identifiers
from codex.common.ai_block import LLMFailure
from codex.common.database import INCLUDE_FUNC
from codex.common.model import FunctionDef
from codex.common.model import APIRouteSpec, FunctionDef, FunctionSpec
from codex.database import create_completed_app
from codex.develop.compile import (
compile_route,
Expand All @@ -30,14 +33,49 @@
from codex.develop.develop import DevelopAIBlock, NiceGUIDevelopAIBlock
from codex.develop.function import construct_function, generate_object_template
from codex.requirements.blocks.ai_page_decompose import PageDecompositionBlock
from codex.requirements.database import get_specification
from langsmith import traceable
from codex.requirements.database import create_single_function_spec, get_specification

RECURSION_DEPTH_LIMIT = int(os.environ.get("RECURSION_DEPTH_LIMIT", 2))

logger = logging.getLogger(__name__)


@traceable
async def write_function(
ids: Identifiers, app: Application, function_spec: FunctionSpec
) -> CompiledRoute:
api_route_spec = APIRouteSpec(
module_name="Solo Function",
http_verb=prisma.enums.HTTPVerb.POST,
function_name=function_spec.name,
path="/",
description=function_spec.description,
access_level=prisma.enums.AccessLevel.PUBLIC,
allowed_access_roles=[],
request_model=function_spec.func_args,
response_model=function_spec.return_type,
)

spec: Specification = await create_single_function_spec(ids, app, api_route_spec)

completed_app = await create_completed_app(ids, spec)

if not spec.Modules:
raise ValueError("No modules found in the specification.")
if not spec.Modules[0].ApiRouteSpecs:
raise ValueError(
"No API routes found in the first module of the specification."
)

return await process_api_route(
api_route=spec.Modules[0].ApiRouteSpecs[0],
ids=ids,
spec=spec,
completed_app=completed_app,
lang="python",
)


@traceable
async def process_api_route(
api_route: prisma.models.APIRouteSpec,
Expand All @@ -46,7 +84,7 @@ async def process_api_route(
completed_app: prisma.models.CompletedApp,
extra_functions: list[Function] = [],
lang: str = "python",
):
) -> CompiledRoute:
if not api_route.RequestObject:
types = []
descs = {}
Expand Down Expand Up @@ -126,7 +164,7 @@ async def process_api_route(
available_funcs, available_objs = await populate_available_functions_objects(
extra_functions
)
await compile_route(
return await compile_route(
compiled_route.id, route_root_func, spec, available_funcs, available_objs
)

Expand Down
10 changes: 9 additions & 1 deletion codex/develop/model.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
from typing import List

from prisma.models import Function, ObjectType
from prisma.models import Function
from prisma.models import Function as FunctionDBModel
from prisma.models import ObjectType
from pydantic import BaseModel

from codex.common.model import FunctionDef
from codex.common.model import ObjectTypeModel as ObjectDef
from codex.develop.function import generate_object_code, generate_object_template


class FunctionResponse(BaseModel):
id: str
name: str
requirements: list[str]
code: str


class Package(BaseModel):
package_name: str
version: str | None = None
Expand Down
75 changes: 74 additions & 1 deletion codex/develop/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,88 @@
import codex.database
import codex.develop.agent as architect_agent
import codex.develop.database
import codex.requirements.blocks.ai_endpoint
import codex.requirements.database
from codex.api_model import DeliverableResponse, DeliverablesListResponse, Identifiers
from codex.api_model import (
ApplicationCreate,
DeliverableResponse,
DeliverablesListResponse,
FunctionRequest,
Identifiers,
)
from codex.common.model import FunctionSpec
from codex.develop.model import FunctionResponse

logger = logging.getLogger(__name__)

delivery_router = APIRouter()


# Deliverables endpoints
@delivery_router.post(
"/function/",
tags=["function"],
)
async def write_function(func_req: FunctionRequest) -> FunctionResponse:
"""
Writes a function based on the provided FunctionRequest.
Args:
func_req (FunctionRequest): The FunctionRequest object containing the details of the function.
Returns:
FunctionResponse: The FunctionResponse object containing the ID, name, requirements, and code of the completed function.
"""

user = await codex.database.get_or_create_user_by_cloud_services_id_and_discord_id(
"AutoGPT", "AutoGPT"
)
# Create App for this function
app = await codex.database.create_app_db(
user.id,
ApplicationCreate(name=func_req.name, description=func_req.description),
)

ids = Identifiers(user_id=user.id, app_id=app.id)
try:
ai_block = codex.requirements.blocks.ai_endpoint.EndpointSchemaRefinementBlock()
endpoint_resp = await ai_block.invoke(
ids=Identifiers(user_id=user.id, app_id=app.id),
invoke_params={
"spec": f"{func_req.name} - {func_req.description}",
"endpoint_repr": f"{func_req.name} - {func_req.description}\n Inputs: {func_req.inputs}\n Outputs: {func_req.outputs}",
"allowed_types": codex.requirements.blocks.ai_endpoint.ALLOWED_TYPES,
},
)
endpoint = endpoint_resp.api_endpoint
function_spec = FunctionSpec(
name=func_req.name,
description=func_req.description,
func_args=endpoint.request_model,
return_type=endpoint.response_model,
)
except Exception as e:
logger.error(f"Error creating function spec: {e}")
raise RuntimeError("Error creating function spec")

completed_function: codex.database.CompiledRoute = (
await architect_agent.write_function(ids, app, function_spec)
)
package_requirements = []
if completed_function.Packages:
for package in completed_function.Packages:
package_requirements.append(
f"{package.packageName}{f':^{package.version}' if package.version else '=*'}"
)

return FunctionResponse(
id=completed_function.id,
name=completed_function.mainFunctionName,
requirements=package_requirements,
code=completed_function.compiledCode,
)


@delivery_router.post(
"/user/{user_id}/apps/{app_id}/specs/{spec_id}/deliverables/",
tags=["deliverables"],
Expand Down
2 changes: 2 additions & 0 deletions codex/prompts/gpt-4-turbo/develop/python.system.base.j2
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ The code.py
```

You are writting functions that are used by a fastapi server, so they need to be async, make sure to await there responses.
{% if database_schema %}
For database access you will use the prisma client.
{% endif %}
You always use types from the core python types or types that has been already declared or imported.
You always add a comprehensive doc string with clear signature and example to each function so the junior developer knows what to do.
6 changes: 5 additions & 1 deletion codex/prompts/gpt-4-turbo/develop/python.system.examples.j2
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ user = await prisma.models.User.prisma().create(
)
```

{% if database_schema %}

### Query examples

For a more complete list of queries you can perform with Prisma Client Python see the documentation.
Expand Down Expand Up @@ -183,4 +185,6 @@ post = await prisma.models.Post.prisma().update(
},
},
)
```
```

{% endif %}
Loading

0 comments on commit dcbfe2d

Please sign in to comment.