Metadata-Version: 2.4
Name: chatsee-ai
Version: 0.12.1
Summary: A Python SDK for Chatsee AI.
Maintainer: ChatSee
Maintainer-email: contact@chatsee.ai
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Requires-Python: >=3.7
Description-Content-Type: text/markdown
Requires-Dist: requests>=2.31.0
Provides-Extra: closed-loop
Requires-Dist: mcp>=1.2.0; extra == "closed-loop"
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: maintainer
Dynamic: maintainer-email
Dynamic: provides-extra
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

# Chatsee SDK (Python)

Instrument an LLM agent so every turn arrives at ChatSee as a trace: what the
user asked, what the agent answered, which tools ran, how long each one took,
what failed, and who it was for.

```bash
pip install chatsee-ai
```

## Quick start

```python
from chatsee import ChatseeTracker

tracker = ChatseeTracker(
    agent_id="agent_1",
    tenant_id="tenant_1",
    api_base_url="dev",     # environment alias, or a full URL
    user_id="u_123",        # optional: who the end user is
)

tracker.start_turn("Where is my order?")

with tracker.track_tool_call("lookup_order", {"id": "A-91"}) as call:
    call["result"] = lookup_order("A-91")

tracker.end_turn("It ships tomorrow.")
```

One `start_turn` … `end_turn` pair is one turn. Everything logged in between —
tool calls, exceptions, metadata — belongs to that turn and is sent in a single
request when it closes.

## What gets captured

| Captured | How |
|----------|-----|
| User and bot message | `start_turn` / `end_turn` |
| End user identity | `user_id` (see below) |
| Session grouping | `session_id`, or the API generates one |
| Tool calls: name, arguments, result, error | `track_tool_call` / `log_tool_call` |
| **Per-tool duration** | measured automatically by `track_tool_call` |
| **Turn duration** | measured between `start_turn` and `end_turn` |
| Errors | `log_exception`, or any exception raised inside `track_tool_call` |
| System prompt | `start_turn(system_prompt=...)` |
| Anything else | `start_turn(metadata={...})` |

Durations are milliseconds of wall clock. When a duration was not measured it is
sent as absent, never as `0` — downstream latency figures are built from real
measurements only, so an unmeasured call is excluded rather than counted as
instant.

## Timing tool calls

`track_tool_call` is the recommended form: it times the call, records the
result, and still logs the call if the tool raises before re-raising to you.

```python
with tracker.track_tool_call("search", {"q": q}) as call:
    call["result"] = search(q)          # exceptions here are logged, then re-raised
```

If you already have the timing, or the call happened elsewhere, log it directly:

```python
tracker.log_tool_call("search", {"q": q}, result=hits, duration_ms=412)
tracker.log_tool_call("refund", {"id": "A-91"}, error="gateway timeout")
```

Both feed the per-tool metrics the ChatSee front end reads — call counts, error
rate, and p50/p95 latency per tool.

## Identifying the end user

`user_id` is a first-class field, so traces can be filtered and grouped by
person rather than by conversation. It is optional — when nothing is supplied,
ingestion assigns a deterministic anonymous id per session — but a real id is
what lets you follow one user across sessions.

```python
tracker = ChatseeTracker(..., user_id="u_123")     # one tracker, one user
tracker.start_turn("Hello", user_id="u_456")       # one tracker, many users
```

Precedence: `start_turn(user_id=...)`, then the tracker-level `user_id`, then
`metadata["user_id"]` — the older metadata form keeps working as a fallback.

## Environments

Select an environment by passing an alias as `api_base_url`, or any full URL.

| Alias | URL |
|-------|-----|
| `dev` | `https://dev-react.chatsee.ai/api` |
| `dev-legacy` | `https://dev.chatsee.ai/api` |
| `qa` (default) | `https://qa.chatsee.ai/api` |
| `demo` | `https://gcp-demo.chatsee.ai/api` |
| `poc` | `https://app.chatsee.ai/api` |

Redaction classifiers are fetched from the same environment
(`…/v1/redaction/fetch-classifiers`); override with `redaction_classifiers_url`.

## Closing a conversation early

Conversations are normally flushed by an inactivity timer. If you know a turn is
the last one, say so and it flushes immediately:

```python
tracker.end_turn("Glad I could help.", is_final_turn=True)
```

## Batching

Send many turns as one API call — one Processor run instead of one per turn.
Useful for backfills and imports.

```python
tracker.send_batch([
    {"user_message": "hi", "bot_message": "hello", "session_id": "s1"},
    {"user_message": "thanks", "bot_message": "any time", "session_id": "s1"},
])
```

## Redaction

Redaction runs client-side, before anything leaves the process. Classifiers come
from your environment and are cached.

```python
tracker = ChatseeTracker(..., redaction_enabled=True)   # redacts each turn
```

Or redact a payload on its own, without a tracker:

```python
from chatsee import redact

redact({"message": "Card 4111 1111 1111 1111"}, api_base_url="qa", fields_to_redact="*")
```

Defaults to `user_message`, `bot_message` and `interactions`; set
`redaction_fields_to_redact` (or `fields_to_redact="*"`) to change that.

## Closed-loop remediations

Pull remediation skills for this agent and acknowledge the ones you have
injected into its system prompt. Requires `mcp_server_url` and `mcp_api_key`;
tenant and agent are resolved server-side from the key.

```python
tracker = ChatseeTracker(..., mcp_server_url="https://…", mcp_api_key="…")

pending = tracker.fetch_remediations()                 # or mode="all"
tracker.acknowledge_remediations([r["id"] for r in pending["remediations"]])
```

## Track Claude Code (adapter)

Claude Code is a closed CLI, so rather than instrumenting it the adapter reads
its session logs, reconstructs each turn and sends it through the SDK.

```bash
# preview (sends nothing)
python -m chatsee.adapters.claude_code --latest --dry-run

# one-time hook install -> every completed turn auto-streams, no terminal needed
python -m chatsee.adapters.claude_code --install-hook \
  --agent-id <AGENT_ID> --tenant-id <TENANT_ID> --env dev

# remove it
python -m chatsee.adapters.claude_code --uninstall-hook
```

Alternatives to the hook: `--watch` (foreground tail) or a one-shot run. A
per-session checkpoint (`~/.chatsee/claude_code_state.json`) prevents duplicates.

**Redaction is on by default here.** Claude Code turns — tool arguments and
results especially — often contain secrets and PII, so the adapter redacts
client-side before sending (`user_message`, `bot_message`,
`tool_calls_details`, `exception`, `system_prompt`). Change the set with
`--redact-fields a,b,c`, or disable with `--no-redact` (not recommended). The
setting is preserved in installed hooks.

## Notes

- `agent_id` and `tenant_id` are required; there is no API key on the tracking path.
- `verify_ssl` defaults to `False` for the internal environments; set `True` for public ones.
- Encryption support has been removed from this SDK.
