Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 17 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,11 @@ session = client.session(
},
)

session.user_message("I am locked out of my account")
interaction = session.interaction(interaction_id="interaction_123")

session.retrieval(
interaction.user_message("I am locked out of my account")

interaction.retrieval(
query="reset password locked out",
documents=[
{
Expand All @@ -83,15 +85,15 @@ session.retrieval(
metrics={"latency_ms": 81, "documents_found": 1},
)

session.tool_call(
interaction.tool_call(
tool_name="lookup_account",
input={"account_id": "acct_987"},
output={"status": "locked", "password_reset_available": True},
metadata={"service": "account-service"},
metrics={"latency_ms": 117, "http_status": 200},
)

session.assistant_response(
interaction.assistant_response(
model="gpt-5.4-mini",
request={"messages": [{"role": "user", "content": "I am locked out of my account"}]},
response={"text": "I found your account. Use the reset link and follow the email prompt."},
Expand All @@ -105,6 +107,7 @@ That emits structured events like:
{
"type": "llm",
"session_id": "thread_123",
"interaction_id": "interaction_123",
"tags": ["production"],
"context": {"environment": "prod", "user_id": "user_123", "workspace_id": "ws_456"},
"observation": {
Expand All @@ -119,7 +122,7 @@ That emits structured events like:
Import the main types from `sessionbat`:

```python
from sessionbat import SessionBat, Session, LangChainCallbackHandler
from sessionbat import SessionBat, Session, Interaction, LangChainCallbackHandler
```

### `SessionBat`
Expand All @@ -134,8 +137,8 @@ client = SessionBat(
)
```

Use `client.session(...)` to create a session and record observations against a
stable `session_id`.
Use `client.session(...)` to create a session with a stable `session_id`, then
use `session.interaction(...)` to record observations against a specific turn or interaction.

The SDK sends events to SessionBat ingestion by default. Pass `api_key` directly
or set `SESSIONBAT_API_KEY`. For tests or local debugging, pass an explicit
Expand All @@ -147,15 +150,15 @@ bounded backoff, and queued events are flushed automatically during interpreter
shutdown. Call `client.flush()` or `client.close()` when you need to wait for
delivery before exiting a short-lived process.

### `Session`
### `Session` and `Interaction`

A `Session` records completed observations:
A `Session` groups interactions. A `Interaction` records completed observations:

- `session.user_message(content)`
- `session.message(role=..., content=...)`
- `session.assistant_response(...)`
- `session.tool_call(...)`
- `session.retrieval(...)`
- `interaction.user_message(content)`
- `interaction.message(role=..., content=...)`
- `interaction.assistant_response(...)`
- `interaction.tool_call(...)`
- `interaction.retrieval(...)`

Each call returns the generated observation id.

Expand Down
5 changes: 3 additions & 2 deletions examples/failure_model_error.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@ def main() -> None:
"plan": "starter",
},
)
interaction = session.interaction(interaction_id="interaction_model_error")

session.user_message("What invoices are overdue for Acme Corp?")
interaction.user_message("What invoices are overdue for Acme Corp?")

session.assistant_response(
interaction.assistant_response(
model="gpt-5.4-mini",
request={
"messages": [
Expand Down
9 changes: 5 additions & 4 deletions examples/failure_retrieval_miss.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@ def main() -> None:
"plan": "pro",
},
)
interaction = session.interaction(interaction_id="interaction_retrieval_miss")

session.user_message("How do I export audit logs for SSO users?")
interaction.user_message("How do I export audit logs for SSO users?")

session.retrieval(
interaction.retrieval(
query="export audit logs for sso users",
documents=[],
metadata={
Expand All @@ -33,7 +34,7 @@ def main() -> None:
metrics={"latency_ms": 63, "documents_found": 0},
)

session.assistant_response(
interaction.assistant_response(
model="gpt-5.4-mini",
request={
"messages": [
Expand All @@ -46,7 +47,7 @@ def main() -> None:
metadata={"provider": "openai"},
metrics={"latency_ms": 704, "input_tokens": 118, "output_tokens": 24},
)
session.user_message(
interaction.user_message(
"Can an admin download SAML access logs anywhere?",
metadata={"sequence": "follow_up"},
)
Expand Down
11 changes: 6 additions & 5 deletions examples/failure_tool_loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,34 +19,35 @@ def main() -> None:
"plan": "enterprise",
},
)
interaction = session.interaction(interaction_id="interaction_tool_loop")

session.user_message("Why is my workspace usage total wrong?")
interaction.user_message("Why is my workspace usage total wrong?")

session.tool_call(
interaction.tool_call(
tool_name="get_workspace_usage",
input={"workspace_id": "ws_999"},
output={"total_tokens": 192044, "cached": False},
metadata={"service": "billing-service"},
metrics={"latency_ms": 141, "http_status": 200, "attempt": 1},
)

session.tool_call(
interaction.tool_call(
tool_name="get_workspace_usage",
input={"workspace_id": "ws_999"},
output={"total_tokens": 192044, "cached": False},
metadata={"service": "billing-service"},
metrics={"latency_ms": 136, "http_status": 200, "attempt": 2},
)

session.tool_call(
interaction.tool_call(
tool_name="get_workspace_usage",
input={"workspace_id": "ws_999"},
output={"total_tokens": 192044, "cached": False},
metadata={"service": "billing-service"},
metrics={"latency_ms": 139, "http_status": 200, "attempt": 3},
)

session.assistant_response(
interaction.assistant_response(
model="gpt-5.4-mini",
request={
"messages": [
Expand Down
9 changes: 5 additions & 4 deletions examples/manual_trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@ def main() -> None:
"plan": "pro",
},
)
interaction = session.interaction(interaction_id="interaction_123")

session.user_message("I am locked out of my account")
interaction.user_message("I am locked out of my account")

session.retrieval(
interaction.retrieval(
query="reset password locked out",
documents=[
{
Expand All @@ -39,15 +40,15 @@ def main() -> None:
metrics={"latency_ms": 81, "documents_found": 1},
)

session.tool_call(
interaction.tool_call(
tool_name="lookup_account",
input={"account_id": "acct_987"},
output={"status": "locked", "password_reset_available": True},
metadata={"service": "account-service"},
metrics={"latency_ms": 117, "http_status": 200},
)

session.assistant_response(
interaction.assistant_response(
model="gpt-5.4-mini",
request={"messages": [{"role": "user", "content": "I am locked out of my account"}]},
response={"text": "I found your account. Use the reset link and follow the email prompt."},
Expand Down
3 changes: 2 additions & 1 deletion src/sessionbat/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from .client import Session, SessionBat
from .client import Interaction, Session, SessionBat
from .langchain import LangChainCallbackHandler, SessionBatCallbackHandler

__all__ = [
"SessionBat",
"Session",
"Interaction",
"LangChainCallbackHandler",
"SessionBatCallbackHandler",
]
59 changes: 41 additions & 18 deletions src/sessionbat/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,44 @@ class Session:
tags: list[str] = field(default_factory=list)
context: dict[str, Any] = field(default_factory=dict)

def interaction(
self,
*,
interaction_id: str,
tags: list[str] | None = None,
context: dict[str, Any] | None = None,
) -> Interaction:
return Interaction(
session=self,
interaction_id=interaction_id,
tags=_merge_tags(self.tags, tags),
context=_merge_dicts(self.context, context),
)

def langchain_callback(
self,
*,
tags: list[str] | None = None,
context: dict[str, Any] | None = None,
metadata: dict[str, Any] | None = None,
) -> Any:
from .langchain import LangChainCallbackHandler

return LangChainCallbackHandler(
self,
tags=tags,
context=context,
metadata=metadata,
)


@dataclass(slots=True)
class Interaction:
session: Session
interaction_id: str
tags: list[str] = field(default_factory=list)
context: dict[str, Any] = field(default_factory=dict)

def message(
self,
*,
Expand Down Expand Up @@ -213,22 +251,6 @@ def retrieval(
context=context,
)

def langchain_callback(
self,
*,
tags: list[str] | None = None,
context: dict[str, Any] | None = None,
metadata: dict[str, Any] | None = None,
) -> Any:
from .langchain import LangChainCallbackHandler

return LangChainCallbackHandler(
self,
tags=tags,
context=context,
metadata=metadata,
)

def _record(
self,
*,
Expand All @@ -252,7 +274,8 @@ def _record(
payload = envelope.as_dict()
payload.update(
{
"session_id": self.session_id,
"session_id": self.session.session_id,
"interaction_id": self.interaction_id,
"observation": {
"kind": kind,
"name": name,
Expand All @@ -265,5 +288,5 @@ def _record(
},
}
)
self.client._send(payload)
self.session.client._send(payload)
return observation_id
Loading
Loading