-
Notifications
You must be signed in to change notification settings - Fork 0
Feat: added type of agent “database” (CAN-301) #139
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
ec08a4f
34e7aa8
1694344
69ee8cb
3ae802d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -278,7 +278,7 @@ def _run_build(config_path): | |
| missing_stubs = [ | ||
| a["name"] | ||
| for a in agents | ||
| if a.get("type", "agent") != "workflow" | ||
| if a.get("type", "agent") not in ("workflow", "database") | ||
| and (a["name"] not in yaml_by_name or not a.get("entrypoint")) | ||
| ] | ||
| if missing_stubs: | ||
|
|
@@ -336,6 +336,20 @@ def _run_build(config_path): | |
| agent_name = agent_cfg["name"] | ||
| agent_type = agent_cfg.get("type", "agent") | ||
|
|
||
| if agent_type == "database": | ||
| # No build: pull the declared image and tag it like any other | ||
| # agent image so the rest of the deploy pipeline treats it the | ||
| # same way (EC2 image transfer, etc.) without further changes. | ||
| image = agent_cfg.get("image") | ||
| if not image: | ||
| logger.warning("Skipping database '%s': no image specified", agent_name) | ||
| continue | ||
| target_image = f"canyonos-{agent_name.lower()}" | ||
| logger.info("Pulling database image '%s' as '%s'", image, target_image) | ||
| subprocess.run(["docker", "pull", image], check=True) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The builds in this command go through |
||
| subprocess.run(["docker", "tag", image, target_image], check=True) | ||
| continue | ||
|
|
||
| if agent_type == "workflow": | ||
| # Workflow container | ||
| workflow_file = agent_cfg.get("workflow_file") | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -195,6 +195,10 @@ def bootstrap_instance(provisioned, spec, replica_index, agent_id): | |
| instance["public_host"] = provisioned["public_host"] | ||
| if spec.get("type") == "workflow": | ||
| instance["api_port"] = str(spec.get("api_port", 8080)) | ||
| elif spec.get("type") == "database": | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The health check this branch passes through doesn't test the database. |
||
| instance["container_port"] = "5432" | ||
| instance["host_port"] = "5432" | ||
| instance["endpoint"] = f"{host}:5432" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| return instance | ||
| except Exception: | ||
| terminate_instance(provisioned) | ||
|
|
@@ -251,6 +255,14 @@ def _bootstrap_instance( | |
| port_args = ["-p", f"{CONTAINER_PORT}:{CONTAINER_PORT}"] | ||
| if spec.get("type") == "workflow": | ||
| port_args += ["-p", f"{spec.get('api_port', 8080)}:8080"] | ||
| elif spec.get("type") == "database": | ||
| port_args += ["-p", f"{spec.get('db_port', 5432)}:5432"] | ||
|
|
||
| volume_args = [] | ||
| if spec.get("type") == "database": | ||
| volume_path = spec.get("volume_path") | ||
| if volume_path: | ||
| volume_args = ["-v", f"canyonos-{agent_name.lower()}-data:{volume_path}"] | ||
|
|
||
| logger.info("Transferring image %s to %s", image, host) | ||
| result = subprocess.run( | ||
|
|
@@ -286,6 +298,7 @@ def _bootstrap_instance( | |
| "--name", | ||
| container, | ||
| *port_args, | ||
| *volume_args, | ||
| "-e", | ||
| f"CANYONOS_REDIS_HOST={redis_host}", | ||
| "-e", | ||
|
|
@@ -311,6 +324,9 @@ def _bootstrap_instance( | |
| cmd.extend(["-e", f"CANYONOS_DATABASE_URL={db_url}"]) | ||
| if project_id: | ||
| cmd.extend(["-e", f"CANYONOS_PROJECT_ID={project_id}"]) | ||
| elif spec.get("type") == "database": | ||
| for key, value in spec.get("env", {}).items(): | ||
| cmd.extend(["-e", f"{key}={value}"]) | ||
|
|
||
| # User secrets from `env_file`. Explicit -e flags above still win over | ||
| # anything in the file. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -107,6 +107,10 @@ def bootstrap_instance(provisioned, spec, replica_index, agent_id): | |
| NETWORK, | ||
| "--name", | ||
| runtime_id, | ||
| # Docker Desktop resolves this automatically; native Linux Docker | ||
| # (e.g. an EC2 test box) does not unless told to. | ||
| "--add-host", | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This breaks two tests |
||
| "host.docker.internal:host-gateway", | ||
| "-p", | ||
| f"{host_port}:{CONTAINER_PORT}", | ||
| "-e", | ||
|
|
@@ -146,6 +150,13 @@ def bootstrap_instance(provisioned, spec, replica_index, agent_id): | |
| cmd.extend(["-e", f"CANYONOS_DATABASE_URL={db_url}"]) | ||
| if project_id: | ||
| cmd.extend(["-e", f"CANYONOS_PROJECT_ID={project_id}"]) | ||
| elif ctrl_type == "database": | ||
| cmd.extend(["-p", f"{spec.get('db_port', 5432)}:5432"]) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The retry loop above only bumps |
||
| volume_path = spec.get("volume_path") | ||
| if volume_path: | ||
| cmd.extend(["-v", f"canyonos-{agent_name.lower()}-data:{volume_path}"]) | ||
| for key, value in spec.get("env", {}).items(): | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| cmd.extend(["-e", f"{key}={value}"]) | ||
| if resources.get("cpu"): | ||
| cmd.extend(["--cpus", str(resources["cpu"])]) | ||
| if resources.get("memory"): | ||
|
|
@@ -200,6 +211,8 @@ def bootstrap_instance(provisioned, spec, replica_index, agent_id): | |
| instance["user"] = user | ||
| if ctrl_type == "workflow": | ||
| instance["api_port"] = str(spec.get("api_port", 8080)) | ||
| elif ctrl_type == "database": | ||
| instance["container_port"] = "5432" | ||
| logger.info("Runtime ready: %s -> %s", runtime_id, instance["endpoint"]) | ||
| return instance | ||
|
|
||
|
|
@@ -219,4 +232,4 @@ def terminate_instance(instance): | |
|
|
||
|
|
||
| def routing_endpoint_for(instance): | ||
| return f"{instance['runtime_id']}:{CONTAINER_PORT}" | ||
| return f"{instance['runtime_id']}:{instance.get('container_port', CONTAINER_PORT)}" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,6 +13,7 @@ | |
| CANYONOS_LLM_STUB_TEXT=testing | ||
| """ | ||
|
|
||
| # This stubbing is used to test workflows without connecting to the actual LLM and incurring costs. Notice though, if you use this, credentials won't be verified as this stub path doesn't use any. | ||
| from __future__ import annotations | ||
|
|
||
| import json | ||
|
|
@@ -78,8 +79,41 @@ def _bedrock_invoke_stream_response(text): | |
| return _stream_response(events) | ||
|
|
||
|
|
||
| def build_stub(provider_name, subpath, text): | ||
| _STUB_EMBEDDING_DIMS = 1536 | ||
|
|
||
|
userAugustos marked this conversation as resolved.
|
||
|
|
||
| def _openai_embedding_stub(body): | ||
| """A minimal embeddings response, one canned vector per requested input.""" | ||
| try: | ||
| count = len(json.loads(body or b"{}").get("input") or [None]) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Saaketh0 U can ignore if this scenario is not actually possible, worth to know |
||
| except (ValueError, TypeError): | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A body that parses to something other than a mapping still reaches |
||
| count = 1 | ||
| return _json_response( | ||
| { | ||
| "object": "list", | ||
| "data": [ | ||
| { | ||
| "object": "embedding", | ||
| "index": i, | ||
| "embedding": [0.0] * _STUB_EMBEDDING_DIMS, | ||
| } | ||
| for i in range(count) | ||
| ], | ||
| "model": "stub", | ||
| "usage": {"prompt_tokens": 1, "total_tokens": 1}, | ||
| } | ||
| ) | ||
|
|
||
|
|
||
| def build_stub(provider_name, subpath, text, body=None): | ||
| """Build a provider-appropriate canned response carrying ``text``.""" | ||
| if ( | ||
| provider_name == "openai" | ||
| and subpath | ||
| and subpath.rstrip("/").endswith("embeddings") | ||
| ): | ||
| return _openai_embedding_stub(body) | ||
|
|
||
| if provider_name == "bedrock": | ||
| op = subpath.rsplit("/", 1)[-1] if subpath else "" | ||
| if op == "converse-stream": | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only
imageis checked;db_port,volume_path,envandreplicasgo through untouched.db_port: "not-an-int"becomes-p not-an-int:5432and dies atdocker run,env: "NOPE"dies at bootstrap, andreplicas: 3starts three separate containers behind one routing entry, splitting the writes the feature exists to keep in one place. Cheaper to catch here than at deploy