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.
Files changed (30) hide show
  1. substrai_agentdeploy-0.1.0/LICENSE +17 -0
  2. substrai_agentdeploy-0.1.0/PKG-INFO +87 -0
  3. substrai_agentdeploy-0.1.0/README.md +68 -0
  4. substrai_agentdeploy-0.1.0/pyproject.toml +32 -0
  5. substrai_agentdeploy-0.1.0/setup.cfg +4 -0
  6. substrai_agentdeploy-0.1.0/src/agentdeploy/__init__.py +38 -0
  7. substrai_agentdeploy-0.1.0/src/agentdeploy/adapters/__init__.py +0 -0
  8. substrai_agentdeploy-0.1.0/src/agentdeploy/adapters/base.py +60 -0
  9. substrai_agentdeploy-0.1.0/src/agentdeploy/cli/__init__.py +0 -0
  10. substrai_agentdeploy-0.1.0/src/agentdeploy/core/__init__.py +0 -0
  11. substrai_agentdeploy-0.1.0/src/agentdeploy/core/agent.py +109 -0
  12. substrai_agentdeploy-0.1.0/src/agentdeploy/core/runtime.py +191 -0
  13. substrai_agentdeploy-0.1.0/src/agentdeploy/cost/__init__.py +0 -0
  14. substrai_agentdeploy-0.1.0/src/agentdeploy/cost/enforcer.py +173 -0
  15. substrai_agentdeploy-0.1.0/src/agentdeploy/observability/__init__.py +0 -0
  16. substrai_agentdeploy-0.1.0/src/agentdeploy/session/__init__.py +0 -0
  17. substrai_agentdeploy-0.1.0/src/agentdeploy/session/manager.py +154 -0
  18. substrai_agentdeploy-0.1.0/src/agentdeploy/tenants/__init__.py +0 -0
  19. substrai_agentdeploy-0.1.0/src/agentdeploy/tools/__init__.py +0 -0
  20. substrai_agentdeploy-0.1.0/src/agentdeploy/tools/registry.py +167 -0
  21. substrai_agentdeploy-0.1.0/src/substrai_agentdeploy.egg-info/PKG-INFO +87 -0
  22. substrai_agentdeploy-0.1.0/src/substrai_agentdeploy.egg-info/SOURCES.txt +28 -0
  23. substrai_agentdeploy-0.1.0/src/substrai_agentdeploy.egg-info/dependency_links.txt +1 -0
  24. substrai_agentdeploy-0.1.0/src/substrai_agentdeploy.egg-info/entry_points.txt +2 -0
  25. substrai_agentdeploy-0.1.0/src/substrai_agentdeploy.egg-info/requires.txt +7 -0
  26. substrai_agentdeploy-0.1.0/src/substrai_agentdeploy.egg-info/top_level.txt +1 -0
  27. substrai_agentdeploy-0.1.0/tests/test_agent.py +118 -0
  28. substrai_agentdeploy-0.1.0/tests/test_cost.py +56 -0
  29. substrai_agentdeploy-0.1.0/tests/test_session.py +78 -0
  30. 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
+ [![PyPI version](https://badge.fury.io/py/substrai-agentdeploy.svg)](https://pypi.org/project/substrai-agentdeploy/)
27
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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
+ [![PyPI version](https://badge.fury.io/py/substrai-agentdeploy.svg)](https://pypi.org/project/substrai-agentdeploy/)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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
+ ]
@@ -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}"
@@ -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