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 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
+
@@ -0,0 +1,6 @@
1
+ include README.md
2
+ include LICENSE
3
+ include py.typed
4
+ recursive-include sentrial *.py
5
+ recursive-include sentrial py.typed
6
+
@@ -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,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
+
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,13 @@
1
+ requests>=2.31.0
2
+
3
+ [all]
4
+ langchain>=0.1.0
5
+
6
+ [dev]
7
+ pytest>=7.4.0
8
+ black>=23.0.0
9
+ mypy>=1.5.0
10
+ ruff>=0.1.0
11
+
12
+ [langchain]
13
+ langchain>=0.1.0
@@ -0,0 +1 @@
1
+ sentrial
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+