substrai-agentdeploy 0.1.0__tar.gz
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.
- substrai_agentdeploy-0.1.0/LICENSE +17 -0
- substrai_agentdeploy-0.1.0/PKG-INFO +87 -0
- substrai_agentdeploy-0.1.0/README.md +68 -0
- substrai_agentdeploy-0.1.0/pyproject.toml +32 -0
- substrai_agentdeploy-0.1.0/setup.cfg +4 -0
- substrai_agentdeploy-0.1.0/src/agentdeploy/__init__.py +38 -0
- substrai_agentdeploy-0.1.0/src/agentdeploy/adapters/__init__.py +0 -0
- substrai_agentdeploy-0.1.0/src/agentdeploy/adapters/base.py +60 -0
- substrai_agentdeploy-0.1.0/src/agentdeploy/cli/__init__.py +0 -0
- substrai_agentdeploy-0.1.0/src/agentdeploy/core/__init__.py +0 -0
- substrai_agentdeploy-0.1.0/src/agentdeploy/core/agent.py +109 -0
- substrai_agentdeploy-0.1.0/src/agentdeploy/core/runtime.py +191 -0
- substrai_agentdeploy-0.1.0/src/agentdeploy/cost/__init__.py +0 -0
- substrai_agentdeploy-0.1.0/src/agentdeploy/cost/enforcer.py +173 -0
- substrai_agentdeploy-0.1.0/src/agentdeploy/observability/__init__.py +0 -0
- substrai_agentdeploy-0.1.0/src/agentdeploy/session/__init__.py +0 -0
- substrai_agentdeploy-0.1.0/src/agentdeploy/session/manager.py +154 -0
- substrai_agentdeploy-0.1.0/src/agentdeploy/tenants/__init__.py +0 -0
- substrai_agentdeploy-0.1.0/src/agentdeploy/tools/__init__.py +0 -0
- substrai_agentdeploy-0.1.0/src/agentdeploy/tools/registry.py +167 -0
- substrai_agentdeploy-0.1.0/src/substrai_agentdeploy.egg-info/PKG-INFO +87 -0
- substrai_agentdeploy-0.1.0/src/substrai_agentdeploy.egg-info/SOURCES.txt +28 -0
- substrai_agentdeploy-0.1.0/src/substrai_agentdeploy.egg-info/dependency_links.txt +1 -0
- substrai_agentdeploy-0.1.0/src/substrai_agentdeploy.egg-info/entry_points.txt +2 -0
- substrai_agentdeploy-0.1.0/src/substrai_agentdeploy.egg-info/requires.txt +7 -0
- substrai_agentdeploy-0.1.0/src/substrai_agentdeploy.egg-info/top_level.txt +1 -0
- substrai_agentdeploy-0.1.0/tests/test_agent.py +118 -0
- substrai_agentdeploy-0.1.0/tests/test_cost.py +56 -0
- substrai_agentdeploy-0.1.0/tests/test_session.py +78 -0
- substrai_agentdeploy-0.1.0/tests/test_tools.py +79 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Gaurav Kumar Sinha (Substrai AI)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: substrai-agentdeploy
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Zero-to-production AI agent deployment framework
|
|
5
|
+
Author-email: Gaurav Kumar Sinha <gaurav@substrai.dev>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/substrai/agentdeploy
|
|
8
|
+
Project-URL: Repository, https://github.com/substrai/agentdeploy
|
|
9
|
+
Keywords: ai-agent,deployment,serverless,lambda,llm,multi-tenancy,production
|
|
10
|
+
Requires-Python: >=3.9
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: pyyaml>=6.0
|
|
14
|
+
Provides-Extra: aws
|
|
15
|
+
Requires-Dist: boto3>=1.28.0; extra == "aws"
|
|
16
|
+
Provides-Extra: dev
|
|
17
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# AgentDeploy
|
|
21
|
+
|
|
22
|
+
**Zero-to-production AI agent deployment framework.**
|
|
23
|
+
|
|
24
|
+
> Built by [SubstrAI](https://github.com/substrai) — Open-source GenAI frameworks for serverless infrastructure.
|
|
25
|
+
|
|
26
|
+
[](https://pypi.org/project/substrai-agentdeploy/)
|
|
27
|
+
[](https://opensource.org/licenses/MIT)
|
|
28
|
+
|
|
29
|
+
## The Problem
|
|
30
|
+
|
|
31
|
+
Building an AI agent is easy. Deploying it to production with auth, scaling, sessions, cost controls, and multi-tenancy takes weeks of custom infrastructure — every time.
|
|
32
|
+
|
|
33
|
+
## The Solution
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from agentdeploy import agent, Tool, Session
|
|
37
|
+
|
|
38
|
+
@Tool(description="Search knowledge base")
|
|
39
|
+
def search_kb(query: str) -> list:
|
|
40
|
+
return ["result 1", "result 2"]
|
|
41
|
+
|
|
42
|
+
@agent(name="support-agent", model="bedrock/claude-3-sonnet", tools=[search_kb])
|
|
43
|
+
def support_agent(message: str, session: Session) -> str:
|
|
44
|
+
return f"I can help with: {message}"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
agentdeploy deploy --env prod
|
|
49
|
+
# ✓ Deployed: https://xxx.execute-api.us-east-1.amazonaws.com/prod/agent
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Features
|
|
53
|
+
|
|
54
|
+
- **@agent decorator** — turn any function into a deployable agent
|
|
55
|
+
- **Session management** — DynamoDB-backed conversation persistence
|
|
56
|
+
- **Tool sandboxing** — per-tenant tool permissions with audit trail
|
|
57
|
+
- **Cost circuit breakers** — auto-kill runs exceeding budget
|
|
58
|
+
- **Multi-tenancy** — tenant isolation, rate limiting, cost tracking
|
|
59
|
+
- **One-command deploy** — API Gateway + Lambda + DynamoDB
|
|
60
|
+
- **Provider agnostic** — works with Bedrock, OpenAI, Anthropic, custom
|
|
61
|
+
- **Adapter interface** — supports LangChain, CrewAI, Strands, custom agents
|
|
62
|
+
|
|
63
|
+
## Installation
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pip install substrai-agentdeploy
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Quick Start
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
agentdeploy init my-agent
|
|
73
|
+
cd my-agent
|
|
74
|
+
agentdeploy dev # local dev server
|
|
75
|
+
agentdeploy deploy --env prod
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## License
|
|
79
|
+
|
|
80
|
+
MIT — see [LICENSE](LICENSE)
|
|
81
|
+
|
|
82
|
+
## Author
|
|
83
|
+
|
|
84
|
+
**Gaurav Kumar Sinha** — Founder, [SubstrAI](https://github.com/substrai)
|
|
85
|
+
|
|
86
|
+
- Email: gaurav@substrai.dev
|
|
87
|
+
- GitHub: [@substrai](https://github.com/substrai)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# AgentDeploy
|
|
2
|
+
|
|
3
|
+
**Zero-to-production AI agent deployment framework.**
|
|
4
|
+
|
|
5
|
+
> Built by [SubstrAI](https://github.com/substrai) — Open-source GenAI frameworks for serverless infrastructure.
|
|
6
|
+
|
|
7
|
+
[](https://pypi.org/project/substrai-agentdeploy/)
|
|
8
|
+
[](https://opensource.org/licenses/MIT)
|
|
9
|
+
|
|
10
|
+
## The Problem
|
|
11
|
+
|
|
12
|
+
Building an AI agent is easy. Deploying it to production with auth, scaling, sessions, cost controls, and multi-tenancy takes weeks of custom infrastructure — every time.
|
|
13
|
+
|
|
14
|
+
## The Solution
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
from agentdeploy import agent, Tool, Session
|
|
18
|
+
|
|
19
|
+
@Tool(description="Search knowledge base")
|
|
20
|
+
def search_kb(query: str) -> list:
|
|
21
|
+
return ["result 1", "result 2"]
|
|
22
|
+
|
|
23
|
+
@agent(name="support-agent", model="bedrock/claude-3-sonnet", tools=[search_kb])
|
|
24
|
+
def support_agent(message: str, session: Session) -> str:
|
|
25
|
+
return f"I can help with: {message}"
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
agentdeploy deploy --env prod
|
|
30
|
+
# ✓ Deployed: https://xxx.execute-api.us-east-1.amazonaws.com/prod/agent
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Features
|
|
34
|
+
|
|
35
|
+
- **@agent decorator** — turn any function into a deployable agent
|
|
36
|
+
- **Session management** — DynamoDB-backed conversation persistence
|
|
37
|
+
- **Tool sandboxing** — per-tenant tool permissions with audit trail
|
|
38
|
+
- **Cost circuit breakers** — auto-kill runs exceeding budget
|
|
39
|
+
- **Multi-tenancy** — tenant isolation, rate limiting, cost tracking
|
|
40
|
+
- **One-command deploy** — API Gateway + Lambda + DynamoDB
|
|
41
|
+
- **Provider agnostic** — works with Bedrock, OpenAI, Anthropic, custom
|
|
42
|
+
- **Adapter interface** — supports LangChain, CrewAI, Strands, custom agents
|
|
43
|
+
|
|
44
|
+
## Installation
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install substrai-agentdeploy
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Quick Start
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
agentdeploy init my-agent
|
|
54
|
+
cd my-agent
|
|
55
|
+
agentdeploy dev # local dev server
|
|
56
|
+
agentdeploy deploy --env prod
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## License
|
|
60
|
+
|
|
61
|
+
MIT — see [LICENSE](LICENSE)
|
|
62
|
+
|
|
63
|
+
## Author
|
|
64
|
+
|
|
65
|
+
**Gaurav Kumar Sinha** — Founder, [SubstrAI](https://github.com/substrai)
|
|
66
|
+
|
|
67
|
+
- Email: gaurav@substrai.dev
|
|
68
|
+
- GitHub: [@substrai](https://github.com/substrai)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "substrai-agentdeploy"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Zero-to-production AI agent deployment framework"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "MIT"}
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
authors = [{name = "Gaurav Kumar Sinha", email = "gaurav@substrai.dev"}]
|
|
13
|
+
keywords = ["ai-agent", "deployment", "serverless", "lambda", "llm", "multi-tenancy", "production"]
|
|
14
|
+
dependencies = ["pyyaml>=6.0"]
|
|
15
|
+
|
|
16
|
+
[project.optional-dependencies]
|
|
17
|
+
aws = ["boto3>=1.28.0"]
|
|
18
|
+
dev = ["pytest>=7.0"]
|
|
19
|
+
|
|
20
|
+
[project.scripts]
|
|
21
|
+
agentdeploy = "agentdeploy.cli.main:main"
|
|
22
|
+
|
|
23
|
+
[project.urls]
|
|
24
|
+
Homepage = "https://github.com/substrai/agentdeploy"
|
|
25
|
+
Repository = "https://github.com/substrai/agentdeploy"
|
|
26
|
+
|
|
27
|
+
[tool.setuptools.packages.find]
|
|
28
|
+
where = ["src"]
|
|
29
|
+
|
|
30
|
+
[tool.pytest.ini_options]
|
|
31
|
+
testpaths = ["tests"]
|
|
32
|
+
pythonpath = ["src"]
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AgentDeploy - Zero-to-Production AI Agent Deployment Framework
|
|
3
|
+
|
|
4
|
+
Takes any agent definition (LangChain, CrewAI, custom) and deploys it
|
|
5
|
+
as a production-grade serverless API with auth, scaling, monitoring,
|
|
6
|
+
cost controls, multi-tenancy, and session management.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
from agentdeploy import agent, Tool, Session
|
|
10
|
+
|
|
11
|
+
@agent(name="my-agent", model="bedrock/claude-3-sonnet")
|
|
12
|
+
def my_agent(message: str, session: Session) -> str:
|
|
13
|
+
return "Hello!"
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
__version__ = "0.1.0"
|
|
17
|
+
|
|
18
|
+
from agentdeploy.core.agent import agent, AgentConfig
|
|
19
|
+
from agentdeploy.core.runtime import AgentRuntime, InvocationResult
|
|
20
|
+
from agentdeploy.session.manager import Session, SessionManager
|
|
21
|
+
from agentdeploy.tools.registry import Tool, ToolRegistry, ToolPermission
|
|
22
|
+
from agentdeploy.adapters.base import BaseAdapter
|
|
23
|
+
from agentdeploy.cost.enforcer import CostEnforcer, CostBudget
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"agent",
|
|
27
|
+
"AgentConfig",
|
|
28
|
+
"AgentRuntime",
|
|
29
|
+
"InvocationResult",
|
|
30
|
+
"Session",
|
|
31
|
+
"SessionManager",
|
|
32
|
+
"Tool",
|
|
33
|
+
"ToolRegistry",
|
|
34
|
+
"ToolPermission",
|
|
35
|
+
"BaseAdapter",
|
|
36
|
+
"CostEnforcer",
|
|
37
|
+
"CostBudget",
|
|
38
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Base adapter interface for agent frameworks."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from typing import Any, Dict, Optional
|
|
7
|
+
|
|
8
|
+
from agentdeploy.session.manager import Session
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BaseAdapter(ABC):
|
|
12
|
+
"""Base class for agent framework adapters.
|
|
13
|
+
|
|
14
|
+
Implement this to support any agent framework (LangChain, CrewAI, etc.)
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
name: str = "base"
|
|
18
|
+
|
|
19
|
+
@abstractmethod
|
|
20
|
+
def invoke(self, message: str, session: Session, **kwargs) -> str:
|
|
21
|
+
"""Invoke the agent with a message.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
message: User message
|
|
25
|
+
session: Current session with history
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
Agent response string
|
|
29
|
+
"""
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
def get_tool_calls(self) -> list:
|
|
33
|
+
"""Get tool calls made during last invocation."""
|
|
34
|
+
return []
|
|
35
|
+
|
|
36
|
+
def get_token_usage(self) -> Dict[str, int]:
|
|
37
|
+
"""Get token usage from last invocation."""
|
|
38
|
+
return {"input_tokens": 0, "output_tokens": 0}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class CustomAdapter(BaseAdapter):
|
|
42
|
+
"""Adapter for custom agent functions (default)."""
|
|
43
|
+
|
|
44
|
+
name = "custom"
|
|
45
|
+
|
|
46
|
+
def __init__(self, agent_fn):
|
|
47
|
+
self.agent_fn = agent_fn
|
|
48
|
+
|
|
49
|
+
def invoke(self, message: str, session: Session, **kwargs) -> str:
|
|
50
|
+
result = self.agent_fn(message, session)
|
|
51
|
+
return str(result) if result else ""
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class EchoAdapter(BaseAdapter):
|
|
55
|
+
"""Simple echo adapter for testing."""
|
|
56
|
+
|
|
57
|
+
name = "echo"
|
|
58
|
+
|
|
59
|
+
def invoke(self, message: str, session: Session, **kwargs) -> str:
|
|
60
|
+
return f"Echo: {message}"
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""Agent decorator and configuration.
|
|
2
|
+
|
|
3
|
+
The @agent decorator turns a Python function into a deployable agent
|
|
4
|
+
with session management, tool access, and cost tracking.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import functools
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class AgentConfig:
|
|
16
|
+
"""Configuration for a deployed agent."""
|
|
17
|
+
|
|
18
|
+
name: str
|
|
19
|
+
model: str = "bedrock/claude-3-sonnet"
|
|
20
|
+
system_prompt: str = "You are a helpful assistant."
|
|
21
|
+
tools: List[Any] = field(default_factory=list)
|
|
22
|
+
max_iterations: int = 10
|
|
23
|
+
timeout_seconds: int = 60
|
|
24
|
+
memory_strategy: str = "sliding_window"
|
|
25
|
+
memory_window_size: int = 20
|
|
26
|
+
memory_ttl_hours: int = 24
|
|
27
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
28
|
+
|
|
29
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
30
|
+
return {
|
|
31
|
+
"name": self.name,
|
|
32
|
+
"model": self.model,
|
|
33
|
+
"system_prompt": self.system_prompt[:50] + "..." if len(self.system_prompt) > 50 else self.system_prompt,
|
|
34
|
+
"tools": [getattr(t, "name", str(t)) for t in self.tools],
|
|
35
|
+
"max_iterations": self.max_iterations,
|
|
36
|
+
"timeout_seconds": self.timeout_seconds,
|
|
37
|
+
"memory_strategy": self.memory_strategy,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class AgentFunction:
|
|
42
|
+
"""Wrapper around a decorated agent function."""
|
|
43
|
+
|
|
44
|
+
def __init__(self, fn: Callable, config: AgentConfig):
|
|
45
|
+
self.fn = fn
|
|
46
|
+
self.config = config
|
|
47
|
+
self.name = config.name
|
|
48
|
+
functools.update_wrapper(self, fn)
|
|
49
|
+
|
|
50
|
+
def __call__(self, *args, **kwargs):
|
|
51
|
+
return self.fn(*args, **kwargs)
|
|
52
|
+
|
|
53
|
+
def invoke(self, message: str, session_id: Optional[str] = None) -> Dict[str, Any]:
|
|
54
|
+
"""Invoke the agent with a message.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
message: User message
|
|
58
|
+
session_id: Optional session ID for conversation continuity
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
Dict with response and metadata
|
|
62
|
+
"""
|
|
63
|
+
from agentdeploy.core.runtime import AgentRuntime
|
|
64
|
+
runtime = AgentRuntime(self.config)
|
|
65
|
+
return runtime.invoke(message, session_id=session_id, agent_fn=self.fn)
|
|
66
|
+
|
|
67
|
+
def __repr__(self) -> str:
|
|
68
|
+
return f"AgentFunction(name='{self.name}', model='{self.config.model}')"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def agent(
|
|
72
|
+
name: str,
|
|
73
|
+
model: str = "bedrock/claude-3-sonnet",
|
|
74
|
+
system_prompt: str = "You are a helpful assistant.",
|
|
75
|
+
tools: Optional[List[Any]] = None,
|
|
76
|
+
max_iterations: int = 10,
|
|
77
|
+
timeout_seconds: int = 60,
|
|
78
|
+
memory_strategy: str = "sliding_window",
|
|
79
|
+
**kwargs,
|
|
80
|
+
) -> Callable:
|
|
81
|
+
"""Decorator to turn a function into a deployable agent.
|
|
82
|
+
|
|
83
|
+
Usage:
|
|
84
|
+
@agent(name="my-agent", model="bedrock/claude-3-sonnet", tools=[search_kb])
|
|
85
|
+
def my_agent(message: str, session: Session) -> str:
|
|
86
|
+
pass
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
name: Agent name (used for deployment and routing)
|
|
90
|
+
model: LLM model identifier
|
|
91
|
+
system_prompt: System prompt for the agent
|
|
92
|
+
tools: List of Tool-decorated functions
|
|
93
|
+
max_iterations: Max reasoning loops
|
|
94
|
+
timeout_seconds: Max execution time
|
|
95
|
+
memory_strategy: Session memory strategy
|
|
96
|
+
"""
|
|
97
|
+
def decorator(fn: Callable) -> AgentFunction:
|
|
98
|
+
config = AgentConfig(
|
|
99
|
+
name=name,
|
|
100
|
+
model=model,
|
|
101
|
+
system_prompt=system_prompt,
|
|
102
|
+
tools=tools or [],
|
|
103
|
+
max_iterations=max_iterations,
|
|
104
|
+
timeout_seconds=timeout_seconds,
|
|
105
|
+
memory_strategy=memory_strategy,
|
|
106
|
+
metadata=kwargs,
|
|
107
|
+
)
|
|
108
|
+
return AgentFunction(fn, config)
|
|
109
|
+
return decorator
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""Agent runtime - handles the request lifecycle."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
8
|
+
|
|
9
|
+
from agentdeploy.core.agent import AgentConfig
|
|
10
|
+
from agentdeploy.session.manager import Session, SessionManager
|
|
11
|
+
from agentdeploy.tools.registry import ToolRegistry, ToolCallResult
|
|
12
|
+
from agentdeploy.cost.enforcer import CostEnforcer, CostBudget
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class InvocationResult:
|
|
17
|
+
"""Result of an agent invocation."""
|
|
18
|
+
|
|
19
|
+
response: str
|
|
20
|
+
session_id: str
|
|
21
|
+
success: bool = True
|
|
22
|
+
latency_ms: float = 0.0
|
|
23
|
+
input_tokens: int = 0
|
|
24
|
+
output_tokens: int = 0
|
|
25
|
+
cost: float = 0.0
|
|
26
|
+
tool_calls: List[Dict[str, Any]] = field(default_factory=list)
|
|
27
|
+
turn_count: int = 0
|
|
28
|
+
error: Optional[str] = None
|
|
29
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
30
|
+
|
|
31
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
32
|
+
return {
|
|
33
|
+
"response": self.response,
|
|
34
|
+
"session_id": self.session_id,
|
|
35
|
+
"success": self.success,
|
|
36
|
+
"latency_ms": self.latency_ms,
|
|
37
|
+
"input_tokens": self.input_tokens,
|
|
38
|
+
"output_tokens": self.output_tokens,
|
|
39
|
+
"cost": self.cost,
|
|
40
|
+
"tool_calls": self.tool_calls,
|
|
41
|
+
"turn_count": self.turn_count,
|
|
42
|
+
"error": self.error,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class AgentRuntime:
|
|
47
|
+
"""Handles the full agent request lifecycle.
|
|
48
|
+
|
|
49
|
+
Request flow:
|
|
50
|
+
1. Load/create session
|
|
51
|
+
2. Check cost budget
|
|
52
|
+
3. Execute agent function
|
|
53
|
+
4. Track tool calls
|
|
54
|
+
5. Record cost
|
|
55
|
+
6. Save session
|
|
56
|
+
7. Return result with metadata
|
|
57
|
+
|
|
58
|
+
Usage:
|
|
59
|
+
runtime = AgentRuntime(config)
|
|
60
|
+
result = runtime.invoke("Hello!", session_id="sess-123")
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
def __init__(
|
|
64
|
+
self,
|
|
65
|
+
config: AgentConfig,
|
|
66
|
+
session_manager: Optional[SessionManager] = None,
|
|
67
|
+
tool_registry: Optional[ToolRegistry] = None,
|
|
68
|
+
cost_enforcer: Optional[CostEnforcer] = None,
|
|
69
|
+
):
|
|
70
|
+
self.config = config
|
|
71
|
+
self.session_manager = session_manager or SessionManager(ttl_hours=config.memory_ttl_hours)
|
|
72
|
+
self.tool_registry = tool_registry or ToolRegistry()
|
|
73
|
+
self.cost_enforcer = cost_enforcer or CostEnforcer()
|
|
74
|
+
self._invocation_log: List[InvocationResult] = []
|
|
75
|
+
|
|
76
|
+
# Register tools
|
|
77
|
+
for tool in config.tools:
|
|
78
|
+
if hasattr(tool, "definition"):
|
|
79
|
+
self.tool_registry.register(tool)
|
|
80
|
+
|
|
81
|
+
def invoke(
|
|
82
|
+
self,
|
|
83
|
+
message: str,
|
|
84
|
+
session_id: Optional[str] = None,
|
|
85
|
+
tenant_id: str = "default",
|
|
86
|
+
agent_fn: Optional[Callable] = None,
|
|
87
|
+
) -> InvocationResult:
|
|
88
|
+
"""Invoke the agent with full lifecycle management.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
message: User message
|
|
92
|
+
session_id: Optional session ID
|
|
93
|
+
tenant_id: Tenant making the request
|
|
94
|
+
agent_fn: Agent function to call
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
InvocationResult with response and metadata
|
|
98
|
+
"""
|
|
99
|
+
start_time = time.time()
|
|
100
|
+
|
|
101
|
+
# 1. Load/create session
|
|
102
|
+
session = self.session_manager.get_or_create(
|
|
103
|
+
session_id, self.config.name, tenant_id
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# 2. Check cost budget
|
|
107
|
+
cost_check = self.cost_enforcer.check_request(
|
|
108
|
+
session_id=session.session_id, estimated_cost=0.01
|
|
109
|
+
)
|
|
110
|
+
if not cost_check.allowed:
|
|
111
|
+
return InvocationResult(
|
|
112
|
+
response="",
|
|
113
|
+
session_id=session.session_id,
|
|
114
|
+
success=False,
|
|
115
|
+
error=cost_check.message,
|
|
116
|
+
latency_ms=(time.time() - start_time) * 1000,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# 3. Add user message to session
|
|
120
|
+
session.add_message("user", message)
|
|
121
|
+
|
|
122
|
+
# 4. Execute agent
|
|
123
|
+
try:
|
|
124
|
+
if agent_fn:
|
|
125
|
+
response = agent_fn(message, session)
|
|
126
|
+
if response is None:
|
|
127
|
+
# Default behavior: echo with context
|
|
128
|
+
response = f"[{self.config.name}] Received: {message}"
|
|
129
|
+
else:
|
|
130
|
+
response = f"[{self.config.name}] Received: {message}"
|
|
131
|
+
except Exception as e:
|
|
132
|
+
return InvocationResult(
|
|
133
|
+
response="",
|
|
134
|
+
session_id=session.session_id,
|
|
135
|
+
success=False,
|
|
136
|
+
error=str(e),
|
|
137
|
+
latency_ms=(time.time() - start_time) * 1000,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# 5. Add assistant response to session
|
|
141
|
+
session.add_message("assistant", response)
|
|
142
|
+
|
|
143
|
+
# 6. Calculate cost (estimate)
|
|
144
|
+
input_tokens = len(message) // 4
|
|
145
|
+
output_tokens = len(response) // 4
|
|
146
|
+
cost = self._estimate_cost(input_tokens, output_tokens)
|
|
147
|
+
|
|
148
|
+
# 7. Record cost
|
|
149
|
+
session.total_tokens += input_tokens + output_tokens
|
|
150
|
+
session.total_cost += cost
|
|
151
|
+
self.cost_enforcer.record_cost(session.session_id, cost, input_tokens + output_tokens)
|
|
152
|
+
|
|
153
|
+
# 8. Save session
|
|
154
|
+
self.session_manager.save(session)
|
|
155
|
+
|
|
156
|
+
latency_ms = (time.time() - start_time) * 1000
|
|
157
|
+
|
|
158
|
+
result = InvocationResult(
|
|
159
|
+
response=response,
|
|
160
|
+
session_id=session.session_id,
|
|
161
|
+
success=True,
|
|
162
|
+
latency_ms=round(latency_ms, 2),
|
|
163
|
+
input_tokens=input_tokens,
|
|
164
|
+
output_tokens=output_tokens,
|
|
165
|
+
cost=round(cost, 8),
|
|
166
|
+
tool_calls=[r.__dict__ for r in self.tool_registry.call_log[-5:]],
|
|
167
|
+
turn_count=session.turn_count,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
self._invocation_log.append(result)
|
|
171
|
+
return result
|
|
172
|
+
|
|
173
|
+
def get_session(self, session_id: str) -> Optional[Session]:
|
|
174
|
+
"""Get a session by ID."""
|
|
175
|
+
return self.session_manager.get(session_id)
|
|
176
|
+
|
|
177
|
+
@property
|
|
178
|
+
def invocation_history(self) -> List[InvocationResult]:
|
|
179
|
+
return self._invocation_log
|
|
180
|
+
|
|
181
|
+
def _estimate_cost(self, input_tokens: int, output_tokens: int) -> float:
|
|
182
|
+
"""Estimate cost based on model."""
|
|
183
|
+
pricing = {
|
|
184
|
+
"bedrock/claude-3-haiku": (0.00025, 0.00125),
|
|
185
|
+
"bedrock/claude-3-sonnet": (0.003, 0.015),
|
|
186
|
+
"bedrock/claude-3-opus": (0.015, 0.075),
|
|
187
|
+
"openai/gpt-4o-mini": (0.00015, 0.0006),
|
|
188
|
+
"openai/gpt-4o": (0.005, 0.015),
|
|
189
|
+
}
|
|
190
|
+
input_price, output_price = pricing.get(self.config.model, (0.001, 0.002))
|
|
191
|
+
return (input_tokens / 1000) * input_price + (output_tokens / 1000) * output_price
|
|
File without changes
|