agentify-toolkit 0.4.3a1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
agentify/__init__.py ADDED
@@ -0,0 +1,41 @@
1
+ """
2
+ Copyright 2026 Backplane Software
3
+ Author: Lewis Sheridan
4
+ License: Apache License, Version 2.0
5
+ Description: Lightweight Python toolkit to build multi-model AI agents.
6
+ """
7
+
8
+ from .agentify import Agent
9
+ from .agents import create_agent, create_agents
10
+ from .specs import load_agent_specs
11
+ from .cli_ui import show_agent_menu
12
+ from .web import run_web_ui
13
+
14
+ __all__ = [
15
+ "Agent",
16
+ "load_agent_specs",
17
+ "create_agent",
18
+ "create_agents",
19
+ "show_agent_menu"
20
+ "run_web_ui"
21
+ ]
22
+
23
+ import os
24
+
25
+ try:
26
+ from importlib.metadata import version, PackageNotFoundError
27
+
28
+ try:
29
+ __version__ = version("agentify-toolkit")
30
+ except PackageNotFoundError:
31
+ # Fallback when running locally from src/ (not installed)
32
+ __version__ = "0.0.0-dev"
33
+ except ImportError:
34
+ # Python <3.8 fallback
35
+ __version__ = "0.0.0-dev"
36
+
37
+ # Optional: mark as dev if running in source directory
38
+ if os.path.exists(os.path.join(os.path.dirname(__file__), "..", "pyproject.toml")):
39
+ # Indicates a local dev environment
40
+ if not __version__.endswith("-dev"):
41
+ __version__ += "-dev"
agentify/agentify.py ADDED
@@ -0,0 +1,61 @@
1
+ """
2
+ Copyright 2026 Backplane Software
3
+ Licensed under the Apache License, Version 2.0
4
+ Author: Lewis Sheridan
5
+ Description: Agentify class to build multi-model AI Agents
6
+ """
7
+
8
+ from dataclasses import dataclass, field
9
+ from typing import Optional
10
+
11
+ from agentify.providers import run_openai, run_anthropic, run_google, run_bedrock, run_x
12
+ from rich.console import Console
13
+ from rich.panel import Panel
14
+ from rich.prompt import Prompt
15
+
16
+
17
+ @dataclass
18
+ class Agent:
19
+ name: str
20
+ description: str
21
+ provider: str
22
+ model_id: str
23
+ role: str
24
+ version: Optional[str] = field(default="0.0.0")
25
+
26
+
27
+ def get_model(self) -> str:
28
+ return self.model_id
29
+
30
+ def run(self, user_prompt: str) -> str:
31
+ match self.provider.lower():
32
+ case "openai":
33
+ return run_openai(self.model_id, user_prompt)
34
+ case "anthropic":
35
+ return run_anthropic(self.model_id, user_prompt)
36
+ case "google":
37
+ return run_google(self.model_id, user_prompt)
38
+ case "bedrock":
39
+ return run_bedrock(self.model_id, user_prompt)
40
+ case "x":
41
+ return run_x(self.model_id, user_prompt)
42
+ case _:
43
+ raise ValueError(f"Unsupported provider: {self.provider}")
44
+
45
+ def chat(agent: "Agent"):
46
+ console = Console()
47
+ console.print(Panel(
48
+ f"[bold cyan][/bold cyan]\n[bold cyan]{agent.name.upper()} [/bold cyan] [dim]{agent.version}[/dim]\nRole: {agent.description}\nUsing [yellow]{agent.model_id}[/yellow] by {agent.provider}",
49
+ border_style="cyan"
50
+ ))
51
+ while True:
52
+ prompt = Prompt.ask("\nEnter your prompt ('/exit' to quit)").strip()
53
+ if prompt.lower() in ["/exit", "quit"]:
54
+ console.print("[yellow]Exiting. Goodbye![/yellow]")
55
+ break
56
+
57
+
58
+ full_prompt = f"You must assume the role of {agent.role} when responding to this prompt:\n\n{prompt}"
59
+ with console.status(f"[blue]Sending prompt to model... {agent.name} is thinking...[/blue]", spinner="dots"):
60
+ response = agent.run(full_prompt)
61
+ console.print(Panel.fit(response, title="Agent Response", border_style="green"))
agentify/agents.py ADDED
@@ -0,0 +1,37 @@
1
+ # Copyright 2026 Backplane Software
2
+ # Licensed under the Apache License, Version 2.0
3
+
4
+ from agentify import Agent
5
+ import os
6
+
7
+ def create_agents(specs: list) -> dict[str, Agent]:
8
+ agents = {}
9
+ for spec in specs:
10
+ agent = create_agent(spec)
11
+ agents[agent.name] = agent
12
+ return agents
13
+
14
+ def create_agent(spec: dict, provider: str = None, model: str = None) -> Agent:
15
+ """
16
+ Create an Agent from a YAML/spec dictionary, optionally overriding model or provider.
17
+ """
18
+ name = spec.get("name")
19
+ description = spec.get("description")
20
+ version = spec.get("version")
21
+ role = spec.get("role")
22
+
23
+ model_spec = spec.get("model", {})
24
+ model_id = model or model_spec.get("id")
25
+ provider = provider or model_spec.get("provider")
26
+ api_key_env = model_spec.get("api_key_env")
27
+
28
+ if api_key_env:
29
+ api_key = os.getenv(api_key_env)
30
+ # if not api_key:
31
+ # raise EnvironmentError(
32
+ # f"Environment variables '{api_key_env}' is not set"
33
+ # )
34
+
35
+ agent = Agent(name=name, provider=provider, model_id=model_id, role=role, description=description, version=version)
36
+
37
+ return agent
agentify/cli.py ADDED
@@ -0,0 +1,294 @@
1
+ # Copyright 2026 Backplane Software
2
+ # Licensed under the Apache License, Version 2.0
3
+
4
+ import click
5
+ from pathlib import Path
6
+ import yaml
7
+ import os
8
+
9
+ from agentify import __version__
10
+ from .specs import load_agent_specs
11
+ from .agents import create_agent, create_agents
12
+
13
+ from .cli_ui import show_agent_menu
14
+ from .cli_config import set_server, get_server, add_provider, remove_provider, list_providers
15
+ from .runtime_client import list_agents, upload_agent, delete_agent
16
+
17
+ from .web import run_web_ui
18
+
19
+ @click.group()
20
+ @click.version_option(version=__version__, prog_name="Agentify")
21
+ def main():
22
+ """Agentify - Declarative AI Agents and Runtime Management"""
23
+ pass
24
+
25
+ # -----------------------------
26
+ # Run local agents (existing logic)
27
+ # -----------------------------
28
+ @main.command()
29
+ @click.argument("path", required=False)
30
+ @click.option("--model", type=str, help="Override the model ID at runtime")
31
+ @click.option("--provider", type=str, help="Override the LLM provider at runtime")
32
+ @click.option("--server", type=str, help="Optional: run on a remote server instead of local")
33
+ @click.option("--web", is_flag=True, help="Run agent with web UI")
34
+ @click.option("--port", type=int, help="Set server port")
35
+ def run(path, provider, model, server, web, port):
36
+ """
37
+ Run an agent YAML file or a folder containing agent YAMLs.
38
+
39
+ PATH can be:
40
+ - A single YAML file → runs that agent directly
41
+ - A folder containing YAML files → presents a menu to select an agent
42
+ """
43
+ # Determine target path
44
+ agent_path = path or "./agents"
45
+ path = Path(agent_path)
46
+ click.echo(f"Loading agents from: {path}")
47
+
48
+ # If server override is provided, run via runtime API
49
+ if server:
50
+ if not path.is_file():
51
+ raise click.BadParameter("Remote run currently only supports a single YAML file")
52
+ resp = upload_agent(server, str(path))
53
+ click.echo(f"Agent uploaded and executed on server {server}: {resp}")
54
+ return
55
+
56
+ # ----- Local / programmatic agent logic -----
57
+ if path.is_file():
58
+ # Load YAML File
59
+ with open(path, "r") as f:
60
+ spec = yaml.safe_load(f)
61
+
62
+ agent = create_agent(spec, provider=provider, model=model)
63
+
64
+ if web:
65
+ run_web_ui(agent, port=port)
66
+ else:
67
+ agent.chat()
68
+
69
+ elif path.is_dir():
70
+ # Multi-agent mode
71
+ specs = load_agent_specs(path)
72
+ agents = create_agents(specs)
73
+ agent = show_agent_menu(agents)
74
+ agent.chat()
75
+ else:
76
+ raise click.BadParameter(f"Path does not exist: {path}")
77
+
78
+
79
+ # -----------------------------
80
+ # List local agents (interactive)
81
+ # -----------------------------
82
+ @main.command()
83
+ @click.argument("path", required=False)
84
+ def list(path):
85
+ """
86
+ List agents in a folder and select one to run (interactive TUI)
87
+ """
88
+ agent_path = path or "./agents"
89
+ path = Path(agent_path)
90
+ click.echo(f"Listing agents from: {path}")
91
+
92
+ if not path.is_dir():
93
+ raise click.BadParameter(f"Path is not a directory: {path}")
94
+
95
+ specs = load_agent_specs(path)
96
+ if not specs:
97
+ click.echo("No agent YAML files found.")
98
+ return
99
+
100
+ agents = create_agents(specs)
101
+ agent = show_agent_menu(agents)
102
+ agent.chat()
103
+
104
+
105
+ # -----------------------------
106
+ # Server configuration
107
+ # -----------------------------
108
+ @main.group()
109
+ def server():
110
+ """Manage default runtime server configuration"""
111
+ pass
112
+
113
+ @server.command("set")
114
+ @click.argument("url")
115
+ def server_set(url):
116
+ """Set the default runtime server"""
117
+ set_server(url)
118
+
119
+ @server.command("show")
120
+ def server_show():
121
+ """Show the current default runtime server"""
122
+ url = get_server()
123
+ if url:
124
+ click.echo(f"Default server: {url}")
125
+ else:
126
+ click.echo("No server configured.")
127
+
128
+ @main.group()
129
+ def config():
130
+ """View or manage Agentify configuration"""
131
+ pass
132
+
133
+ @config.command("show")
134
+ def config_show():
135
+ """Show current Agentify configuration"""
136
+ import json
137
+ from agentify.cli_config import get_server
138
+
139
+ config_data = {
140
+ "server": get_server()
141
+ }
142
+
143
+ click.echo(json.dumps(config_data, indent=4))
144
+
145
+
146
+ # -----------------------------
147
+ # Runtime server commands
148
+ # -----------------------------
149
+ @main.group()
150
+ def runtime():
151
+ """Manage agents on a remote runtime server"""
152
+ pass
153
+
154
+ @runtime.command("list")
155
+ @click.option("--server", default=None, help="Override default server URL")
156
+ def show_server_agents(server):
157
+ """List agents running on the runtime server"""
158
+ url = server or get_server()
159
+ if not url:
160
+ click.echo("No server configured. Use 'agentify server set <url>'")
161
+ return
162
+
163
+ agents = list_agents(url)
164
+ click.echo(f"{'NAME':20} {'STATUS':10} {'PROVIDER':10} {'MODEL'}")
165
+ for a in agents:
166
+ click.echo(f"{a['name']:20} {a['status']:10} {a['provider']:10} {a['model']}")
167
+
168
+ @runtime.command("show")
169
+ @click.argument("agent_name")
170
+ @click.option("--server", default=None, help="Override default server URL")
171
+ def runtime_show(agent_name, server):
172
+ """Show details of a specific agent on the runtime server"""
173
+ url = server or get_server()
174
+ if not url:
175
+ click.echo("No server configured. Use 'agentify server set <url>'")
176
+ return
177
+
178
+ agent = list_agents(url, filter_name=agent_name) # or implement get_agent_details(agent_name)
179
+ if not agent:
180
+ click.echo(f"Agent not found: {agent_name}")
181
+ return
182
+
183
+ # Print detailed info
184
+ for a in agent:
185
+ click.echo(f"Name: {a['name']}")
186
+ click.echo(f"Status: {a['status']}")
187
+ click.echo(f"Provider: {a['provider']}")
188
+ click.echo(f"Model: {a['model']}")
189
+ click.echo(f"Version: {a.get('version', 'N/A')}")
190
+ click.echo("-" * 40)
191
+
192
+ @runtime.command("load")
193
+ @click.argument("path")
194
+ @click.option("--server", default=None, help="Override default server URL")
195
+ def upload(path, server):
196
+ """Upload an agent YAML file to the runtime server"""
197
+ url = server or get_server()
198
+ if not url:
199
+ click.echo("No server configured. Use 'agentify server set <url>'")
200
+ return
201
+ resp = upload_agent(url, path)
202
+ click.echo(f"Uploaded {path} -> {url}: {resp}")
203
+
204
+
205
+ @runtime.command("delete")
206
+ @click.argument("agent_name")
207
+ @click.option("--server", default=None, help="Override default server URL")
208
+ def delete(agent_name, server):
209
+ """Delete an agent from the runtime server"""
210
+ url = server or get_server()
211
+ if not url:
212
+ click.echo("No server configured. Use 'agentify server set <url>'")
213
+ return
214
+ resp = delete_agent(url, agent_name)
215
+ click.echo(f"Deleted {agent_name} from {url}: {resp}")
216
+
217
+ @main.group()
218
+ def providers():
219
+ """Add/Remote AI Provider API Keys"""
220
+ pass
221
+
222
+ @providers.command("add")
223
+ @click.argument("provider")
224
+ @click.option("--env", "env_var", help="Environment variable name")
225
+ @click.option("--key", help="API key (optional, will prompt if omitted)")
226
+ def providers_add(provider, env_var, key):
227
+ """Add an AI Provider and API KEY"""
228
+
229
+ provider = provider.lower()
230
+ env_var = env_var or f"{provider.upper()}_API_KEY"
231
+
232
+ if not key:
233
+ key = click.prompt(
234
+ f"Enter API key for {provider}",
235
+ hide_input=True,
236
+ )
237
+
238
+ add_provider(provider, env_var)
239
+
240
+ click.echo(f"✓ Provider '{provider}' added to local config\n")
241
+ click.echo("To apply in your current shell, run:\n")
242
+ click.echo(f"export {env_var}={key}")
243
+
244
+ @providers.command("list")
245
+ def providers_list():
246
+ """List configured providers with their current status"""
247
+
248
+ providers = list_providers()
249
+
250
+ if not providers:
251
+ click.echo("No providers configured.")
252
+ return
253
+
254
+ # click.echo("Configured providers:\n")
255
+ # for name, cfg in providers.items():
256
+ # click.echo(f"• {name}")
257
+ # click.echo(f" env: {cfg['env']}\n")
258
+
259
+ click.echo("Configured providers:\n")
260
+ for name, cfg in providers.items():
261
+ env_var = cfg.get("env")
262
+
263
+ # Check if the env var is present in the current shell
264
+ if env_var in os.environ and os.environ[env_var]:
265
+ loaded = click.style("READY", fg="green") # green for ready
266
+ else:
267
+ loaded = click.style(
268
+ f"MISSING - run command: agentify providers add {name}", fg="yellow"
269
+ ) # yellow for not set
270
+
271
+ click.echo(f"• {name}")
272
+ click.echo(f" env: {env_var}")
273
+ click.echo(f" status: {loaded}\n")
274
+
275
+
276
+
277
+ @providers.command("remove")
278
+ @click.argument("provider")
279
+ def providers_remove(provider):
280
+ """Remove a configured providers"""
281
+
282
+ env_var = remove_provider(provider)
283
+
284
+ if not env_var:
285
+ click.echo(f"Provider not found: {provider}")
286
+ return
287
+
288
+ click.echo(f"✓ Provider '{provider}' removed from config\n")
289
+ click.echo("To remove from your current shell, run:\n")
290
+ click.echo(f"unset {env_var}")
291
+
292
+
293
+ if __name__ == "__main__":
294
+ main()
agentify/cli_config.py ADDED
@@ -0,0 +1,100 @@
1
+ import json
2
+ import yaml
3
+ from pathlib import Path
4
+
5
+ AGENTIFY_DIR = Path.home() / ".agentify"
6
+ CONFIG_PATH = Path.home() / ".agentify" / "config.json"
7
+ PROVIDERS_FILE = AGENTIFY_DIR / "providers.yaml"
8
+
9
+ def load_config():
10
+ if CONFIG_PATH.exists():
11
+ return json.loads(CONFIG_PATH.read_text())
12
+ return {}
13
+
14
+ def save_config(config: dict):
15
+ CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
16
+ CONFIG_PATH.write_text(json.dumps(config, indent=2))
17
+
18
+ def set_server(url: str):
19
+ config = load_config()
20
+ config["server_url"] = url
21
+ save_config(config)
22
+ print(f"Default server set to {url}")
23
+
24
+ def get_server(default=None):
25
+ config = load_config()
26
+ return config.get("server_url", default)
27
+
28
+
29
+
30
+
31
+ # -----------------------------
32
+ # Provider config
33
+ # -----------------------------
34
+ def load_providers():
35
+ """
36
+ Load provider configuration from disk.
37
+
38
+ Returns:
39
+ dict: provider configuration
40
+ """
41
+ if not PROVIDERS_FILE.exists():
42
+ return {}
43
+
44
+ with open(PROVIDERS_FILE, "r") as f:
45
+ return yaml.safe_load(f) or {}
46
+
47
+
48
+ def save_providers(data: dict):
49
+ """
50
+ Persist provider configuration to disk.
51
+ """
52
+ AGENTIFY_DIR.mkdir(parents=True, exist_ok=True)
53
+ with open(PROVIDERS_FILE, "w") as f:
54
+ yaml.safe_dump(data, f)
55
+
56
+
57
+ def add_provider(provider: str, env_var: str):
58
+ """
59
+ Register a provider and its environment variable.
60
+ """
61
+ provider = provider.lower()
62
+
63
+ data = load_providers()
64
+ data.setdefault("providers", {})
65
+ data["providers"][provider] = {
66
+ "env": env_var
67
+ }
68
+
69
+ save_providers(data)
70
+
71
+
72
+ def remove_provider(provider: str):
73
+ """
74
+ Remove a provider from configuration.
75
+
76
+ Returns:
77
+ str | None: env var name if provider existed
78
+ """
79
+ provider = provider.lower()
80
+ data = load_providers()
81
+ providers = data.get("providers", {})
82
+
83
+ if provider not in providers:
84
+ return None
85
+
86
+ env_var = providers[provider]["env"]
87
+ del providers[provider]
88
+ save_providers(data)
89
+ return env_var
90
+
91
+
92
+ def list_providers():
93
+ """
94
+ Return configured providers.
95
+
96
+ Returns:
97
+ dict
98
+ """
99
+ data = load_providers()
100
+ return data.get("providers", {})
agentify/cli_ui.py ADDED
@@ -0,0 +1,44 @@
1
+ # Copyright 2026 Backplane Software
2
+ # Licensed under the Apache License, Version 2.0
3
+
4
+ from rich.console import Console
5
+ from rich.panel import Panel
6
+ from rich.prompt import Prompt
7
+ from rich.table import Table
8
+
9
+ def show_agent_menu(agents: dict) -> "Agent":
10
+ console = Console()
11
+
12
+ table = Table(title="Available Agents", header_style="bold cyan")
13
+ table.add_column("#", style="yellow", justify="right")
14
+ table.add_column("AgentName", style="green")
15
+ table.add_column("Agent Version", style="dim")
16
+ table.add_column("Agent Role", style="dim")
17
+ table.add_column("AI Provider", style="dim")
18
+ table.add_column("LLM Model", style="dim")
19
+
20
+ agent_list = list(agents.values())
21
+
22
+ for i, agent in enumerate(agent_list, start=1):
23
+
24
+
25
+ table.add_row(
26
+ str(i),
27
+ agent.name,
28
+ agent.version,
29
+ agent.description,
30
+ agent.provider,
31
+ agent.model_id,
32
+ )
33
+
34
+ console.print(table)
35
+
36
+ while True:
37
+ choice = input("Select an agent: ").strip()
38
+ if choice.isdigit() and 1 <= int(choice) <= len(agent_list):
39
+ selected_agent = agent_list[int(choice) - 1]
40
+ return selected_agent
41
+ elif int(choice) == (len(agent_list) + 1):
42
+ console.print("Create custom Agent")
43
+ console.print("[red]Invalid selection[/red]")
44
+
@@ -0,0 +1,5 @@
1
+ from .openai import run_openai
2
+ from .anthropic import run_anthropic
3
+ from .google import run_google
4
+ from .bedrock import run_bedrock
5
+ from .x import run_x
@@ -0,0 +1,15 @@
1
+ from anthropic import Anthropic
2
+
3
+ def run_anthropic(model_id: str, user_prompt: str) -> str:
4
+ client = Anthropic()
5
+ message = client.messages.create(
6
+ model= model_id,
7
+ max_tokens=1024,
8
+ messages=[
9
+ {
10
+ "role": "user",
11
+ "content": user_prompt
12
+ }
13
+ ]
14
+ )
15
+ return message.content[0].text
@@ -0,0 +1,30 @@
1
+ import boto3
2
+ import json
3
+
4
+ def run_bedrock(model_id: str, user_prompt: str) -> str:
5
+ client = boto3.client(
6
+ service_name="bedrock-runtime",
7
+ region_name="eu-west-1"
8
+ )
9
+
10
+ body = {
11
+ "anthropic_version": "bedrock-2023-05-31",
12
+ "max_tokens": 1024,
13
+ "temperature": 0.2,
14
+ "messages": [
15
+ {
16
+ "role": "user",
17
+ "content": user_prompt
18
+ }
19
+ ]
20
+ }
21
+
22
+ response = client.invoke_model(
23
+ modelId=model_id,
24
+ body=json.dumps(body),
25
+ contentType="application/json",
26
+ accept="application/json"
27
+ )
28
+
29
+ response_body = json.loads(response["body"].read())
30
+ return response_body["content"][0]["text"]
@@ -0,0 +1,10 @@
1
+ from google import genai
2
+
3
+ def run_google(model_id: str, user_prompt: str) -> str:
4
+ client = genai.Client()
5
+ response = client.models.generate_content(
6
+ model=model_id,
7
+ contents=user_prompt
8
+ )
9
+
10
+ return response.text
@@ -0,0 +1,11 @@
1
+
2
+ from openai import OpenAI
3
+
4
+ def run_openai(model_id: str, user_prompt: str) -> str:
5
+ client = OpenAI()
6
+
7
+ response = client.responses.create(
8
+ model=model_id,
9
+ input=user_prompt
10
+ )
11
+ return response.output_text
@@ -0,0 +1,12 @@
1
+ import os
2
+ from xai_sdk import Client
3
+ from xai_sdk.chat import user, system
4
+
5
+ def run_x(model_id: str, user_prompt: str) -> str:
6
+ client = Client()
7
+ chat = client.chat.create(model=model_id)
8
+ chat.append(system("You are Grok, a highly intelligent, helpful AI assistant."))
9
+ chat.append(user(user_prompt))
10
+ response = chat.sample()
11
+
12
+ return response.content
@@ -0,0 +1,23 @@
1
+ import requests
2
+
3
+ def list_agents(server_url):
4
+ resp = requests.get(f"{server_url}/agents")
5
+ resp.raise_for_status()
6
+ return resp.json()
7
+
8
+ def upload_agent(server_url, yaml_file):
9
+ files = {'file': open(yaml_file, 'rb')}
10
+ resp = requests.post(f"{server_url}/agents", files=files)
11
+ resp.raise_for_status()
12
+ return resp.json()
13
+
14
+ def run_agent(server_url, yaml_file):
15
+ files = {'file': open(yaml_file, 'rb')}
16
+ resp = requests.post(f"{server_url}/agents/run", files=files)
17
+ resp.raise_for_status()
18
+ return resp.json()
19
+
20
+ def delete_agent(server_url, agent_name):
21
+ resp = requests.delete(f"{server_url}/agents/{agent_name}")
22
+ resp.raise_for_status()
23
+ return resp.json()