sentrial 0.1.2__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.
- sentrial-0.1.2/LICENSE +22 -0
- sentrial-0.1.2/MANIFEST.in +6 -0
- sentrial-0.1.2/PKG-INFO +165 -0
- sentrial-0.1.2/README.md +128 -0
- sentrial-0.1.2/py.typed +0 -0
- sentrial-0.1.2/pyproject.toml +72 -0
- sentrial-0.1.2/sentrial/__init__.py +19 -0
- sentrial-0.1.2/sentrial/client.py +191 -0
- sentrial-0.1.2/sentrial/langchain.py +354 -0
- sentrial-0.1.2/sentrial/types.py +44 -0
- sentrial-0.1.2/sentrial.egg-info/PKG-INFO +165 -0
- sentrial-0.1.2/sentrial.egg-info/SOURCES.txt +14 -0
- sentrial-0.1.2/sentrial.egg-info/dependency_links.txt +1 -0
- sentrial-0.1.2/sentrial.egg-info/requires.txt +13 -0
- sentrial-0.1.2/sentrial.egg-info/top_level.txt +1 -0
- sentrial-0.1.2/setup.cfg +4 -0
sentrial-0.1.2/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Sentrial
|
|
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. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
sentrial-0.1.2/PKG-INFO
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sentrial
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: Sentrial - Observability and time-travel debugging for AI agents
|
|
5
|
+
Author-email: Sentrial Team <support@sentrial.ai>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://sentrial.app
|
|
8
|
+
Project-URL: Documentation, https://docs.sentrial.app
|
|
9
|
+
Project-URL: Repository, https://github.com/neelshar/sentrial
|
|
10
|
+
Project-URL: Bug Tracker, https://github.com/neelshar/sentrial/issues
|
|
11
|
+
Keywords: ai,agents,observability,debugging,langchain,llm
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
23
|
+
Requires-Python: >=3.8
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: requests>=2.31.0
|
|
27
|
+
Provides-Extra: langchain
|
|
28
|
+
Requires-Dist: langchain>=0.1.0; extra == "langchain"
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: pytest>=7.4.0; extra == "dev"
|
|
31
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
32
|
+
Requires-Dist: mypy>=1.5.0; extra == "dev"
|
|
33
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
34
|
+
Provides-Extra: all
|
|
35
|
+
Requires-Dist: langchain>=0.1.0; extra == "all"
|
|
36
|
+
Dynamic: license-file
|
|
37
|
+
|
|
38
|
+
# Sentrial Python SDK
|
|
39
|
+
|
|
40
|
+
Observability and time-travel debugging for AI agents. Track every decision, tool call, and state change in your agent workflows.
|
|
41
|
+
|
|
42
|
+
## Features
|
|
43
|
+
|
|
44
|
+
- **Automatic Tracking**: Capture agent reasoning, tool calls, and state changes
|
|
45
|
+
- **Git-like Branching**: Fork agent sessions at any point
|
|
46
|
+
- **Time Travel**: Replay and debug past agent executions
|
|
47
|
+
- **Framework Agnostic**: Works with LangChain, custom agents, and more
|
|
48
|
+
- **Visual UI**: Beautiful web interface to explore agent behavior
|
|
49
|
+
|
|
50
|
+
## Installation
|
|
51
|
+
|
|
52
|
+
### From PyPI
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# Standard installation
|
|
56
|
+
pip install sentrial
|
|
57
|
+
|
|
58
|
+
# With LangChain integration
|
|
59
|
+
pip install sentrial[langchain]
|
|
60
|
+
|
|
61
|
+
# All integrations
|
|
62
|
+
pip install sentrial[all]
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### From GitHub
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pip install git+https://github.com/neelshar/Sentrial.git#subdirectory=packages/python-sdk
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Local Development
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
cd packages/python-sdk
|
|
75
|
+
pip install -e .
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Quick Start
|
|
79
|
+
|
|
80
|
+
### Basic Usage
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from sentrial import SentrialClient
|
|
84
|
+
|
|
85
|
+
# Initialize client
|
|
86
|
+
client = SentrialClient(
|
|
87
|
+
api_url="https://api.sentrial.app", # Or your self-hosted URL
|
|
88
|
+
project_id="your-project-id"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# Create a session
|
|
92
|
+
session_id = client.create_session(name="Customer Support Agent")
|
|
93
|
+
|
|
94
|
+
# Track tool calls
|
|
95
|
+
client.track_tool_call(
|
|
96
|
+
session_id=session_id,
|
|
97
|
+
tool_name="search_knowledge_base",
|
|
98
|
+
tool_input={"query": "password reset"},
|
|
99
|
+
tool_output={"articles": ["KB-001", "KB-002"]},
|
|
100
|
+
reasoning="Searching for relevant articles"
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# Track LLM decisions
|
|
104
|
+
client.track_decision(
|
|
105
|
+
session_id=session_id,
|
|
106
|
+
reasoning="User already tried KB solutions. Escalating to human support.",
|
|
107
|
+
alternatives=["Try another KB article", "Ask for more info"],
|
|
108
|
+
confidence=0.85
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# Close session
|
|
112
|
+
client.close_session(session_id)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### LangChain Integration
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
from sentrial import SentrialClient, SentrialCallbackHandler
|
|
119
|
+
from langchain.agents import AgentExecutor, create_react_agent
|
|
120
|
+
|
|
121
|
+
# Initialize Sentrial
|
|
122
|
+
client = SentrialClient(api_url="...", project_id="...")
|
|
123
|
+
session_id = client.create_session(name="LangChain Agent")
|
|
124
|
+
|
|
125
|
+
# Create callback handler
|
|
126
|
+
handler = SentrialCallbackHandler(client, session_id, verbose=True)
|
|
127
|
+
|
|
128
|
+
# Use with LangChain - automatic tracking!
|
|
129
|
+
agent_executor = AgentExecutor(
|
|
130
|
+
agent=agent,
|
|
131
|
+
tools=tools,
|
|
132
|
+
callbacks=[handler], # ← That's it!
|
|
133
|
+
verbose=True
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
result = agent_executor.invoke({"input": "Help user with login issue"})
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
The callback handler automatically tracks:
|
|
140
|
+
- Agent reasoning (Chain of Thought)
|
|
141
|
+
- Tool calls (inputs & outputs)
|
|
142
|
+
- Tool errors
|
|
143
|
+
- Agent completion
|
|
144
|
+
|
|
145
|
+
## Examples
|
|
146
|
+
|
|
147
|
+
- [simple_agent.py](https://github.com/neelshar/Sentrial/blob/main/examples/simple_agent.py) - Basic agent tracking
|
|
148
|
+
- [langchain_agent.py](https://github.com/neelshar/Sentrial/blob/main/examples/langchain_agent.py) - Full LangChain integration
|
|
149
|
+
|
|
150
|
+
## Documentation
|
|
151
|
+
|
|
152
|
+
- [Getting Started](https://sentrial.app/docs/quickstart)
|
|
153
|
+
- [API Reference](https://sentrial.app/docs/api/auth)
|
|
154
|
+
- [LangChain Integration](https://sentrial.app/docs/integrations/langchain)
|
|
155
|
+
|
|
156
|
+
## Support
|
|
157
|
+
|
|
158
|
+
- Email: support@sentrial.ai
|
|
159
|
+
- Discord: [Join our community](https://discord.gg/sentrial)
|
|
160
|
+
- Issues: [GitHub Issues](https://github.com/neelshar/Sentrial/issues)
|
|
161
|
+
|
|
162
|
+
## License
|
|
163
|
+
|
|
164
|
+
MIT License - see [LICENSE](LICENSE) for details.
|
|
165
|
+
|
sentrial-0.1.2/README.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Sentrial Python SDK
|
|
2
|
+
|
|
3
|
+
Observability and time-travel debugging for AI agents. Track every decision, tool call, and state change in your agent workflows.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Automatic Tracking**: Capture agent reasoning, tool calls, and state changes
|
|
8
|
+
- **Git-like Branching**: Fork agent sessions at any point
|
|
9
|
+
- **Time Travel**: Replay and debug past agent executions
|
|
10
|
+
- **Framework Agnostic**: Works with LangChain, custom agents, and more
|
|
11
|
+
- **Visual UI**: Beautiful web interface to explore agent behavior
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
### From PyPI
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# Standard installation
|
|
19
|
+
pip install sentrial
|
|
20
|
+
|
|
21
|
+
# With LangChain integration
|
|
22
|
+
pip install sentrial[langchain]
|
|
23
|
+
|
|
24
|
+
# All integrations
|
|
25
|
+
pip install sentrial[all]
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### From GitHub
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install git+https://github.com/neelshar/Sentrial.git#subdirectory=packages/python-sdk
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Local Development
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
cd packages/python-sdk
|
|
38
|
+
pip install -e .
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Quick Start
|
|
42
|
+
|
|
43
|
+
### Basic Usage
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from sentrial import SentrialClient
|
|
47
|
+
|
|
48
|
+
# Initialize client
|
|
49
|
+
client = SentrialClient(
|
|
50
|
+
api_url="https://api.sentrial.app", # Or your self-hosted URL
|
|
51
|
+
project_id="your-project-id"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# Create a session
|
|
55
|
+
session_id = client.create_session(name="Customer Support Agent")
|
|
56
|
+
|
|
57
|
+
# Track tool calls
|
|
58
|
+
client.track_tool_call(
|
|
59
|
+
session_id=session_id,
|
|
60
|
+
tool_name="search_knowledge_base",
|
|
61
|
+
tool_input={"query": "password reset"},
|
|
62
|
+
tool_output={"articles": ["KB-001", "KB-002"]},
|
|
63
|
+
reasoning="Searching for relevant articles"
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# Track LLM decisions
|
|
67
|
+
client.track_decision(
|
|
68
|
+
session_id=session_id,
|
|
69
|
+
reasoning="User already tried KB solutions. Escalating to human support.",
|
|
70
|
+
alternatives=["Try another KB article", "Ask for more info"],
|
|
71
|
+
confidence=0.85
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# Close session
|
|
75
|
+
client.close_session(session_id)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### LangChain Integration
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from sentrial import SentrialClient, SentrialCallbackHandler
|
|
82
|
+
from langchain.agents import AgentExecutor, create_react_agent
|
|
83
|
+
|
|
84
|
+
# Initialize Sentrial
|
|
85
|
+
client = SentrialClient(api_url="...", project_id="...")
|
|
86
|
+
session_id = client.create_session(name="LangChain Agent")
|
|
87
|
+
|
|
88
|
+
# Create callback handler
|
|
89
|
+
handler = SentrialCallbackHandler(client, session_id, verbose=True)
|
|
90
|
+
|
|
91
|
+
# Use with LangChain - automatic tracking!
|
|
92
|
+
agent_executor = AgentExecutor(
|
|
93
|
+
agent=agent,
|
|
94
|
+
tools=tools,
|
|
95
|
+
callbacks=[handler], # ← That's it!
|
|
96
|
+
verbose=True
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
result = agent_executor.invoke({"input": "Help user with login issue"})
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The callback handler automatically tracks:
|
|
103
|
+
- Agent reasoning (Chain of Thought)
|
|
104
|
+
- Tool calls (inputs & outputs)
|
|
105
|
+
- Tool errors
|
|
106
|
+
- Agent completion
|
|
107
|
+
|
|
108
|
+
## Examples
|
|
109
|
+
|
|
110
|
+
- [simple_agent.py](https://github.com/neelshar/Sentrial/blob/main/examples/simple_agent.py) - Basic agent tracking
|
|
111
|
+
- [langchain_agent.py](https://github.com/neelshar/Sentrial/blob/main/examples/langchain_agent.py) - Full LangChain integration
|
|
112
|
+
|
|
113
|
+
## Documentation
|
|
114
|
+
|
|
115
|
+
- [Getting Started](https://sentrial.app/docs/quickstart)
|
|
116
|
+
- [API Reference](https://sentrial.app/docs/api/auth)
|
|
117
|
+
- [LangChain Integration](https://sentrial.app/docs/integrations/langchain)
|
|
118
|
+
|
|
119
|
+
## Support
|
|
120
|
+
|
|
121
|
+
- Email: support@sentrial.ai
|
|
122
|
+
- Discord: [Join our community](https://discord.gg/sentrial)
|
|
123
|
+
- Issues: [GitHub Issues](https://github.com/neelshar/Sentrial/issues)
|
|
124
|
+
|
|
125
|
+
## License
|
|
126
|
+
|
|
127
|
+
MIT License - see [LICENSE](LICENSE) for details.
|
|
128
|
+
|
sentrial-0.1.2/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "sentrial"
|
|
7
|
+
version = "0.1.2"
|
|
8
|
+
description = "Sentrial - Observability and time-travel debugging for AI agents"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
authors = [
|
|
11
|
+
{name = "Sentrial Team", email = "support@sentrial.ai"}
|
|
12
|
+
]
|
|
13
|
+
license = {text = "MIT"}
|
|
14
|
+
keywords = ["ai", "agents", "observability", "debugging", "langchain", "llm"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.8",
|
|
21
|
+
"Programming Language :: Python :: 3.9",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
26
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
27
|
+
]
|
|
28
|
+
requires-python = ">=3.8"
|
|
29
|
+
dependencies = [
|
|
30
|
+
"requests>=2.31.0",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.optional-dependencies]
|
|
34
|
+
langchain = [
|
|
35
|
+
"langchain>=0.1.0",
|
|
36
|
+
]
|
|
37
|
+
dev = [
|
|
38
|
+
"pytest>=7.4.0",
|
|
39
|
+
"black>=23.0.0",
|
|
40
|
+
"mypy>=1.5.0",
|
|
41
|
+
"ruff>=0.1.0",
|
|
42
|
+
]
|
|
43
|
+
all = [
|
|
44
|
+
"langchain>=0.1.0",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
[project.urls]
|
|
48
|
+
Homepage = "https://sentrial.app"
|
|
49
|
+
Documentation = "https://docs.sentrial.app"
|
|
50
|
+
Repository = "https://github.com/neelshar/sentrial"
|
|
51
|
+
"Bug Tracker" = "https://github.com/neelshar/sentrial/issues"
|
|
52
|
+
|
|
53
|
+
[tool.setuptools.packages.find]
|
|
54
|
+
where = ["."]
|
|
55
|
+
include = ["sentrial*"]
|
|
56
|
+
|
|
57
|
+
[tool.setuptools.package-data]
|
|
58
|
+
sentrial = ["py.typed"]
|
|
59
|
+
|
|
60
|
+
[tool.black]
|
|
61
|
+
line-length = 100
|
|
62
|
+
target-version = ["py38", "py39", "py310", "py311"]
|
|
63
|
+
|
|
64
|
+
[tool.ruff]
|
|
65
|
+
line-length = 100
|
|
66
|
+
target-version = "py38"
|
|
67
|
+
|
|
68
|
+
[tool.mypy]
|
|
69
|
+
python_version = "3.8"
|
|
70
|
+
strict = true
|
|
71
|
+
warn_return_any = true
|
|
72
|
+
warn_unused_configs = true
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Sentrial Python SDK
|
|
3
|
+
|
|
4
|
+
Simple SDK for tracking agent events and sending them to Sentrial.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .client import SentrialClient
|
|
8
|
+
from .types import EventType
|
|
9
|
+
|
|
10
|
+
__version__ = "0.1.2"
|
|
11
|
+
__all__ = ["SentrialClient", "EventType"]
|
|
12
|
+
|
|
13
|
+
# Optional LangChain integration (only available if langchain is installed)
|
|
14
|
+
try:
|
|
15
|
+
from .langchain import SentrialCallbackHandler
|
|
16
|
+
__all__.append("SentrialCallbackHandler")
|
|
17
|
+
except ImportError:
|
|
18
|
+
# LangChain not installed, skip
|
|
19
|
+
pass
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""Sentrial Client - Main SDK interface"""
|
|
2
|
+
|
|
3
|
+
import requests
|
|
4
|
+
from typing import Any, Optional
|
|
5
|
+
from .types import EventType
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SentrialClient:
|
|
9
|
+
"""
|
|
10
|
+
Sentrial Client for tracking agent events.
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
client = SentrialClient(
|
|
14
|
+
api_url="http://localhost:3001",
|
|
15
|
+
project_id="your-project-id"
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
# Create a session
|
|
19
|
+
session_id = client.create_session(name="My Agent Run")
|
|
20
|
+
|
|
21
|
+
# Track events
|
|
22
|
+
client.track_tool_call(
|
|
23
|
+
session_id=session_id,
|
|
24
|
+
tool_name="search",
|
|
25
|
+
tool_input={"query": "test"},
|
|
26
|
+
tool_output={"result": "success"}
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# Close session
|
|
30
|
+
client.close_session(session_id)
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
api_url: str = "http://localhost:3001",
|
|
36
|
+
project_id: str = "00000000-0000-0000-0000-000000000000",
|
|
37
|
+
api_key: Optional[str] = None,
|
|
38
|
+
):
|
|
39
|
+
"""
|
|
40
|
+
Initialize Sentrial client.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
api_url: URL of the Sentrial API server
|
|
44
|
+
project_id: Project ID (get from Sentrial dashboard)
|
|
45
|
+
api_key: API key for authentication
|
|
46
|
+
"""
|
|
47
|
+
self.api_url = api_url.rstrip("/")
|
|
48
|
+
self.project_id = project_id
|
|
49
|
+
self.api_key = api_key
|
|
50
|
+
self.session = requests.Session()
|
|
51
|
+
self.current_state: dict[str, Any] = {}
|
|
52
|
+
|
|
53
|
+
if api_key:
|
|
54
|
+
self.session.headers.update({"Authorization": f"Bearer {api_key}"})
|
|
55
|
+
|
|
56
|
+
def create_session(
|
|
57
|
+
self,
|
|
58
|
+
name: str,
|
|
59
|
+
branch_name: str = "main",
|
|
60
|
+
metadata: Optional[dict[str, Any]] = None,
|
|
61
|
+
) -> str:
|
|
62
|
+
"""
|
|
63
|
+
Create a new session.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
name: Name of the session
|
|
67
|
+
branch_name: Branch name (default: "main")
|
|
68
|
+
metadata: Optional metadata
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Session ID
|
|
72
|
+
"""
|
|
73
|
+
response = self.session.post(
|
|
74
|
+
f"{self.api_url}/api/sdk/sessions",
|
|
75
|
+
json={
|
|
76
|
+
"projectId": self.project_id,
|
|
77
|
+
"name": name,
|
|
78
|
+
"branchName": branch_name,
|
|
79
|
+
"metadata": metadata,
|
|
80
|
+
},
|
|
81
|
+
)
|
|
82
|
+
response.raise_for_status()
|
|
83
|
+
data = response.json()
|
|
84
|
+
return data["id"]
|
|
85
|
+
|
|
86
|
+
def track_tool_call(
|
|
87
|
+
self,
|
|
88
|
+
session_id: str,
|
|
89
|
+
tool_name: str,
|
|
90
|
+
tool_input: dict[str, Any],
|
|
91
|
+
tool_output: dict[str, Any],
|
|
92
|
+
reasoning: Optional[str] = None,
|
|
93
|
+
branch_name: str = "main",
|
|
94
|
+
) -> dict[str, Any]:
|
|
95
|
+
"""
|
|
96
|
+
Track a tool call event.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
session_id: Session ID
|
|
100
|
+
tool_name: Name of the tool
|
|
101
|
+
tool_input: Tool input data
|
|
102
|
+
tool_output: Tool output data
|
|
103
|
+
reasoning: Optional reasoning
|
|
104
|
+
branch_name: Branch name (default: "main")
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Event data
|
|
108
|
+
"""
|
|
109
|
+
state_before = self.current_state.copy()
|
|
110
|
+
|
|
111
|
+
# Update current state (simplified)
|
|
112
|
+
self.current_state[f"{tool_name}_result"] = tool_output
|
|
113
|
+
|
|
114
|
+
response = self.session.post(
|
|
115
|
+
f"{self.api_url}/api/sdk/events",
|
|
116
|
+
json={
|
|
117
|
+
"sessionId": session_id,
|
|
118
|
+
"eventType": EventType.TOOL_CALL.value,
|
|
119
|
+
"toolName": tool_name,
|
|
120
|
+
"toolInput": tool_input,
|
|
121
|
+
"toolOutput": tool_output,
|
|
122
|
+
"reasoning": reasoning,
|
|
123
|
+
"stateBefore": state_before,
|
|
124
|
+
"stateAfter": self.current_state.copy(),
|
|
125
|
+
"branchName": branch_name,
|
|
126
|
+
},
|
|
127
|
+
)
|
|
128
|
+
response.raise_for_status()
|
|
129
|
+
return response.json()
|
|
130
|
+
|
|
131
|
+
def track_decision(
|
|
132
|
+
self,
|
|
133
|
+
session_id: str,
|
|
134
|
+
reasoning: str,
|
|
135
|
+
alternatives: Optional[list[str]] = None,
|
|
136
|
+
confidence: Optional[float] = None,
|
|
137
|
+
branch_name: str = "main",
|
|
138
|
+
) -> dict[str, Any]:
|
|
139
|
+
"""
|
|
140
|
+
Track an LLM decision event.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
session_id: Session ID
|
|
144
|
+
reasoning: Decision reasoning
|
|
145
|
+
alternatives: Alternative options considered
|
|
146
|
+
confidence: Confidence score (0.0 to 1.0)
|
|
147
|
+
branch_name: Branch name (default: "main")
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
Event data
|
|
151
|
+
"""
|
|
152
|
+
state_before = self.current_state.copy()
|
|
153
|
+
|
|
154
|
+
response = self.session.post(
|
|
155
|
+
f"{self.api_url}/api/sdk/events",
|
|
156
|
+
json={
|
|
157
|
+
"sessionId": session_id,
|
|
158
|
+
"eventType": EventType.LLM_DECISION.value,
|
|
159
|
+
"reasoning": reasoning,
|
|
160
|
+
"alternativesConsidered": alternatives,
|
|
161
|
+
"confidence": confidence,
|
|
162
|
+
"stateBefore": state_before,
|
|
163
|
+
"stateAfter": self.current_state.copy(),
|
|
164
|
+
"branchName": branch_name,
|
|
165
|
+
},
|
|
166
|
+
)
|
|
167
|
+
response.raise_for_status()
|
|
168
|
+
return response.json()
|
|
169
|
+
|
|
170
|
+
def update_state(self, key: str, value: Any):
|
|
171
|
+
"""Update the current state."""
|
|
172
|
+
self.current_state[key] = value
|
|
173
|
+
|
|
174
|
+
def close_session(self, session_id: str, duration_ms: Optional[int] = None):
|
|
175
|
+
"""
|
|
176
|
+
Mark a session as completed.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
session_id: Session ID
|
|
180
|
+
duration_ms: Duration in milliseconds
|
|
181
|
+
"""
|
|
182
|
+
response = self.session.patch(
|
|
183
|
+
f"{self.api_url}/api/sdk/sessions/{session_id}",
|
|
184
|
+
json={
|
|
185
|
+
"status": "completed",
|
|
186
|
+
"durationMs": duration_ms,
|
|
187
|
+
},
|
|
188
|
+
)
|
|
189
|
+
response.raise_for_status()
|
|
190
|
+
return response.json()
|
|
191
|
+
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
"""LangChain integration for Sentrial observability."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List, Optional
|
|
4
|
+
from uuid import UUID
|
|
5
|
+
|
|
6
|
+
try:
|
|
7
|
+
from langchain_core.callbacks.base import BaseCallbackHandler
|
|
8
|
+
from langchain_core.agents import AgentAction, AgentFinish
|
|
9
|
+
from langchain_core.outputs import LLMResult
|
|
10
|
+
except ImportError:
|
|
11
|
+
raise ImportError(
|
|
12
|
+
"LangChain is required for this integration. "
|
|
13
|
+
"Install it with: pip install langchain-core"
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
from .client import SentrialClient
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SentrialCallbackHandler(BaseCallbackHandler):
|
|
20
|
+
"""
|
|
21
|
+
LangChain callback handler for Sentrial observability.
|
|
22
|
+
|
|
23
|
+
Automatically tracks:
|
|
24
|
+
- Agent reasoning (Chain of Thought)
|
|
25
|
+
- Tool executions (with inputs/outputs)
|
|
26
|
+
- Tool errors
|
|
27
|
+
- LLM calls
|
|
28
|
+
|
|
29
|
+
Usage:
|
|
30
|
+
from sentrial import SentrialClient
|
|
31
|
+
from sentrial.langchain import SentrialCallbackHandler
|
|
32
|
+
|
|
33
|
+
client = SentrialClient(api_url="...", project_id="...")
|
|
34
|
+
session_id = client.create_session(name="My Agent")
|
|
35
|
+
|
|
36
|
+
handler = SentrialCallbackHandler(client, session_id)
|
|
37
|
+
|
|
38
|
+
# Pass to LangChain agent
|
|
39
|
+
agent_executor = AgentExecutor(
|
|
40
|
+
agent=agent,
|
|
41
|
+
tools=tools,
|
|
42
|
+
callbacks=[handler],
|
|
43
|
+
verbose=True
|
|
44
|
+
)
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
client: SentrialClient,
|
|
50
|
+
session_id: str,
|
|
51
|
+
track_llm_calls: bool = False,
|
|
52
|
+
verbose: bool = False
|
|
53
|
+
):
|
|
54
|
+
"""
|
|
55
|
+
Initialize Sentrial callback handler.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
client: SentrialClient instance
|
|
59
|
+
session_id: Active session ID
|
|
60
|
+
track_llm_calls: Whether to track individual LLM API calls (default: False)
|
|
61
|
+
verbose: Print tracking info (default: False)
|
|
62
|
+
"""
|
|
63
|
+
super().__init__()
|
|
64
|
+
self.client = client
|
|
65
|
+
self.session_id = session_id
|
|
66
|
+
self.track_llm_calls = track_llm_calls
|
|
67
|
+
self.verbose = verbose
|
|
68
|
+
|
|
69
|
+
# Track tool runs in flight (run_id -> tool data)
|
|
70
|
+
self.tool_runs: Dict[str, Dict[str, Any]] = {}
|
|
71
|
+
|
|
72
|
+
# Track pending agent actions (for correlating with tool outputs)
|
|
73
|
+
self.pending_actions: Dict[str, Dict[str, Any]] = {}
|
|
74
|
+
|
|
75
|
+
# Track agent steps
|
|
76
|
+
self.step_count = 0
|
|
77
|
+
|
|
78
|
+
def _log(self, message: str):
|
|
79
|
+
"""Log if verbose mode is enabled."""
|
|
80
|
+
if self.verbose:
|
|
81
|
+
print(f"[Sentrial] {message}")
|
|
82
|
+
|
|
83
|
+
# ===== Agent Reasoning (Chain of Thought) =====
|
|
84
|
+
|
|
85
|
+
def on_agent_action(
|
|
86
|
+
self,
|
|
87
|
+
action: AgentAction,
|
|
88
|
+
*,
|
|
89
|
+
run_id: UUID,
|
|
90
|
+
parent_run_id: Optional[UUID] = None,
|
|
91
|
+
**kwargs: Any
|
|
92
|
+
) -> None:
|
|
93
|
+
"""
|
|
94
|
+
Capture agent reasoning (Chain of Thought) and tool calls.
|
|
95
|
+
|
|
96
|
+
This is called when the agent decides what action to take.
|
|
97
|
+
The action.log contains the full "Thought: ... Action: ..." trace.
|
|
98
|
+
|
|
99
|
+
For agents that don't trigger on_tool_start/on_tool_end separately
|
|
100
|
+
(like function-calling agents or Gemini), we track the tool call here.
|
|
101
|
+
"""
|
|
102
|
+
self.step_count += 1
|
|
103
|
+
reasoning = action.log if action.log else f"Action: {action.tool}"
|
|
104
|
+
tool_name = action.tool
|
|
105
|
+
tool_input = action.tool_input
|
|
106
|
+
|
|
107
|
+
self._log(f"Step {self.step_count}: Agent action - {tool_name}")
|
|
108
|
+
|
|
109
|
+
# Store the pending action for correlation with tool output (if on_tool_end fires)
|
|
110
|
+
self.pending_actions[str(run_id)] = {
|
|
111
|
+
"tool": tool_name,
|
|
112
|
+
"input": tool_input,
|
|
113
|
+
"reasoning": reasoning,
|
|
114
|
+
"tracked": False # Will be set to True if we track from on_tool_end
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
# Track as tool_call with the available info
|
|
118
|
+
# This ensures tools are tracked even if on_tool_start/on_tool_end don't fire
|
|
119
|
+
input_dict = tool_input if isinstance(tool_input, dict) else {"input": str(tool_input) if tool_input else ""}
|
|
120
|
+
|
|
121
|
+
self.client.track_tool_call(
|
|
122
|
+
session_id=self.session_id,
|
|
123
|
+
tool_name=tool_name,
|
|
124
|
+
tool_input=input_dict,
|
|
125
|
+
tool_output={"status": "executed"}, # Placeholder, updated if on_tool_end fires
|
|
126
|
+
reasoning=reasoning
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
# Mark as tracked so on_tool_end doesn't duplicate
|
|
130
|
+
self.pending_actions[str(run_id)]["tracked"] = True
|
|
131
|
+
|
|
132
|
+
def on_agent_finish(
|
|
133
|
+
self,
|
|
134
|
+
finish: AgentFinish,
|
|
135
|
+
*,
|
|
136
|
+
run_id: UUID,
|
|
137
|
+
parent_run_id: Optional[UUID] = None,
|
|
138
|
+
**kwargs: Any
|
|
139
|
+
) -> None:
|
|
140
|
+
"""Track agent completion."""
|
|
141
|
+
self._log("Agent finished")
|
|
142
|
+
|
|
143
|
+
self.client.track_decision(
|
|
144
|
+
session_id=self.session_id,
|
|
145
|
+
reasoning=f"Agent completed: {finish.return_values.get('output', 'No output')}",
|
|
146
|
+
confidence=1.0
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
# ===== Tool Execution =====
|
|
150
|
+
|
|
151
|
+
def on_tool_start(
|
|
152
|
+
self,
|
|
153
|
+
serialized: Dict[str, Any],
|
|
154
|
+
input_str: str,
|
|
155
|
+
*,
|
|
156
|
+
run_id: UUID,
|
|
157
|
+
parent_run_id: Optional[UUID] = None,
|
|
158
|
+
**kwargs: Any
|
|
159
|
+
) -> None:
|
|
160
|
+
"""Buffer tool inputs for correlation with outputs."""
|
|
161
|
+
tool_name = serialized.get("name", "unknown_tool")
|
|
162
|
+
|
|
163
|
+
self._log(f"Tool started: {tool_name}")
|
|
164
|
+
|
|
165
|
+
# Check if we have a pending action for this tool from on_agent_action
|
|
166
|
+
parent_id = str(parent_run_id) if parent_run_id else None
|
|
167
|
+
pending = None
|
|
168
|
+
if parent_id and parent_id in self.pending_actions:
|
|
169
|
+
pending = self.pending_actions.get(parent_id)
|
|
170
|
+
|
|
171
|
+
self.tool_runs[str(run_id)] = {
|
|
172
|
+
"name": tool_name,
|
|
173
|
+
"input": input_str,
|
|
174
|
+
"pending_action": pending,
|
|
175
|
+
"parent_run_id": parent_id
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
def on_tool_end(
|
|
179
|
+
self,
|
|
180
|
+
output: str,
|
|
181
|
+
*,
|
|
182
|
+
run_id: UUID,
|
|
183
|
+
parent_run_id: Optional[UUID] = None,
|
|
184
|
+
**kwargs: Any
|
|
185
|
+
) -> None:
|
|
186
|
+
"""Track successful tool execution with full input/output."""
|
|
187
|
+
run_data = self.tool_runs.pop(str(run_id), None)
|
|
188
|
+
|
|
189
|
+
# Check if this was already tracked from on_agent_action
|
|
190
|
+
parent_id = str(parent_run_id) if parent_run_id else None
|
|
191
|
+
if parent_id and parent_id in self.pending_actions:
|
|
192
|
+
pending = self.pending_actions[parent_id]
|
|
193
|
+
if pending.get("tracked"):
|
|
194
|
+
# Already tracked from on_agent_action, just log
|
|
195
|
+
self._log(f"Tool output received: {pending['tool']} (already tracked)")
|
|
196
|
+
del self.pending_actions[parent_id]
|
|
197
|
+
return
|
|
198
|
+
|
|
199
|
+
if run_data:
|
|
200
|
+
self._log(f"Tool completed: {run_data['name']}")
|
|
201
|
+
|
|
202
|
+
# Parse tool input - try to get structured data
|
|
203
|
+
tool_input = run_data.get("input", "")
|
|
204
|
+
if isinstance(tool_input, str):
|
|
205
|
+
# Try to parse as JSON
|
|
206
|
+
import json
|
|
207
|
+
try:
|
|
208
|
+
tool_input = json.loads(tool_input)
|
|
209
|
+
except (json.JSONDecodeError, TypeError):
|
|
210
|
+
tool_input = {"input": tool_input}
|
|
211
|
+
|
|
212
|
+
# Parse tool output
|
|
213
|
+
tool_output = output
|
|
214
|
+
if isinstance(tool_output, str):
|
|
215
|
+
import json
|
|
216
|
+
try:
|
|
217
|
+
tool_output = json.loads(tool_output)
|
|
218
|
+
except (json.JSONDecodeError, TypeError):
|
|
219
|
+
tool_output = {"output": tool_output}
|
|
220
|
+
|
|
221
|
+
# Get reasoning from pending action if available
|
|
222
|
+
pending = run_data.get("pending_action")
|
|
223
|
+
reasoning = pending.get("reasoning") if pending else f"Executed {run_data['name']}"
|
|
224
|
+
|
|
225
|
+
self.client.track_tool_call(
|
|
226
|
+
session_id=self.session_id,
|
|
227
|
+
tool_name=run_data["name"],
|
|
228
|
+
tool_input=tool_input if isinstance(tool_input, dict) else {"input": tool_input},
|
|
229
|
+
tool_output=tool_output if isinstance(tool_output, dict) else {"output": tool_output},
|
|
230
|
+
reasoning=reasoning
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
# Clean up pending action if we had one
|
|
234
|
+
parent_id = run_data.get("parent_run_id")
|
|
235
|
+
if parent_id and parent_id in self.pending_actions:
|
|
236
|
+
del self.pending_actions[parent_id]
|
|
237
|
+
else:
|
|
238
|
+
self._log(f"Tool completed but no start event found (run_id: {run_id})")
|
|
239
|
+
|
|
240
|
+
def on_tool_error(
|
|
241
|
+
self,
|
|
242
|
+
error: Exception,
|
|
243
|
+
*,
|
|
244
|
+
run_id: UUID,
|
|
245
|
+
parent_run_id: Optional[UUID] = None,
|
|
246
|
+
**kwargs: Any
|
|
247
|
+
) -> None:
|
|
248
|
+
"""Track tool failures."""
|
|
249
|
+
run_data = self.tool_runs.pop(str(run_id), None)
|
|
250
|
+
|
|
251
|
+
if run_data:
|
|
252
|
+
self._log(f"Tool failed: {run_data['name']} - {str(error)}")
|
|
253
|
+
|
|
254
|
+
self.client.track_tool_call(
|
|
255
|
+
session_id=self.session_id,
|
|
256
|
+
tool_name=run_data["name"],
|
|
257
|
+
tool_input={"input": run_data["input"]},
|
|
258
|
+
tool_output={"error": str(error), "error_type": type(error).__name__},
|
|
259
|
+
reasoning=f"Tool execution failed: {str(error)}"
|
|
260
|
+
)
|
|
261
|
+
else:
|
|
262
|
+
self._log(f"Tool failed but no start event found (run_id: {run_id})")
|
|
263
|
+
|
|
264
|
+
# ===== LLM Calls (Optional) =====
|
|
265
|
+
|
|
266
|
+
def on_llm_start(
|
|
267
|
+
self,
|
|
268
|
+
serialized: Dict[str, Any],
|
|
269
|
+
prompts: List[str],
|
|
270
|
+
*,
|
|
271
|
+
run_id: UUID,
|
|
272
|
+
parent_run_id: Optional[UUID] = None,
|
|
273
|
+
**kwargs: Any
|
|
274
|
+
) -> None:
|
|
275
|
+
"""Track LLM API calls (if enabled)."""
|
|
276
|
+
if not self.track_llm_calls:
|
|
277
|
+
return
|
|
278
|
+
|
|
279
|
+
model_name = serialized.get("name", "unknown_model")
|
|
280
|
+
self._log(f"LLM call started: {model_name}")
|
|
281
|
+
|
|
282
|
+
def on_llm_end(
|
|
283
|
+
self,
|
|
284
|
+
response: LLMResult,
|
|
285
|
+
*,
|
|
286
|
+
run_id: UUID,
|
|
287
|
+
parent_run_id: Optional[UUID] = None,
|
|
288
|
+
**kwargs: Any
|
|
289
|
+
) -> None:
|
|
290
|
+
"""Track LLM completion (if enabled)."""
|
|
291
|
+
if not self.track_llm_calls:
|
|
292
|
+
return
|
|
293
|
+
|
|
294
|
+
self._log("LLM call completed")
|
|
295
|
+
|
|
296
|
+
# Could track as a special event type if needed
|
|
297
|
+
# For now, we focus on agent-level reasoning and tools
|
|
298
|
+
|
|
299
|
+
def on_llm_error(
|
|
300
|
+
self,
|
|
301
|
+
error: Exception,
|
|
302
|
+
*,
|
|
303
|
+
run_id: UUID,
|
|
304
|
+
parent_run_id: Optional[UUID] = None,
|
|
305
|
+
**kwargs: Any
|
|
306
|
+
) -> None:
|
|
307
|
+
"""Track LLM errors (if enabled)."""
|
|
308
|
+
if not self.track_llm_calls:
|
|
309
|
+
return
|
|
310
|
+
|
|
311
|
+
self._log(f"LLM call failed: {str(error)}")
|
|
312
|
+
|
|
313
|
+
# ===== Chain Events (Optional, for future use) =====
|
|
314
|
+
|
|
315
|
+
def on_chain_start(
|
|
316
|
+
self,
|
|
317
|
+
serialized: Dict[str, Any],
|
|
318
|
+
inputs: Dict[str, Any],
|
|
319
|
+
*,
|
|
320
|
+
run_id: UUID,
|
|
321
|
+
parent_run_id: Optional[UUID] = None,
|
|
322
|
+
**kwargs: Any
|
|
323
|
+
) -> None:
|
|
324
|
+
"""Track chain execution start."""
|
|
325
|
+
pass # Can be used for more granular tracking if needed
|
|
326
|
+
|
|
327
|
+
def on_chain_end(
|
|
328
|
+
self,
|
|
329
|
+
outputs: Dict[str, Any],
|
|
330
|
+
*,
|
|
331
|
+
run_id: UUID,
|
|
332
|
+
parent_run_id: Optional[UUID] = None,
|
|
333
|
+
**kwargs: Any
|
|
334
|
+
) -> None:
|
|
335
|
+
"""Track chain execution end."""
|
|
336
|
+
pass # Can be used for more granular tracking if needed
|
|
337
|
+
|
|
338
|
+
def on_chain_error(
|
|
339
|
+
self,
|
|
340
|
+
error: Exception,
|
|
341
|
+
*,
|
|
342
|
+
run_id: UUID,
|
|
343
|
+
parent_run_id: Optional[UUID] = None,
|
|
344
|
+
**kwargs: Any
|
|
345
|
+
) -> None:
|
|
346
|
+
"""Track chain execution errors."""
|
|
347
|
+
self._log(f"Chain failed: {str(error)}")
|
|
348
|
+
|
|
349
|
+
self.client.track_decision(
|
|
350
|
+
session_id=self.session_id,
|
|
351
|
+
reasoning=f"Chain execution failed: {str(error)}",
|
|
352
|
+
confidence=0.0
|
|
353
|
+
)
|
|
354
|
+
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Type definitions for Sentrial SDK"""
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from typing import Any, Optional, TypedDict
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class EventType(str, Enum):
|
|
8
|
+
"""Event types"""
|
|
9
|
+
|
|
10
|
+
TOOL_CALL = "tool_call"
|
|
11
|
+
LLM_DECISION = "llm_decision"
|
|
12
|
+
STATE_CHANGE = "state_change"
|
|
13
|
+
ERROR = "error"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ToolCallEvent(TypedDict, total=False):
|
|
17
|
+
"""Tool call event data"""
|
|
18
|
+
|
|
19
|
+
session_id: str
|
|
20
|
+
event_type: str
|
|
21
|
+
tool_name: str
|
|
22
|
+
tool_input: dict[str, Any]
|
|
23
|
+
tool_output: dict[str, Any]
|
|
24
|
+
tool_error: Optional[dict[str, Any]]
|
|
25
|
+
reasoning: Optional[str]
|
|
26
|
+
state_before: Optional[dict[str, Any]]
|
|
27
|
+
state_after: Optional[dict[str, Any]]
|
|
28
|
+
branch_name: str
|
|
29
|
+
metadata: Optional[dict[str, Any]]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class DecisionEvent(TypedDict, total=False):
|
|
33
|
+
"""LLM decision event data"""
|
|
34
|
+
|
|
35
|
+
session_id: str
|
|
36
|
+
event_type: str
|
|
37
|
+
reasoning: str
|
|
38
|
+
alternatives_considered: Optional[list[str]]
|
|
39
|
+
confidence: Optional[float]
|
|
40
|
+
state_before: Optional[dict[str, Any]]
|
|
41
|
+
state_after: Optional[dict[str, Any]]
|
|
42
|
+
branch_name: str
|
|
43
|
+
metadata: Optional[dict[str, Any]]
|
|
44
|
+
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sentrial
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: Sentrial - Observability and time-travel debugging for AI agents
|
|
5
|
+
Author-email: Sentrial Team <support@sentrial.ai>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://sentrial.app
|
|
8
|
+
Project-URL: Documentation, https://docs.sentrial.app
|
|
9
|
+
Project-URL: Repository, https://github.com/neelshar/sentrial
|
|
10
|
+
Project-URL: Bug Tracker, https://github.com/neelshar/sentrial/issues
|
|
11
|
+
Keywords: ai,agents,observability,debugging,langchain,llm
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
23
|
+
Requires-Python: >=3.8
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: requests>=2.31.0
|
|
27
|
+
Provides-Extra: langchain
|
|
28
|
+
Requires-Dist: langchain>=0.1.0; extra == "langchain"
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: pytest>=7.4.0; extra == "dev"
|
|
31
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
32
|
+
Requires-Dist: mypy>=1.5.0; extra == "dev"
|
|
33
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
34
|
+
Provides-Extra: all
|
|
35
|
+
Requires-Dist: langchain>=0.1.0; extra == "all"
|
|
36
|
+
Dynamic: license-file
|
|
37
|
+
|
|
38
|
+
# Sentrial Python SDK
|
|
39
|
+
|
|
40
|
+
Observability and time-travel debugging for AI agents. Track every decision, tool call, and state change in your agent workflows.
|
|
41
|
+
|
|
42
|
+
## Features
|
|
43
|
+
|
|
44
|
+
- **Automatic Tracking**: Capture agent reasoning, tool calls, and state changes
|
|
45
|
+
- **Git-like Branching**: Fork agent sessions at any point
|
|
46
|
+
- **Time Travel**: Replay and debug past agent executions
|
|
47
|
+
- **Framework Agnostic**: Works with LangChain, custom agents, and more
|
|
48
|
+
- **Visual UI**: Beautiful web interface to explore agent behavior
|
|
49
|
+
|
|
50
|
+
## Installation
|
|
51
|
+
|
|
52
|
+
### From PyPI
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# Standard installation
|
|
56
|
+
pip install sentrial
|
|
57
|
+
|
|
58
|
+
# With LangChain integration
|
|
59
|
+
pip install sentrial[langchain]
|
|
60
|
+
|
|
61
|
+
# All integrations
|
|
62
|
+
pip install sentrial[all]
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### From GitHub
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pip install git+https://github.com/neelshar/Sentrial.git#subdirectory=packages/python-sdk
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Local Development
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
cd packages/python-sdk
|
|
75
|
+
pip install -e .
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Quick Start
|
|
79
|
+
|
|
80
|
+
### Basic Usage
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from sentrial import SentrialClient
|
|
84
|
+
|
|
85
|
+
# Initialize client
|
|
86
|
+
client = SentrialClient(
|
|
87
|
+
api_url="https://api.sentrial.app", # Or your self-hosted URL
|
|
88
|
+
project_id="your-project-id"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# Create a session
|
|
92
|
+
session_id = client.create_session(name="Customer Support Agent")
|
|
93
|
+
|
|
94
|
+
# Track tool calls
|
|
95
|
+
client.track_tool_call(
|
|
96
|
+
session_id=session_id,
|
|
97
|
+
tool_name="search_knowledge_base",
|
|
98
|
+
tool_input={"query": "password reset"},
|
|
99
|
+
tool_output={"articles": ["KB-001", "KB-002"]},
|
|
100
|
+
reasoning="Searching for relevant articles"
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# Track LLM decisions
|
|
104
|
+
client.track_decision(
|
|
105
|
+
session_id=session_id,
|
|
106
|
+
reasoning="User already tried KB solutions. Escalating to human support.",
|
|
107
|
+
alternatives=["Try another KB article", "Ask for more info"],
|
|
108
|
+
confidence=0.85
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# Close session
|
|
112
|
+
client.close_session(session_id)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### LangChain Integration
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
from sentrial import SentrialClient, SentrialCallbackHandler
|
|
119
|
+
from langchain.agents import AgentExecutor, create_react_agent
|
|
120
|
+
|
|
121
|
+
# Initialize Sentrial
|
|
122
|
+
client = SentrialClient(api_url="...", project_id="...")
|
|
123
|
+
session_id = client.create_session(name="LangChain Agent")
|
|
124
|
+
|
|
125
|
+
# Create callback handler
|
|
126
|
+
handler = SentrialCallbackHandler(client, session_id, verbose=True)
|
|
127
|
+
|
|
128
|
+
# Use with LangChain - automatic tracking!
|
|
129
|
+
agent_executor = AgentExecutor(
|
|
130
|
+
agent=agent,
|
|
131
|
+
tools=tools,
|
|
132
|
+
callbacks=[handler], # ← That's it!
|
|
133
|
+
verbose=True
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
result = agent_executor.invoke({"input": "Help user with login issue"})
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
The callback handler automatically tracks:
|
|
140
|
+
- Agent reasoning (Chain of Thought)
|
|
141
|
+
- Tool calls (inputs & outputs)
|
|
142
|
+
- Tool errors
|
|
143
|
+
- Agent completion
|
|
144
|
+
|
|
145
|
+
## Examples
|
|
146
|
+
|
|
147
|
+
- [simple_agent.py](https://github.com/neelshar/Sentrial/blob/main/examples/simple_agent.py) - Basic agent tracking
|
|
148
|
+
- [langchain_agent.py](https://github.com/neelshar/Sentrial/blob/main/examples/langchain_agent.py) - Full LangChain integration
|
|
149
|
+
|
|
150
|
+
## Documentation
|
|
151
|
+
|
|
152
|
+
- [Getting Started](https://sentrial.app/docs/quickstart)
|
|
153
|
+
- [API Reference](https://sentrial.app/docs/api/auth)
|
|
154
|
+
- [LangChain Integration](https://sentrial.app/docs/integrations/langchain)
|
|
155
|
+
|
|
156
|
+
## Support
|
|
157
|
+
|
|
158
|
+
- Email: support@sentrial.ai
|
|
159
|
+
- Discord: [Join our community](https://discord.gg/sentrial)
|
|
160
|
+
- Issues: [GitHub Issues](https://github.com/neelshar/Sentrial/issues)
|
|
161
|
+
|
|
162
|
+
## License
|
|
163
|
+
|
|
164
|
+
MIT License - see [LICENSE](LICENSE) for details.
|
|
165
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
MANIFEST.in
|
|
3
|
+
README.md
|
|
4
|
+
py.typed
|
|
5
|
+
pyproject.toml
|
|
6
|
+
sentrial/__init__.py
|
|
7
|
+
sentrial/client.py
|
|
8
|
+
sentrial/langchain.py
|
|
9
|
+
sentrial/types.py
|
|
10
|
+
sentrial.egg-info/PKG-INFO
|
|
11
|
+
sentrial.egg-info/SOURCES.txt
|
|
12
|
+
sentrial.egg-info/dependency_links.txt
|
|
13
|
+
sentrial.egg-info/requires.txt
|
|
14
|
+
sentrial.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
sentrial
|
sentrial-0.1.2/setup.cfg
ADDED