diff --git a/giza/client.py b/giza/client.py index cae818e..5213e2a 100644 --- a/giza/client.py +++ b/giza/client.py @@ -16,6 +16,7 @@ from giza.schemas.agents import Agent, AgentCreate, AgentList, AgentUpdate from giza.schemas.endpoints import Endpoint, EndpointCreate, EndpointsList from giza.schemas.jobs import Job, JobCreate, JobList +from giza.schemas.logs import Logs from giza.schemas.message import Msg from giza.schemas.models import Model, ModelCreate, ModelList, ModelUpdate from giza.schemas.proofs import Proof, ProofList @@ -667,6 +668,37 @@ def get(self, endpoint_id: int) -> Endpoint: return Endpoint(**response.json()) + @auth + def get_logs(self, endpoint_id: int) -> Logs: + """ + Get the latest logs of an endpoint. + + Args: + endpoint_id: Endpoint identifier + + Returns: + Logs: The logs of the specified deployment + """ + headers = copy.deepcopy(self.default_headers) + headers.update(self._get_auth_header()) + + response = self.session.get( + "/".join( + [ + self.url, + self.ENDPOINTS, + str(endpoint_id), + "logs", + ] + ), + headers=headers, + ) + + self._echo_debug(str(response)) + response.raise_for_status() + + return Logs(**response.json()) + @auth def delete(self, endpoint_id: int) -> None: """ @@ -958,6 +990,30 @@ def get(self, job_id: int, params: Optional[dict[str, str]] = None) -> Job: return Job(**response.json()) + @auth + def get_logs(self, job_id: int) -> Logs: + """ + Make a call to the API to retrieve job logs. + + Args: + job_id: Job identfier to retrieve the logs for + + Returns: + Logs: the logs of the specified job + """ + headers = copy.deepcopy(self.default_headers) + headers.update(self._get_auth_header()) + + response = self.session.get( + f"{self.url}/{self.JOBS_ENDPOINT}/{job_id}/logs", + headers=headers, + ) + self._echo_debug(str(response)) + + response.raise_for_status() + + return Logs(**response.json()) + @auth def create( self, @@ -1312,6 +1368,31 @@ def get(self, model_id: int, version_id: int) -> Version: return Version(**response.json()) + @auth + def get_logs(self, model_id: int, version_id: int) -> Logs: + """ + Get a version transpilation logs. + + Args: + model_id: Model identifier + version_id: Version identifier + + Returns: + The version transpilation logs + """ + headers = copy.deepcopy(self.default_headers) + headers.update(self._get_auth_header()) + + response = self.session.get( + f"{self._get_version_url(model_id)}/{version_id}/logs", + headers=headers, + ) + + self._echo_debug(str(response)) + response.raise_for_status() + + return Logs(**response.json()) + @auth def upload_cairo(self, model_id: int, version_id: int, file_path: str) -> Version: """ diff --git a/giza/commands/agents.py b/giza/commands/agents.py index 97be480..854fbf2 100644 --- a/giza/commands/agents.py +++ b/giza/commands/agents.py @@ -9,7 +9,15 @@ from giza import API_HOST from giza.client import AgentsClient, EndpointsClient -from giza.options import DEBUG_OPTION +from giza.options import ( + AGENT_OPTION, + DEBUG_OPTION, + DESCRIPTION_OPTION, + ENDPOINT_OPTION, + MODEL_OPTION, + NAME_OPTION, + VERSION_OPTION, +) from giza.schemas.agents import AgentCreate, AgentList, AgentUpdate from giza.utils import echo from giza.utils.exception_handling import ExceptionHandler @@ -27,30 +35,11 @@ """, ) def create( - model_id: Optional[int] = typer.Option( - None, - "--model-id", - "-m", - help="The ID of the model used to create the agent", - ), - version_id: Optional[int] = typer.Option( - None, - "--version-id", - "-v", - help="The ID of the version used to create the agent", - ), - endpoint_id: int = typer.Option( - None, - "--endpoint-id", - "-e", - help="The ID of the endpoint used to create the agent", - ), - name: Optional[str] = typer.Option( - None, "--name", "-n", help="The name of the agent" - ), - description: Optional[str] = typer.Option( - None, "--description", "-d", help="The description of the agent" - ), + model_id: Optional[int] = MODEL_OPTION, + version_id: Optional[int] = VERSION_OPTION, + endpoint_id: int = ENDPOINT_OPTION, + name: Optional[str] = NAME_OPTION, + description: Optional[str] = DESCRIPTION_OPTION, debug: Optional[bool] = DEBUG_OPTION, ) -> None: echo("Creating agent ✅ ") @@ -155,12 +144,7 @@ def list( """, ) def get( - agent_id: int = typer.Option( - None, - "--agent-id", - "-a", - help="The ID of the agent", - ), + agent_id: int = AGENT_OPTION, debug: Optional[bool] = DEBUG_OPTION, ) -> None: echo(f"Getting agent {agent_id} ✅ ") @@ -179,12 +163,7 @@ def get( """, ) def delete_agent( - agent_id: int = typer.Option( - None, - "--agent-id", - "-a", - help="The ID of the agent", - ), + agent_id: int = AGENT_OPTION, debug: Optional[bool] = DEBUG_OPTION, ) -> None: echo(f"Deleting agent {agent_id} ✅ ") @@ -207,18 +186,9 @@ def delete_agent( """, ) def update( - agent_id: int = typer.Option( - None, - "--agent-id", - "-a", - help="The ID of the agent", - ), - name: Optional[str] = typer.Option( - None, "--name", "-n", help="The name of the agent" - ), - description: Optional[str] = typer.Option( - None, "--description", "-d", help="The description of the agent" - ), + agent_id: int = AGENT_OPTION, + name: Optional[str] = NAME_OPTION, + description: Optional[str] = DESCRIPTION_OPTION, parameters: Optional[List[str]] = typer.Option( None, "--parameters", "-p", help="The parameters of the agent" ), diff --git a/giza/commands/endpoints.py b/giza/commands/endpoints.py index 16e1226..918272d 100644 --- a/giza/commands/endpoints.py +++ b/giza/commands/endpoints.py @@ -9,7 +9,13 @@ from giza import API_HOST from giza.client import EndpointsClient from giza.frameworks import cairo, ezkl -from giza.options import DEBUG_OPTION +from giza.options import ( + DEBUG_OPTION, + ENDPOINT_OPTION, + FRAMEWORK_OPTION, + MODEL_OPTION, + VERSION_OPTION, +) from giza.schemas.endpoints import EndpointsList from giza.schemas.proofs import Proof, ProofList from giza.utils import echo, get_response_info @@ -21,20 +27,10 @@ def deploy( data: str = typer.Argument(None), - model_id: int = typer.Option( - None, - "--model-id", - "-m", - help="The ID of the model where an endpoint will be created", - ), - version_id: int = typer.Option( - None, - "--version-id", - "-v", - help="The ID of the version that will used in the endpoint", - ), + model_id: int = MODEL_OPTION, + version_id: int = VERSION_OPTION, size: ServiceSize = typer.Option(ServiceSize.S, "--size", "-s"), - framework: Framework = typer.Option(Framework.CAIRO, "--framework", "-f"), + framework: Framework = FRAMEWORK_OPTION, debug: Optional[bool] = DEBUG_OPTION, ) -> None: if framework == Framework.CAIRO: @@ -76,10 +72,8 @@ def deploy( """, ) def list( - model_id: int = typer.Option(None, "--model-id", "-m", help="The ID of the model"), - version_id: int = typer.Option( - None, "--version-id", "-v", help="The ID of the version" - ), + model_id: int = MODEL_OPTION, + version_id: int = VERSION_OPTION, only_active: bool = typer.Option( False, "--only-active", "-a", help="Only list active endpoints" ), @@ -132,14 +126,7 @@ def list( """, ) def get( - endpoint_id: int = typer.Option( - None, - "--deployment-id", - "-d", - "--endpoint-id", - "-e", - help="The ID of the endpoint", - ), + endpoint_id: int = ENDPOINT_OPTION, debug: Optional[bool] = DEBUG_OPTION, ) -> None: echo(f"Getting endpoint {endpoint_id} ✅ ") @@ -183,14 +170,7 @@ def get( """, ) def delete_endpoint( - endpoint_id: int = typer.Option( - None, - "--deployment-id", - "-d", - "--endpoint-id", - "-e", - help="The ID of the endpoint", - ), + endpoint_id: int = ENDPOINT_OPTION, debug: Optional[bool] = DEBUG_OPTION, ) -> None: echo(f"Deleting endpoint {endpoint_id} ✅ ") @@ -210,14 +190,7 @@ def delete_endpoint( """, ) def list_proofs( - endpoint_id: int = typer.Option( - None, - "--deployment-id", - "-d", - "--endpoint-id", - "-e", - help="The ID of the endpoint", - ), + endpoint_id: int = ENDPOINT_OPTION, debug: Optional[bool] = DEBUG_OPTION, ) -> None: echo(f"Getting proofs from endpoint {endpoint_id} ✅ ") @@ -260,14 +233,7 @@ def list_proofs( """, ) def get_proof( - endpoint_id: int = typer.Option( - None, - "--deployment-id", - "-d", - "--endpoint-id", - "-e", - help="The ID of the endpoint", - ), + endpoint_id: int = ENDPOINT_OPTION, proof_id: str = typer.Option( None, "--proof-id", "-p", help="The ID or request id of the proof" ), @@ -313,14 +279,7 @@ def get_proof( """, ) def download_proof( - endpoint_id: int = typer.Option( - None, - "--deployment-id", - "-d", - "--endpoint-id", - "-e", - help="The ID of the endpoint", - ), + endpoint_id: int = ENDPOINT_OPTION, proof_id: str = typer.Option( None, "--proof-id", "-p", help="The ID or request id of the proof" ), @@ -374,14 +333,7 @@ def download_proof( """, ) def list_jobs( - endpoint_id: int = typer.Option( - None, - "--deployment-id", - "-d", - "--endpoint-id", - "-e", - help="The ID of the endpoint", - ), + endpoint_id: int = ENDPOINT_OPTION, debug: Optional[bool] = DEBUG_OPTION, ) -> None: echo(f"Getting jobs from endpoint {endpoint_id} ✅ ") @@ -401,14 +353,7 @@ def list_jobs( """, ) def verify( - endpoint_id: int = typer.Option( - None, - "--deployment-id", - "-d", - "--endpoint-id", - "-e", - help="The ID of the endpoint", - ), + endpoint_id: int = ENDPOINT_OPTION, proof_id: str = typer.Option( None, "--proof-id", "-p", help="The ID or request id of the proof" ), @@ -419,3 +364,27 @@ def verify( client = EndpointsClient(API_HOST) verification = client.verify_proof(endpoint_id, proof_id) print_json(verification.model_dump_json(exclude_unset=True)) + + +@app.command( + short_help="📜 Retrieves the logs from a version", + help="""📜 Retrieves the logs from a version + + This command will print the latest logs from the endpoint to help debug and understand + what its happening under the hood. + + If no logs are available, an empty string is printed. + """, +) +def logs( + endpoint_id: int = ENDPOINT_OPTION, + debug: Optional[bool] = DEBUG_OPTION, +) -> None: + echo(f"Getting logs for endpoint {endpoint_id} ✅ ") + with ExceptionHandler(debug=debug): + client = EndpointsClient(API_HOST) + logs = client.get_logs(endpoint_id) + if logs.logs == "": + echo.warning("No logs available") + else: + print(logs.logs) diff --git a/giza/commands/models.py b/giza/commands/models.py index cc0d039..61e7d9c 100644 --- a/giza/commands/models.py +++ b/giza/commands/models.py @@ -8,7 +8,7 @@ from giza import API_HOST from giza.client import ModelsClient -from giza.options import DEBUG_OPTION +from giza.options import DEBUG_OPTION, DESCRIPTION_OPTION, MODEL_OPTION from giza.schemas.models import ModelCreate from giza.utils import echo, get_response_info @@ -27,9 +27,7 @@ """, ) def get( - model_id: int = typer.Option( - ..., "--model-id", "-m", help="Model id to retrieve information from" - ), + model_id: int = MODEL_OPTION, debug: Optional[bool] = DEBUG_OPTION, ) -> None: """ @@ -140,9 +138,7 @@ def create( name: str = typer.Option( ..., "--name", "-n", help="Name of the model to be created" ), - description: str = typer.Option( - None, "--description", "-d", help="Description of the model to be created" - ), + description: str = DESCRIPTION_OPTION, debug: Optional[bool] = DEBUG_OPTION, ) -> None: """ diff --git a/giza/commands/prove.py b/giza/commands/prove.py index 8d7528c..e1597aa 100644 --- a/giza/commands/prove.py +++ b/giza/commands/prove.py @@ -3,7 +3,7 @@ import typer from giza.frameworks import cairo, ezkl -from giza.options import DEBUG_OPTION +from giza.options import DEBUG_OPTION, FRAMEWORK_OPTION, MODEL_OPTION, VERSION_OPTION from giza.utils.enums import Framework, JobSize app = typer.Typer() @@ -11,10 +11,10 @@ def prove( data: List[str] = typer.Argument(None), - model_id: Optional[int] = typer.Option(None, "--model-id", "-m"), - version_id: Optional[int] = typer.Option(None, "--version-id", "-v"), + model_id: Optional[int] = MODEL_OPTION, + version_id: Optional[int] = VERSION_OPTION, size: JobSize = typer.Option(JobSize.S, "--size", "-s"), - framework: Framework = typer.Option(Framework.CAIRO, "--framework", "-f"), + framework: Framework = FRAMEWORK_OPTION, output_path: str = typer.Option("zk.proof", "--output-path", "-o"), debug: Optional[bool] = DEBUG_OPTION, ) -> None: diff --git a/giza/commands/verify.py b/giza/commands/verify.py index 6ac1eb1..5a50219 100644 --- a/giza/commands/verify.py +++ b/giza/commands/verify.py @@ -3,20 +3,20 @@ import typer from giza.frameworks import cairo, ezkl -from giza.options import DEBUG_OPTION +from giza.options import DEBUG_OPTION, FRAMEWORK_OPTION, MODEL_OPTION, VERSION_OPTION from giza.utils.enums import Framework, JobSize app = typer.Typer() def verify( - model_id: Optional[int] = typer.Option(None, "--model-id", "-m"), - version_id: Optional[int] = typer.Option(None, "--version-id", "-v"), + model_id: Optional[int] = MODEL_OPTION, + version_id: Optional[int] = VERSION_OPTION, proof_id: Optional[int] = typer.Option(None, "--proof-id", "-p"), use_job: Optional[bool] = typer.Option(False, "--use-job"), proof: Optional[str] = typer.Option(None, "--proof", "-P"), size: JobSize = typer.Option(JobSize.S, "--size", "-s"), - framework: Framework = typer.Option(Framework.CAIRO, "--framework", "-f"), + framework: Framework = FRAMEWORK_OPTION, debug: Optional[bool] = DEBUG_OPTION, ) -> None: if framework == Framework.CAIRO: diff --git a/giza/commands/versions.py b/giza/commands/versions.py index bd036af..b597e38 100644 --- a/giza/commands/versions.py +++ b/giza/commands/versions.py @@ -11,7 +11,15 @@ from giza import API_HOST from giza.client import TranspileClient, VersionsClient from giza.frameworks import cairo, ezkl -from giza.options import DEBUG_OPTION +from giza.options import ( + DEBUG_OPTION, + DESCRIPTION_OPTION, + FRAMEWORK_OPTION, + INPUT_OPTION, + MODEL_OPTION, + OUTPUT_PATH_OPTION, + VERSION_OPTION, +) from giza.schemas.versions import Version, VersionList from giza.utils import echo from giza.utils.enums import Framework, VersionStatus @@ -40,8 +48,8 @@ def update_sierra(model_id: int, version_id: int, model_path: str): """, ) def get( - model_id: int = typer.Option(None, help="The ID of the model"), - version_id: int = typer.Option(None, help="The ID of the version"), + model_id: int = MODEL_OPTION, + version_id: int = VERSION_OPTION, debug: bool = DEBUG_OPTION, ) -> None: if any([model_id is None, version_id is None]): @@ -56,26 +64,12 @@ def get( def transpile( model_path: str = typer.Argument(None, help="Path of the model to transpile"), - model_id: int = typer.Option( - None, help="The ID of the model where a new version will be created" - ), + model_id: int = MODEL_OPTION, desc: str = typer.Option(None, help="Description of the version"), - model_desc: str = typer.Option( - None, help="Description of the Model to create if model_id is not provided" - ), - framework: Framework = typer.Option(Framework.CAIRO, "--framework", "-f"), - output_path: str = typer.Option( - "cairo_model", - "--output-path", - "-o", - help="The path where the cairo model will be saved", - ), - input_data: str = typer.Option( - None, - "--input-data", - "-i", - help="The input data to use for the transpilation", - ), + model_desc: str = DESCRIPTION_OPTION, + framework: Framework = FRAMEWORK_OPTION, + output_path: str = OUTPUT_PATH_OPTION, + input_data: str = INPUT_OPTION, download_model: bool = typer.Option( True, "--download-model", @@ -147,10 +141,8 @@ def transpile( """, ) def update( - model_id: int = typer.Option(None, "--model-id", "-m", help="The ID of the model"), - version_id: int = typer.Option( - None, "--version-id", "-v", help="The ID of the version" - ), + model_id: int = MODEL_OPTION, + version_id: int = VERSION_OPTION, model_path: str = typer.Option( None, "--model-path", "-M", help="Path of the model to update" ), @@ -191,7 +183,7 @@ def update( """, ) def list( - model_id: int = typer.Option(None, help="The ID of the model"), + model_id: int = MODEL_OPTION, debug: bool = DEBUG_OPTION, ) -> None: if model_id is None: @@ -214,11 +206,9 @@ def list( """, ) def download( - model_id: int = typer.Option(None, help="The ID of the model"), - version_id: int = typer.Option(None, help="The ID of the version"), - output_path: str = typer.Option( - "cairo_model", "--output-path", "-o", help="Path to output the cairo model" - ), + model_id: int = MODEL_OPTION, + version_id: int = VERSION_OPTION, + output_path: str = OUTPUT_PATH_OPTION, download_model: bool = typer.Option( False, "--download-model", @@ -274,8 +264,8 @@ def download( """, ) def download_original( - model_id: int = typer.Option(None, help="The ID of the model"), - version_id: int = typer.Option(None, help="The ID of the version"), + model_id: int = MODEL_OPTION, + version_id: int = VERSION_OPTION, output_path: str = typer.Option( "model.onnx", "--output-path", "-o", help="Path to output the ONNX model" ), @@ -305,3 +295,30 @@ def download_original( f.write(onnx_model) echo(f"ONNX model saved at: {output_path}") + + +@app.command( + short_help="📜 Retrieves the logs from a version.", + help="""📜 Retrieves the logs from a version. + + This commands needs a model id and version id to retrieve data from the server. + + If no version exists an error will be thrown. + """, +) +def logs( + model_id: int = MODEL_OPTION, + version_id: int = VERSION_OPTION, + debug: bool = DEBUG_OPTION, +) -> None: + if any([model_id is None, version_id is None]): + echo.error("⛔️Model ID and version ID are required⛔️") + sys.exit(1) + echo("Retrieving logs ✅ ") + with ExceptionHandler(debug=debug): + client = VersionsClient(API_HOST) + logs = client.get_logs(model_id, version_id) + if logs.logs == "": + echo.warning("No logs available") + else: + print(logs.logs) diff --git a/giza/frameworks/cairo.py b/giza/frameworks/cairo.py index 269fc40..25f60fb 100644 --- a/giza/frameworks/cairo.py +++ b/giza/frameworks/cairo.py @@ -83,6 +83,11 @@ def prove( f"Proving Job with name '{current_job.job_name}' and id {current_job.id} failed" ) ) + logs = client.get_logs(job.id) + if logs.logs == "": + echo.warning("No logs available") + else: + print(logs.logs) sys.exit(1) else: live.update( @@ -344,21 +349,17 @@ def transpile( elif version.status == VersionStatus.FAILED: echo.error("⛔️ Transpilation failed! ⛔️") echo.error(f"⛔️ Reason -> {version.message} ⛔️") - if version.logs: + logs = client.get_logs(model.id, version.version) + if logs.logs == "": + echo.warning("No logs available") + else: echo.error("##### Printing Transpilation Logs #####") echo( "Note: These logs are retrieved from the platform execution environment" ) - print(version.logs) + print(logs.logs) echo.error("##### End of Logs #####") sys.exit(1) - if version.logs: - echo("##### Printing Transpilation Logs #####") - echo( - "Note: These logs are retrieved from the platform execution environment" - ) - print(version.logs) - echo("##### End of Logs #####") except ValidationError as e: echo.error("Version validation error") echo.error("Review the provided information") @@ -478,6 +479,11 @@ def verify( f"Verification Job with name '{current_job.job_name}' and id {current_job.id} failed" ) ) + logs = client.get_logs(job.id) + if logs.logs == "": + echo.warning("No logs available") + else: + print(logs.logs) sys.exit(1) else: live.update( diff --git a/giza/options.py b/giza/options.py index ba8f352..6856014 100644 --- a/giza/options.py +++ b/giza/options.py @@ -1,6 +1,7 @@ import typer from giza.callbacks import debug_callback +from giza.utils.enums import Framework DEBUG_OPTION = typer.Option( False, @@ -13,3 +14,29 @@ Disabled by default.""", ) + +MODEL_OPTION = typer.Option(None, "--model-id", "-m", help="The ID of the model") +VERSION_OPTION = typer.Option(None, "--version-id", "-v", help="The ID of the version") +ENDPOINT_OPTION = typer.Option( + None, "--endpoint-id", "-e", help="The endpoint ID to use" +) +AGENT_OPTION = typer.Option( + None, + "--agent-id", + "-a", + help="The ID of the agent", +) +OUTPUT_PATH_OPTION = typer.Option( + "cairo_model", "--output-path", "-o", help="The path to save the output to" +) +DESCRIPTION_OPTION = typer.Option( + None, "--description", "-d", help="The description of the resource" +) +FRAMEWORK_OPTION = typer.Option(Framework.CAIRO, "--framework", "-f") +INPUT_OPTION = typer.Option( + None, + "--input-data", + "-i", + help="The input data to use", +) +NAME_OPTION = typer.Option(None, "--name", "-n", help="The name of the resource") diff --git a/giza/schemas/logs.py b/giza/schemas/logs.py new file mode 100644 index 0000000..0a92300 --- /dev/null +++ b/giza/schemas/logs.py @@ -0,0 +1,5 @@ +from pydantic import BaseModel + + +class Logs(BaseModel): + logs: str diff --git a/giza/schemas/versions.py b/giza/schemas/versions.py index bebf0e1..2ae3b36 100644 --- a/giza/schemas/versions.py +++ b/giza/schemas/versions.py @@ -26,7 +26,6 @@ class Version(BaseModel): created_date: datetime.datetime last_update: datetime.datetime framework: Framework - logs: Optional[str] = None class VersionList(RootModel): diff --git a/giza/utils/exception_handling.py b/giza/utils/exception_handling.py index 894a0ff..37a1d63 100644 --- a/giza/utils/exception_handling.py +++ b/giza/utils/exception_handling.py @@ -8,6 +8,7 @@ from giza.utils import echo, get_response_info +# TODO: Implement it as a context manager which accepts a dict of errors and messages class ExceptionHandler: """ Context manager to handle exceptions in the CLI. diff --git a/tests/commands/test_endpoints.py b/tests/commands/test_endpoints.py index 4c0dd6c..6a364a1 100644 --- a/tests/commands/test_endpoints.py +++ b/tests/commands/test_endpoints.py @@ -200,7 +200,7 @@ def test_get_deployment(): [ "endpoints", "get", - "--deployment-id", + "--endpoint-id", "1", ], ) @@ -215,7 +215,7 @@ def test_get_deployment_http_error(): [ "endpoints", "get", - "--deployment-id", + "--endpoint-id", "1", ], expected_error=True, @@ -235,7 +235,7 @@ def test_endpoints_verify(): [ "endpoints", "verify", - "--deployment-id", + "--endpoint-id", "1", "--proof-id", "1",