Rename actor to worker (#174)
# PR Description This PR renames `actor` to `worker` **Does not include deployment related things in `.github/workflows/release-containers.yml`**
This commit is contained in:
parent
2cc9aba0f4
commit
890ee96ef4
23 changed files with 152 additions and 152 deletions
4
.vscode/launch.json
vendored
4
.vscode/launch.json
vendored
|
|
@ -18,11 +18,11 @@
|
||||||
"cwd": "${workspaceFolder}/examples/fastapi/arcade_example_fastapi"
|
"cwd": "${workspaceFolder}/examples/fastapi/arcade_example_fastapi"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Debug `arcade actorup --no-auth`",
|
"name": "Debug `arcade workerup --no-auth`",
|
||||||
"type": "python",
|
"type": "python",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "${workspaceFolder}/arcade/run_cli.py",
|
"program": "${workspaceFolder}/arcade/run_cli.py",
|
||||||
"args": ["actorup", "--no-auth"],
|
"args": ["workerup", "--no-auth"],
|
||||||
"console": "integratedTerminal",
|
"console": "integratedTerminal",
|
||||||
"jinja": true,
|
"jinja": true,
|
||||||
"justMyCode": true,
|
"justMyCode": true,
|
||||||
|
|
|
||||||
|
|
@ -30,25 +30,25 @@ if os.environ.get("HOMEBREW_REPOSITORY") is not None:
|
||||||
|
|
||||||
|
|
||||||
def start_servers(
|
def start_servers(
|
||||||
actor_host: str,
|
worker_host: str,
|
||||||
actor_port: int,
|
worker_port: int,
|
||||||
engine_config: str | None,
|
engine_config: str | None,
|
||||||
engine_env: str | None = None,
|
engine_env: str | None = None,
|
||||||
debug: bool = False,
|
debug: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Start the actor and engine servers.
|
Start the worker and engine servers.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
host: Host for the actor server.
|
host: Host for the worker server.
|
||||||
port: Port for the actor server.
|
port: Port for the worker server.
|
||||||
engine_config: Path to the engine configuration file.
|
engine_config: Path to the engine configuration file.
|
||||||
engine_env: Path to the engine environment file.
|
engine_env: Path to the engine environment file.
|
||||||
debug: Whether to run in debug mode.
|
debug: Whether to run in debug mode.
|
||||||
"""
|
"""
|
||||||
# Validate host and port
|
# Validate host and port
|
||||||
actor_host = _validate_host(actor_host)
|
worker_host = _validate_host(worker_host)
|
||||||
actor_port = _validate_port(actor_port)
|
worker_port = _validate_port(worker_port)
|
||||||
|
|
||||||
# Ensure engine_config is provided and validated
|
# Ensure engine_config is provided and validated
|
||||||
engine_config = _get_config_file(engine_config, default_filename="engine.yaml")
|
engine_config = _get_config_file(engine_config, default_filename="engine.yaml")
|
||||||
|
|
@ -56,14 +56,14 @@ def start_servers(
|
||||||
# Ensure engine_env is provided or found and either way, validated
|
# Ensure engine_env is provided or found and either way, validated
|
||||||
env_file = _get_config_file(engine_env, default_filename="arcade.env", optional=True)
|
env_file = _get_config_file(engine_env, default_filename="arcade.env", optional=True)
|
||||||
|
|
||||||
# Prepare command-line arguments for the actor server and engine
|
# Prepare command-line arguments for the worker server and engine
|
||||||
actor_cmd = _build_actor_command(actor_host, actor_port, debug)
|
worker_cmd = _build_worker_command(worker_host, worker_port, debug)
|
||||||
|
|
||||||
# even if the user didn't pass an env file we may have found it in the default locations
|
# even if the user didn't pass an env file we may have found it in the default locations
|
||||||
engine_cmd = _build_engine_command(engine_config, engine_env=env_file if env_file else None)
|
engine_cmd = _build_engine_command(engine_config, engine_env=env_file if env_file else None)
|
||||||
|
|
||||||
# Start and manage the processes
|
# Start and manage the processes
|
||||||
_manage_processes(actor_cmd, actor_host, actor_port, engine_cmd, debug=debug)
|
_manage_processes(worker_cmd, worker_host, worker_port, engine_cmd, debug=debug)
|
||||||
|
|
||||||
|
|
||||||
def _validate_host(host: str) -> str:
|
def _validate_host(host: str) -> str:
|
||||||
|
|
@ -71,7 +71,7 @@ def _validate_host(host: str) -> str:
|
||||||
Validates the host input.
|
Validates the host input.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
host: Host for the actor server.
|
host: Host for the worker server.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The validated host as a string.
|
The validated host as a string.
|
||||||
|
|
@ -95,7 +95,7 @@ def _validate_port(port: int) -> int:
|
||||||
Validates the port input.
|
Validates the port input.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
port: Port for the actor server.
|
port: Port for the worker server.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The validated port as an integer.
|
The validated port as an integer.
|
||||||
|
|
@ -176,13 +176,13 @@ def _get_config_file(
|
||||||
raise RuntimeError(f"Config file '{default_filename}' not found.")
|
raise RuntimeError(f"Config file '{default_filename}' not found.")
|
||||||
|
|
||||||
|
|
||||||
def _build_actor_command(host: str, port: int, debug: bool) -> list[str]:
|
def _build_worker_command(host: str, port: int, debug: bool) -> list[str]:
|
||||||
"""
|
"""
|
||||||
Builds the command to start the actor server.
|
Builds the command to start the worker server.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
host: Host for the actor server.
|
host: Host for the worker server.
|
||||||
port: Port for the actor server.
|
port: Port for the worker server.
|
||||||
debug: Whether to run in debug mode.
|
debug: Whether to run in debug mode.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
@ -198,7 +198,7 @@ def _build_actor_command(host: str, port: int, debug: bool) -> list[str]:
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
cmd = [
|
cmd = [
|
||||||
arcade_bin,
|
arcade_bin,
|
||||||
"actorup",
|
"workerup",
|
||||||
"--host",
|
"--host",
|
||||||
host,
|
host,
|
||||||
"--port",
|
"--port",
|
||||||
|
|
@ -246,28 +246,28 @@ def _build_engine_command(engine_config: str | None, engine_env: str | None = No
|
||||||
|
|
||||||
|
|
||||||
def _manage_processes(
|
def _manage_processes(
|
||||||
actor_cmd: list[str],
|
worker_cmd: list[str],
|
||||||
actor_host: str,
|
worker_host: str,
|
||||||
actor_port: int,
|
worker_port: int,
|
||||||
engine_cmd: list[str],
|
engine_cmd: list[str],
|
||||||
engine_env: dict[str, str] | None = None,
|
engine_env: dict[str, str] | None = None,
|
||||||
debug: bool = False,
|
debug: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Manages the lifecycle of the actor and engine processes.
|
Manages the lifecycle of the worker and engine processes.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
actor_cmd: The command to start the actor server.
|
worker_cmd: The command to start the worker server.
|
||||||
engine_cmd: The command to start the engine.
|
engine_cmd: The command to start the engine.
|
||||||
engine_env: Environment variables to set for the engine.
|
engine_env: Environment variables to set for the engine.
|
||||||
debug: Whether to run in debug mode.
|
debug: Whether to run in debug mode.
|
||||||
"""
|
"""
|
||||||
actor_process: subprocess.Popen | None = None
|
worker_process: subprocess.Popen | None = None
|
||||||
engine_process: subprocess.Popen | None = None
|
engine_process: subprocess.Popen | None = None
|
||||||
|
|
||||||
def terminate_processes(exit_program: bool = False) -> None:
|
def terminate_processes(exit_program: bool = False) -> None:
|
||||||
console.print("Terminating child processes...", style="bold yellow")
|
console.print("Terminating child processes...", style="bold yellow")
|
||||||
_terminate_process(actor_process)
|
_terminate_process(worker_process)
|
||||||
_terminate_process(engine_process)
|
_terminate_process(engine_process)
|
||||||
if exit_program:
|
if exit_program:
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
@ -279,18 +279,18 @@ def _manage_processes(
|
||||||
|
|
||||||
while retry_count <= max_retries:
|
while retry_count <= max_retries:
|
||||||
try:
|
try:
|
||||||
# Start the actor server
|
# Start the worker server
|
||||||
console.print("Starting actor server...", style="bold green")
|
console.print("Starting worker server...", style="bold green")
|
||||||
actor_process = _start_process("Actor", actor_cmd, debug=debug)
|
worker_process = _start_process("Worker", worker_cmd, debug=debug)
|
||||||
|
|
||||||
_wait_for_healthy_actor(actor_process, actor_host, actor_port)
|
_wait_for_healthy_worker(worker_process, worker_host, worker_port)
|
||||||
|
|
||||||
# Start the engine
|
# Start the engine
|
||||||
console.print("Starting engine...", style="bold green")
|
console.print("Starting engine...", style="bold green")
|
||||||
engine_process = _start_process("Engine", engine_cmd, env=engine_env, debug=debug)
|
engine_process = _start_process("Engine", engine_cmd, env=engine_env, debug=debug)
|
||||||
|
|
||||||
# Monitor processes
|
# Monitor processes
|
||||||
_monitor_processes(actor_process, engine_process)
|
_monitor_processes(worker_process, engine_process)
|
||||||
|
|
||||||
# If we reach here, one of the processes has exited
|
# If we reach here, one of the processes has exited
|
||||||
retry_count += 1
|
retry_count += 1
|
||||||
|
|
@ -345,7 +345,7 @@ def _start_process(
|
||||||
else:
|
else:
|
||||||
_env["GIN_MODE"] = "release"
|
_env["GIN_MODE"] = "release"
|
||||||
|
|
||||||
if name == "Actor":
|
if name == "Worker":
|
||||||
_env["PYTHONUNBUFFERED"] = "1"
|
_env["PYTHONUNBUFFERED"] = "1"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -365,26 +365,26 @@ def _start_process(
|
||||||
raise RuntimeError(f"Failed to start {name}")
|
raise RuntimeError(f"Failed to start {name}")
|
||||||
|
|
||||||
|
|
||||||
def _wait_for_healthy_actor(
|
def _wait_for_healthy_worker(
|
||||||
actor_process: subprocess.Popen, actor_host: str, actor_port: int
|
worker_process: subprocess.Popen, worker_host: str, worker_port: int
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Wait until an HTTP request to `host:port/actor/health` returns 200"""
|
"""Wait until an HTTP request to `host:port/worker/health` returns 200"""
|
||||||
|
|
||||||
while actor_process.poll() is None: # Continue waiting UNLESS the actor process has exited
|
while worker_process.poll() is None: # Continue waiting UNLESS the worker process has exited
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
try:
|
try:
|
||||||
conn = http.client.HTTPConnection(actor_host, actor_port, timeout=1)
|
conn = http.client.HTTPConnection(worker_host, worker_port, timeout=1)
|
||||||
conn.request("GET", "/actor/health")
|
conn.request("GET", "/worker/health")
|
||||||
res = conn.getresponse()
|
res = conn.getresponse()
|
||||||
if res.status == 200:
|
if res.status == 200:
|
||||||
break
|
break
|
||||||
conn.close()
|
conn.close()
|
||||||
except (socket.gaierror, http.client.HTTPException, ConnectionRefusedError, TimeoutError):
|
except (socket.gaierror, http.client.HTTPException, ConnectionRefusedError, TimeoutError):
|
||||||
pass # Handle expected exceptions gracefully
|
pass # Handle expected exceptions gracefully
|
||||||
console.print("Waiting for actor to start...", style="bold yellow")
|
console.print("Waiting for worker to start...", style="bold yellow")
|
||||||
|
|
||||||
time.sleep(1) # Wait just a little longer for everything to settle (discovered experimentally)
|
time.sleep(1) # Wait just a little longer for everything to settle (discovered experimentally)
|
||||||
console.print("Actor is healthy", style="bold green")
|
console.print("Worker is healthy", style="bold green")
|
||||||
|
|
||||||
|
|
||||||
def _stream_output(process: subprocess.Popen, name: str) -> None:
|
def _stream_output(process: subprocess.Popen, name: str) -> None:
|
||||||
|
|
@ -395,7 +395,7 @@ def _stream_output(process: subprocess.Popen, name: str) -> None:
|
||||||
process: The subprocess.Popen object.
|
process: The subprocess.Popen object.
|
||||||
name: Name of the process.
|
name: Name of the process.
|
||||||
"""
|
"""
|
||||||
stdout_style = "green" if name == "Actor" else "#87CEFA"
|
stdout_style = "green" if name == "Worker" else "#87CEFA"
|
||||||
|
|
||||||
def stream(pipe: io.TextIOWrapper | None, style: str) -> None:
|
def stream(pipe: io.TextIOWrapper | None, style: str) -> None:
|
||||||
if pipe is None:
|
if pipe is None:
|
||||||
|
|
@ -418,23 +418,23 @@ def _stream_output(process: subprocess.Popen, name: str) -> None:
|
||||||
threading.Thread(target=stream, args=(process.stderr, "red"), daemon=True).start()
|
threading.Thread(target=stream, args=(process.stderr, "red"), daemon=True).start()
|
||||||
|
|
||||||
|
|
||||||
def _monitor_processes(actor_process: subprocess.Popen, engine_process: subprocess.Popen) -> None:
|
def _monitor_processes(worker_process: subprocess.Popen, engine_process: subprocess.Popen) -> None:
|
||||||
"""
|
"""
|
||||||
Monitors the actor and engine processes, restarts them if they exit.
|
Monitors the worker and engine processes, restarts them if they exit.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
actor_process: The actor subprocess.
|
worker_process: The worker subprocess.
|
||||||
engine_process: The engine subprocess.
|
engine_process: The engine subprocess.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
actor_status = actor_process.poll()
|
worker_status = worker_process.poll()
|
||||||
engine_status = engine_process.poll()
|
engine_status = engine_process.poll()
|
||||||
|
|
||||||
if actor_status is not None or engine_status is not None:
|
if worker_status is not None or engine_status is not None:
|
||||||
if actor_status is not None:
|
if worker_status is not None:
|
||||||
console.print(
|
console.print(
|
||||||
f"Actor process exited with code {actor_status}. Restarting both processes...",
|
f"Worker process exited with code {worker_status}. Restarting both processes...",
|
||||||
style="bold red",
|
style="bold red",
|
||||||
)
|
)
|
||||||
if engine_status is not None:
|
if engine_status is not None:
|
||||||
|
|
@ -442,7 +442,7 @@ def _monitor_processes(actor_process: subprocess.Popen, engine_process: subproce
|
||||||
f"Engine process exited with code {engine_status}. Restarting both processes...",
|
f"Engine process exited with code {engine_status}. Restarting both processes...",
|
||||||
style="bold red",
|
style="bold red",
|
||||||
)
|
)
|
||||||
_terminate_process(actor_process)
|
_terminate_process(worker_process)
|
||||||
_terminate_process(engine_process)
|
_terminate_process(engine_process)
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
break # Exit to restart both processes
|
break # Exit to restart both processes
|
||||||
|
|
|
||||||
|
|
@ -413,9 +413,9 @@ def evals(
|
||||||
|
|
||||||
@cli.command(help="Launch Arcade AI locally for tool dev", rich_help_panel="Launch")
|
@cli.command(help="Launch Arcade AI locally for tool dev", rich_help_panel="Launch")
|
||||||
def dev(
|
def dev(
|
||||||
host: str = typer.Option("127.0.0.1", help="Host for the actor server.", show_default=True),
|
host: str = typer.Option("127.0.0.1", help="Host for the worker server.", show_default=True),
|
||||||
port: int = typer.Option(
|
port: int = typer.Option(
|
||||||
8002, "-p", "--port", help="Port for the actor server.", show_default=True
|
8002, "-p", "--port", help="Port for the worker server.", show_default=True
|
||||||
),
|
),
|
||||||
engine_config: str = typer.Option(
|
engine_config: str = typer.Option(
|
||||||
None, "-c", "--config", help="Path to the engine configuration file."
|
None, "-c", "--config", help="Path to the engine configuration file."
|
||||||
|
|
@ -426,7 +426,7 @@ def dev(
|
||||||
debug: bool = typer.Option(False, "-d", "--debug", help="Show debug information"),
|
debug: bool = typer.Option(False, "-d", "--debug", help="Show debug information"),
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Start both the actor and engine servers.
|
Start both the worker and engine servers.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
start_servers(host, port, engine_config, engine_env=env_file, debug=debug)
|
start_servers(host, port, engine_config, engine_env=env_file, debug=debug)
|
||||||
|
|
@ -436,8 +436,8 @@ def dev(
|
||||||
typer.Exit(code=1)
|
typer.Exit(code=1)
|
||||||
|
|
||||||
|
|
||||||
@cli.command(help="Start a local Arcade Actor server", rich_help_panel="Launch", hidden=True)
|
@cli.command(help="Start a local Arcade Worker server", rich_help_panel="Launch", hidden=True)
|
||||||
def actorup(
|
def workerup(
|
||||||
host: str = typer.Option(
|
host: str = typer.Option(
|
||||||
"127.0.0.1",
|
"127.0.0.1",
|
||||||
help="Host for the app, from settings by default.",
|
help="Host for the app, from settings by default.",
|
||||||
|
|
@ -449,7 +449,7 @@ def actorup(
|
||||||
disable_auth: bool = typer.Option(
|
disable_auth: bool = typer.Option(
|
||||||
False,
|
False,
|
||||||
"--no-auth",
|
"--no-auth",
|
||||||
help="Disable authentication for the actor. Not recommended for production.",
|
help="Disable authentication for the worker. Not recommended for production.",
|
||||||
show_default=True,
|
show_default=True,
|
||||||
),
|
),
|
||||||
otel_enable: bool = typer.Option(
|
otel_enable: bool = typer.Option(
|
||||||
|
|
@ -458,18 +458,18 @@ def actorup(
|
||||||
debug: bool = typer.Option(False, "--debug", "-d", help="Show debug information"),
|
debug: bool = typer.Option(False, "--debug", "-d", help="Show debug information"),
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Starts the actor with host, port, and reload options. Uses
|
Starts the worker with host, port, and reload options. Uses
|
||||||
Uvicorn as ASGI actor. Parameters allow runtime configuration.
|
Uvicorn as ASGI worker. Parameters allow runtime configuration.
|
||||||
"""
|
"""
|
||||||
from arcade.cli.serve import serve_default_actor
|
from arcade.cli.serve import serve_default_worker
|
||||||
|
|
||||||
try:
|
try:
|
||||||
serve_default_actor(
|
serve_default_worker(
|
||||||
host, port, disable_auth=disable_auth, enable_otel=otel_enable, debug=debug
|
host, port, disable_auth=disable_auth, enable_otel=otel_enable, debug=debug
|
||||||
)
|
)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
typer.Exit()
|
typer.Exit()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_message = f"❌ Failed to start Arcade Actor: {escape(str(e))}"
|
error_message = f"❌ Failed to start Arcade Worker: {escape(str(e))}"
|
||||||
console.print(error_message, style="bold red")
|
console.print(error_message, style="bold red")
|
||||||
typer.Exit(code=1)
|
typer.Exit(code=1)
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,8 @@ except ImportError:
|
||||||
"Uvicorn is not installed. Please install it using `pip install arcade-ai[fastapi]`."
|
"Uvicorn is not installed. Please install it using `pip install arcade-ai[fastapi]`."
|
||||||
)
|
)
|
||||||
|
|
||||||
from arcade.actor.fastapi.actor import FastAPIActor
|
|
||||||
from arcade.sdk import Toolkit
|
from arcade.sdk import Toolkit
|
||||||
|
from arcade.worker.fastapi.worker import FastAPIWorker
|
||||||
|
|
||||||
|
|
||||||
class InterceptHandler(logging.Handler):
|
class InterceptHandler(logging.Handler):
|
||||||
|
|
@ -80,7 +80,7 @@ async def lifespan(app: fastapi.FastAPI): # type: ignore[no-untyped-def]
|
||||||
logger.debug("Lifespan cancelled.")
|
logger.debug("Lifespan cancelled.")
|
||||||
|
|
||||||
|
|
||||||
def serve_default_actor(
|
def serve_default_worker(
|
||||||
host: str = "127.0.0.1",
|
host: str = "127.0.0.1",
|
||||||
port: int = 8002,
|
port: int = 8002,
|
||||||
disable_auth: bool = False,
|
disable_auth: bool = False,
|
||||||
|
|
@ -91,7 +91,7 @@ def serve_default_actor(
|
||||||
**kwargs: Any,
|
**kwargs: Any,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Get an instance of a FastAPI server with the Arcade Actor.
|
Get an instance of a FastAPI server with the Arcade Worker.
|
||||||
"""
|
"""
|
||||||
# Setup unified logging
|
# Setup unified logging
|
||||||
setup_logging(log_level=logging.DEBUG if debug else logging.INFO)
|
setup_logging(log_level=logging.DEBUG if debug else logging.INFO)
|
||||||
|
|
@ -105,27 +105,27 @@ def serve_default_actor(
|
||||||
num_tools = sum(len(tools) for tools in toolkit.tools.values())
|
num_tools = sum(len(tools) for tools in toolkit.tools.values())
|
||||||
logger.info(f" - {toolkit.name} ({toolkit.package_name}): {num_tools} tools")
|
logger.info(f" - {toolkit.name} ({toolkit.package_name}): {num_tools} tools")
|
||||||
|
|
||||||
actor_secret = os.environ.get("ARCADE_ACTOR_SECRET")
|
worker_secret = os.environ.get("ARCADE_WORKER_SECRET")
|
||||||
if not disable_auth and not actor_secret:
|
if not disable_auth and not worker_secret:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"Warning: ARCADE_ACTOR_SECRET environment variable is not set. Using 'dev' as the actor secret.",
|
"Warning: ARCADE_WORKER_SECRET environment variable is not set. Using 'dev' as the worker secret.",
|
||||||
)
|
)
|
||||||
actor_secret = actor_secret or "dev"
|
worker_secret = worker_secret or "dev"
|
||||||
|
|
||||||
app = fastapi.FastAPI(
|
app = fastapi.FastAPI(
|
||||||
title="Arcade AI Actor",
|
title="Arcade AI Worker",
|
||||||
description="Arcade AI default Actor implementation using FastAPI.",
|
description="Arcade AI default Worker implementation using FastAPI.",
|
||||||
version="0.1.0",
|
version="0.1.0",
|
||||||
lifespan=lifespan, # Use custom lifespan to catch errors, notably KeyboardInterrupt (Ctrl+C)
|
lifespan=lifespan, # Use custom lifespan to catch errors, notably KeyboardInterrupt (Ctrl+C)
|
||||||
)
|
)
|
||||||
|
|
||||||
otel_handler = OTELHandler(app, enable=enable_otel)
|
otel_handler = OTELHandler(app, enable=enable_otel)
|
||||||
|
|
||||||
actor = FastAPIActor(
|
worker = FastAPIWorker(
|
||||||
app, secret=actor_secret, disable_auth=disable_auth, otel_meter=otel_handler.get_meter()
|
app, secret=worker_secret, disable_auth=disable_auth, otel_meter=otel_handler.get_meter()
|
||||||
)
|
)
|
||||||
for toolkit in toolkits:
|
for toolkit in toolkits:
|
||||||
actor.register_toolkit(toolkit)
|
worker.register_toolkit(toolkit)
|
||||||
|
|
||||||
logger.info("Starting FastAPI server...")
|
logger.info("Starting FastAPI server...")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@ class MaterializedTool(BaseModel):
|
||||||
|
|
||||||
|
|
||||||
class ToolCatalog(BaseModel):
|
class ToolCatalog(BaseModel):
|
||||||
"""Singleton class that holds all tools for a given actor"""
|
"""Singleton class that holds all tools for a given worker"""
|
||||||
|
|
||||||
_tools: dict[FullyQualifiedName, MaterializedTool] = {}
|
_tools: dict[FullyQualifiedName, MaterializedTool] = {}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ class OTELHandler:
|
||||||
"🔎 Initializing OpenTelemetry. Use environment variables to configure the connection"
|
"🔎 Initializing OpenTelemetry. Use environment variables to configure the connection"
|
||||||
)
|
)
|
||||||
self.resource = Resource(
|
self.resource = Resource(
|
||||||
attributes={SERVICE_NAME: "arcade-actor", "environment": self.environment}
|
attributes={SERVICE_NAME: "arcade-worker", "environment": self.environment}
|
||||||
)
|
)
|
||||||
|
|
||||||
self._init_tracer()
|
self._init_tracer()
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,10 @@ ARCADE_API_PORT=9099
|
||||||
# ANTHROPIC_API_KEY=
|
# ANTHROPIC_API_KEY=
|
||||||
# GROQ_API_KEY=
|
# GROQ_API_KEY=
|
||||||
|
|
||||||
### Actor Configuration ###
|
### Worker Configuration ###
|
||||||
|
|
||||||
ARCADE_ACTOR_URI=http://localhost:8002
|
ARCADE_WORKER_URI=http://localhost:8002
|
||||||
# ARCADE_ACTOR_SECRET=
|
# ARCADE_WORKER_SECRET=
|
||||||
|
|
||||||
### Token Storage ###
|
### Token Storage ###
|
||||||
# REDIS_HOST=
|
# REDIS_HOST=
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@
|
||||||
```bash
|
```bash
|
||||||
arcade login
|
arcade login
|
||||||
```
|
```
|
||||||
4. Start the Arcade Engine and Actor:
|
4. Start the Arcade Engine and Worker:
|
||||||
```bash
|
```bash
|
||||||
arcade dev
|
arcade dev
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -19,19 +19,19 @@ class SigningAlgorithm(str, Enum):
|
||||||
HS256 = "HS256"
|
HS256 = "HS256"
|
||||||
|
|
||||||
|
|
||||||
def validate_engine_token(actor_secret: str, token: str) -> TokenValidationResult:
|
def validate_engine_token(worker_secret: str, token: str) -> TokenValidationResult:
|
||||||
try:
|
try:
|
||||||
payload = jwt.decode(
|
payload = jwt.decode(
|
||||||
token,
|
token,
|
||||||
actor_secret,
|
worker_secret,
|
||||||
algorithms=[SigningAlgorithm.HS256],
|
algorithms=[SigningAlgorithm.HS256],
|
||||||
verify=True,
|
verify=True,
|
||||||
audience="actor",
|
audience="worker",
|
||||||
)
|
)
|
||||||
except jwt.InvalidSignatureError as e:
|
except jwt.InvalidSignatureError as e:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"Invalid signature. Is the Arcade Engine configured with the Actor secret '%s'?",
|
"Invalid signature. Is the Arcade Engine configured with the Worker secret '%s'?",
|
||||||
actor_secret,
|
worker_secret,
|
||||||
)
|
)
|
||||||
return TokenValidationResult(valid=False, error=str(e))
|
return TokenValidationResult(valid=False, error=str(e))
|
||||||
|
|
||||||
|
|
@ -7,13 +7,6 @@ from typing import Any, Callable, ClassVar
|
||||||
from opentelemetry import trace
|
from opentelemetry import trace
|
||||||
from opentelemetry.metrics import Meter
|
from opentelemetry.metrics import Meter
|
||||||
|
|
||||||
from arcade.actor.core.common import Actor, Router
|
|
||||||
from arcade.actor.core.components import (
|
|
||||||
ActorComponent,
|
|
||||||
CallToolComponent,
|
|
||||||
CatalogComponent,
|
|
||||||
HealthCheckComponent,
|
|
||||||
)
|
|
||||||
from arcade.core.catalog import ToolCatalog, Toolkit
|
from arcade.core.catalog import ToolCatalog, Toolkit
|
||||||
from arcade.core.executor import ToolExecutor
|
from arcade.core.executor import ToolExecutor
|
||||||
from arcade.core.schema import (
|
from arcade.core.schema import (
|
||||||
|
|
@ -21,19 +14,26 @@ from arcade.core.schema import (
|
||||||
ToolCallResponse,
|
ToolCallResponse,
|
||||||
ToolDefinition,
|
ToolDefinition,
|
||||||
)
|
)
|
||||||
|
from arcade.worker.core.common import Router, Worker
|
||||||
|
from arcade.worker.core.components import (
|
||||||
|
CallToolComponent,
|
||||||
|
CatalogComponent,
|
||||||
|
HealthCheckComponent,
|
||||||
|
WorkerComponent,
|
||||||
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class BaseActor(Actor):
|
class BaseWorker(Worker):
|
||||||
"""
|
"""
|
||||||
A base actor class that provides a default implementation for registering tools and invoking them.
|
A base worker class that provides a default implementation for registering tools and invoking them.
|
||||||
Actor implementations for specific web frameworks will inherit from this class.
|
Worker implementations for specific web frameworks will inherit from this class.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
base_path = "/actor" # By default, prefix all our routes with /actor
|
base_path = "/worker" # By default, prefix all our routes with /worker
|
||||||
|
|
||||||
default_components: ClassVar[tuple[type[ActorComponent], ...]] = (
|
default_components: ClassVar[tuple[type[WorkerComponent], ...]] = (
|
||||||
CatalogComponent,
|
CatalogComponent,
|
||||||
CallToolComponent,
|
CallToolComponent,
|
||||||
HealthCheckComponent,
|
HealthCheckComponent,
|
||||||
|
|
@ -43,14 +43,14 @@ class BaseActor(Actor):
|
||||||
self, secret: str | None = None, disable_auth: bool = False, otel_meter: Meter | None = None
|
self, secret: str | None = None, disable_auth: bool = False, otel_meter: Meter | None = None
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Initialize the BaseActor with an empty ToolCatalog.
|
Initialize the BaseWorker with an empty ToolCatalog.
|
||||||
If no secret is provided, the actor will use the ARCADE_ACTOR_SECRET environment variable.
|
If no secret is provided, the worker will use the ARCADE_WORKER_SECRET environment variable.
|
||||||
"""
|
"""
|
||||||
self.catalog = ToolCatalog()
|
self.catalog = ToolCatalog()
|
||||||
self.disable_auth = disable_auth
|
self.disable_auth = disable_auth
|
||||||
if disable_auth:
|
if disable_auth:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"Warning: Actor is running without authentication. Not recommended for production."
|
"Warning: Worker is running without authentication. Not recommended for production."
|
||||||
)
|
)
|
||||||
|
|
||||||
self.secret = self._set_secret(secret, disable_auth)
|
self.secret = self._set_secret(secret, disable_auth)
|
||||||
|
|
@ -71,12 +71,12 @@ class BaseActor(Actor):
|
||||||
return secret
|
return secret
|
||||||
|
|
||||||
# If secret is not provided, try to get it from environment variables
|
# If secret is not provided, try to get it from environment variables
|
||||||
env_secret = os.environ.get("ARCADE_ACTOR_SECRET")
|
env_secret = os.environ.get("ARCADE_WORKER_SECRET")
|
||||||
if env_secret:
|
if env_secret:
|
||||||
return env_secret
|
return env_secret
|
||||||
|
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"No secret provided for actor. Set the ARCADE_ACTOR_SECRET environment variable."
|
"No secret provided for worker. Set the ARCADE_WORKER_SECRET environment variable."
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_catalog(self) -> list[ToolDefinition]:
|
def get_catalog(self) -> list[ToolDefinition]:
|
||||||
|
|
@ -173,7 +173,7 @@ class BaseActor(Actor):
|
||||||
|
|
||||||
def health_check(self) -> dict[str, Any]:
|
def health_check(self) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Provide a health check that serves as a heartbeat of actor health.
|
Provide a health check that serves as a heartbeat of worker health.
|
||||||
"""
|
"""
|
||||||
return {"status": "ok", "tool_count": len(self.catalog)}
|
return {"status": "ok", "tool_count": len(self.catalog)}
|
||||||
|
|
||||||
|
|
@ -8,7 +8,7 @@ from arcade.core.schema import ToolCallRequest, ToolCallResponse, ToolDefinition
|
||||||
|
|
||||||
class RequestData(BaseModel):
|
class RequestData(BaseModel):
|
||||||
"""
|
"""
|
||||||
The raw data for a request to an actor.
|
The raw data for a request to a worker.
|
||||||
This is not intended to represent everything about an HTTP request,
|
This is not intended to represent everything about an HTTP request,
|
||||||
but just the essential info a framework integration will need to extract from the request.
|
but just the essential info a framework integration will need to extract from the request.
|
||||||
"""
|
"""
|
||||||
|
|
@ -23,7 +23,7 @@ class RequestData(BaseModel):
|
||||||
|
|
||||||
class Router(ABC):
|
class Router(ABC):
|
||||||
"""
|
"""
|
||||||
A router is responsible for adding routes to the underlying framework hosting the actor.
|
A router is responsible for adding routes to the underlying framework hosting the worker.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
|
|
@ -36,37 +36,37 @@ class Router(ABC):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Actor(ABC):
|
class Worker(ABC):
|
||||||
"""
|
"""
|
||||||
An Actor represents a collection of tools that is hosted inside a web framework
|
A Worker represents a collection of tools that is hosted inside a web framework
|
||||||
and can be called by an Engine.
|
and can be called by an Engine.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_catalog(self) -> list[ToolDefinition]:
|
def get_catalog(self) -> list[ToolDefinition]:
|
||||||
"""
|
"""
|
||||||
Get the catalog of tools available in the actor.
|
Get the catalog of tools available in the worker.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
async def call_tool(self, request: ToolCallRequest) -> ToolCallResponse:
|
async def call_tool(self, request: ToolCallRequest) -> ToolCallResponse:
|
||||||
"""
|
"""
|
||||||
Send a request to call a tool to the Actor
|
Send a request to call a tool to the Worker
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def health_check(self) -> dict[str, Any]:
|
def health_check(self) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Perform a health check of the actor
|
Perform a health check of the worker
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ActorComponent(ABC):
|
class WorkerComponent(ABC):
|
||||||
def __init__(self, actor: Actor) -> None:
|
def __init__(self, worker: Worker) -> None:
|
||||||
self.actor = actor
|
self.worker = worker
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def register(self, router: Router) -> None:
|
def register(self, router: Router) -> None:
|
||||||
|
|
@ -2,13 +2,13 @@ from typing import Any
|
||||||
|
|
||||||
from opentelemetry import trace
|
from opentelemetry import trace
|
||||||
|
|
||||||
from arcade.actor.core.common import Actor, ActorComponent, RequestData, Router
|
|
||||||
from arcade.core.schema import ToolCallRequest, ToolCallResponse, ToolDefinition
|
from arcade.core.schema import ToolCallRequest, ToolCallResponse, ToolDefinition
|
||||||
|
from arcade.worker.core.common import RequestData, Router, Worker, WorkerComponent
|
||||||
|
|
||||||
|
|
||||||
class CatalogComponent(ActorComponent):
|
class CatalogComponent(WorkerComponent):
|
||||||
def __init__(self, actor: Actor) -> None:
|
def __init__(self, worker: Worker) -> None:
|
||||||
self.actor = actor
|
self.worker = worker
|
||||||
|
|
||||||
def register(self, router: Router) -> None:
|
def register(self, router: Router) -> None:
|
||||||
"""
|
"""
|
||||||
|
|
@ -22,12 +22,12 @@ class CatalogComponent(ActorComponent):
|
||||||
"""
|
"""
|
||||||
tracer = trace.get_tracer(__name__)
|
tracer = trace.get_tracer(__name__)
|
||||||
with tracer.start_as_current_span("Catalog"):
|
with tracer.start_as_current_span("Catalog"):
|
||||||
return self.actor.get_catalog()
|
return self.worker.get_catalog()
|
||||||
|
|
||||||
|
|
||||||
class CallToolComponent(ActorComponent):
|
class CallToolComponent(WorkerComponent):
|
||||||
def __init__(self, actor: Actor) -> None:
|
def __init__(self, worker: Worker) -> None:
|
||||||
self.actor = actor
|
self.worker = worker
|
||||||
|
|
||||||
def register(self, router: Router) -> None:
|
def register(self, router: Router) -> None:
|
||||||
"""
|
"""
|
||||||
|
|
@ -43,12 +43,12 @@ class CallToolComponent(ActorComponent):
|
||||||
with tracer.start_as_current_span("CallTool"):
|
with tracer.start_as_current_span("CallTool"):
|
||||||
call_tool_request_data = request.body_json
|
call_tool_request_data = request.body_json
|
||||||
call_tool_request = ToolCallRequest.model_validate(call_tool_request_data)
|
call_tool_request = ToolCallRequest.model_validate(call_tool_request_data)
|
||||||
return await self.actor.call_tool(call_tool_request)
|
return await self.worker.call_tool(call_tool_request)
|
||||||
|
|
||||||
|
|
||||||
class HealthCheckComponent(ActorComponent):
|
class HealthCheckComponent(WorkerComponent):
|
||||||
def __init__(self, actor: Actor) -> None:
|
def __init__(self, worker: Worker) -> None:
|
||||||
self.actor = actor
|
self.worker = worker
|
||||||
|
|
||||||
def register(self, router: Router) -> None:
|
def register(self, router: Router) -> None:
|
||||||
"""
|
"""
|
||||||
|
|
@ -62,4 +62,4 @@ class HealthCheckComponent(ActorComponent):
|
||||||
"""
|
"""
|
||||||
tracer = trace.get_tracer(__name__)
|
tracer = trace.get_tracer(__name__)
|
||||||
with tracer.start_as_current_span("HealthCheck"):
|
with tracer.start_as_current_span("HealthCheck"):
|
||||||
return self.actor.health_check()
|
return self.worker.health_check()
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
from fastapi import HTTPException
|
from fastapi import HTTPException
|
||||||
from fastapi.security import HTTPAuthorizationCredentials
|
from fastapi.security import HTTPAuthorizationCredentials
|
||||||
|
|
||||||
from arcade.actor.core.auth import validate_engine_token
|
from arcade.worker.core.auth import validate_engine_token
|
||||||
|
|
||||||
|
|
||||||
# Dependency function to validate JWT
|
# Dependency function to validate JWT
|
||||||
async def validate_engine_request(
|
async def validate_engine_request(
|
||||||
actor_secret: str,
|
worker_secret: str,
|
||||||
credentials: HTTPAuthorizationCredentials,
|
credentials: HTTPAuthorizationCredentials,
|
||||||
) -> None:
|
) -> None:
|
||||||
jwt: str = credentials.credentials
|
jwt: str = credentials.credentials
|
||||||
validation_result = validate_engine_token(actor_secret, jwt)
|
validation_result = validate_engine_token(worker_secret, jwt)
|
||||||
|
|
||||||
if not validation_result.valid:
|
if not validation_result.valid:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
|
|
@ -5,18 +5,18 @@ from fastapi import Depends, FastAPI, Request
|
||||||
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
||||||
from opentelemetry.metrics import Meter
|
from opentelemetry.metrics import Meter
|
||||||
|
|
||||||
from arcade.actor.core.base import (
|
from arcade.worker.core.base import (
|
||||||
BaseActor,
|
BaseWorker,
|
||||||
Router,
|
Router,
|
||||||
)
|
)
|
||||||
from arcade.actor.core.common import RequestData
|
from arcade.worker.core.common import RequestData
|
||||||
from arcade.actor.fastapi.auth import validate_engine_request
|
from arcade.worker.fastapi.auth import validate_engine_request
|
||||||
from arcade.actor.utils import is_async_callable
|
from arcade.worker.utils import is_async_callable
|
||||||
|
|
||||||
|
|
||||||
class FastAPIActor(BaseActor):
|
class FastAPIWorker(BaseWorker):
|
||||||
"""
|
"""
|
||||||
An Arcade Actor that is hosted inside a FastAPI app.
|
An Arcade Worker that is hosted inside a FastAPI app.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
|
@ -28,8 +28,8 @@ class FastAPIActor(BaseActor):
|
||||||
otel_meter: Meter | None = None,
|
otel_meter: Meter | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Initialize the FastAPIActor with a FastAPI app instance.
|
Initialize the FastAPIWorker with a FastAPI app instance.
|
||||||
If no secret is provided, the actor will use the ARCADE_ACTOR_SECRET environment variable.
|
If no secret is provided, the worker will use the ARCADE_WORKER_SECRET environment variable.
|
||||||
"""
|
"""
|
||||||
super().__init__(secret, disable_auth, otel_meter)
|
super().__init__(secret, disable_auth, otel_meter)
|
||||||
self.app = app
|
self.app = app
|
||||||
|
|
@ -41,28 +41,28 @@ security = HTTPBearer() # Authorization: Bearer <xxx>
|
||||||
|
|
||||||
|
|
||||||
class FastAPIRouter(Router):
|
class FastAPIRouter(Router):
|
||||||
def __init__(self, app: FastAPI, actor: BaseActor) -> None:
|
def __init__(self, app: FastAPI, worker: BaseWorker) -> None:
|
||||||
self.app = app
|
self.app = app
|
||||||
self.actor = actor
|
self.worker = worker
|
||||||
|
|
||||||
def _wrap_handler(self, handler: Callable, require_auth: bool = True) -> Callable:
|
def _wrap_handler(self, handler: Callable, require_auth: bool = True) -> Callable:
|
||||||
"""
|
"""
|
||||||
Wrap the handler to handle FastAPI-specific request and response.
|
Wrap the handler to handle FastAPI-specific request and response.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
use_auth_for_route = not self.actor.disable_auth and require_auth
|
use_auth_for_route = not self.worker.disable_auth and require_auth
|
||||||
|
|
||||||
def call_validate_engine_request(actor_secret: str) -> Callable:
|
def call_validate_engine_request(worker_secret: str) -> Callable:
|
||||||
async def dependency(
|
async def dependency(
|
||||||
credentials: HTTPAuthorizationCredentials = Depends(security),
|
credentials: HTTPAuthorizationCredentials = Depends(security),
|
||||||
) -> None:
|
) -> None:
|
||||||
await validate_engine_request(actor_secret, credentials)
|
await validate_engine_request(worker_secret, credentials)
|
||||||
|
|
||||||
return dependency
|
return dependency
|
||||||
|
|
||||||
async def wrapped_handler(
|
async def wrapped_handler(
|
||||||
request: Request,
|
request: Request,
|
||||||
_: None = Depends(call_validate_engine_request(self.actor.secret))
|
_: None = Depends(call_validate_engine_request(self.worker.secret))
|
||||||
if use_auth_for_route
|
if use_auth_for_route
|
||||||
else None,
|
else None,
|
||||||
) -> Any:
|
) -> Any:
|
||||||
|
|
@ -87,7 +87,7 @@ class FastAPIRouter(Router):
|
||||||
Add a route to the FastAPI application.
|
Add a route to the FastAPI application.
|
||||||
"""
|
"""
|
||||||
self.app.add_api_route(
|
self.app.add_api_route(
|
||||||
f"{self.actor.base_path}/{endpoint_path}",
|
f"{self.worker.base_path}/{endpoint_path}",
|
||||||
self._wrap_handler(handler, require_auth),
|
self._wrap_handler(handler, require_auth),
|
||||||
methods=[method],
|
methods=[method],
|
||||||
)
|
)
|
||||||
|
|
@ -38,7 +38,7 @@ def test_init_with_enable_true(
|
||||||
handler = OTELHandler(app, enable=True)
|
handler = OTELHandler(app, enable=True)
|
||||||
|
|
||||||
# Verify that the resource is set correctly
|
# Verify that the resource is set correctly
|
||||||
assert handler.resource.attributes["service.name"] == "arcade-actor"
|
assert handler.resource.attributes["service.name"] == "arcade-worker"
|
||||||
assert "environment" in handler.resource.attributes
|
assert "environment" in handler.resource.attributes
|
||||||
|
|
||||||
# Verify that initialization methods are called
|
# Verify that initialization methods are called
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ RUN python -m pip install -r ./requirements.txt
|
||||||
# Expose the port
|
# Expose the port
|
||||||
EXPOSE $PORT
|
EXPOSE $PORT
|
||||||
|
|
||||||
# Run the arcade actorup (hidden cli command)
|
# Run the arcade workerup (hidden cli command)
|
||||||
COPY docker/start.sh /app/start.sh
|
COPY docker/start.sh /app/start.sh
|
||||||
RUN chmod +x /app/start.sh
|
RUN chmod +x /app/start.sh
|
||||||
CMD ["/app/start.sh"]
|
CMD ["/app/start.sh"]
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ ECR_ENDPOINT ?= 471112909428.dkr.ecr.us-east-1.amazonaws.com
|
||||||
VERSION ?= 0.1.0.dev0
|
VERSION ?= 0.1.0.dev0
|
||||||
COMMIT ?= $(shell git describe --dirty --always --abbrev=15)
|
COMMIT ?= $(shell git describe --dirty --always --abbrev=15)
|
||||||
BUILD_DATE ?= $(shell date -u +"%Y-%m-%dT%H:%M:%SZ")
|
BUILD_DATE ?= $(shell date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||||
IMAGE_NAME ?= actor
|
IMAGE_NAME ?= worker
|
||||||
PORT ?= 8002
|
PORT ?= 8002
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -57,7 +57,7 @@ ecr-login: # Login to ECR
|
||||||
|
|
||||||
.PHONY: help
|
.PHONY: help
|
||||||
help:
|
help:
|
||||||
@echo "🛠️ Actor Docker Commands:\n"
|
@echo "🛠️ Worker Docker Commands:\n"
|
||||||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
||||||
|
|
||||||
.DEFAULT_GOAL := help
|
.DEFAULT_GOAL := help
|
||||||
|
|
|
||||||
|
|
@ -7,4 +7,4 @@ for toolkit in $(echo $TOOLKITS | tr "," " "); do
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "Starting arcade..."
|
echo "Starting arcade..."
|
||||||
arcade actorup --host $HOST --port $PORT $([ "$OTEL_ENABLE" = "true" ] && echo "--otel-enable")
|
arcade workerup --host $HOST --port $PORT $([ "$OTEL_ENABLE" = "true" ] && echo "--otel-enable")
|
||||||
|
|
|
||||||
|
|
@ -5,16 +5,16 @@ from fastapi import FastAPI, HTTPException
|
||||||
from openai import AsyncOpenAI
|
from openai import AsyncOpenAI
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from arcade.actor.fastapi.actor import FastAPIActor
|
|
||||||
from arcade.sdk import Toolkit
|
from arcade.sdk import Toolkit
|
||||||
|
from arcade.worker.fastapi.worker import FastAPIWorker
|
||||||
|
|
||||||
client = AsyncOpenAI(api_key=os.environ["ARCADE_API_KEY"], base_url="http://localhost:9099/v1")
|
client = AsyncOpenAI(api_key=os.environ["ARCADE_API_KEY"], base_url="http://localhost:9099/v1")
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
|
||||||
actor_secret = os.environ["ARCADE_ACTOR_SECRET"]
|
worker_secret = os.environ["ARCADE_WORKER_SECRET"]
|
||||||
actor = FastAPIActor(app, secret=actor_secret)
|
worker = FastAPIWorker(app, secret=worker_secret)
|
||||||
actor.register_toolkit(Toolkit.from_module(arcade_math))
|
worker.register_toolkit(Toolkit.from_module(arcade_math))
|
||||||
|
|
||||||
|
|
||||||
class ChatRequest(BaseModel):
|
class ChatRequest(BaseModel):
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue