backloop-sdk 0.1.0__tar.gz → 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.
- {backloop_sdk-0.1.0 → backloop_sdk-0.1.2}/PKG-INFO +4 -4
- {backloop_sdk-0.1.0 → backloop_sdk-0.1.2}/README.md +1 -1
- {backloop_sdk-0.1.0 → backloop_sdk-0.1.2}/pyproject.toml +3 -3
- {backloop_sdk-0.1.0 → backloop_sdk-0.1.2}/src/backloop/__init__.py +1 -1
- {backloop_sdk-0.1.0 → backloop_sdk-0.1.2}/src/backloop/server.py +8 -0
- {backloop_sdk-0.1.0 → backloop_sdk-0.1.2}/tests/test_server.py +13 -0
- {backloop_sdk-0.1.0 → backloop_sdk-0.1.2}/.gitignore +0 -0
- {backloop_sdk-0.1.0 → backloop_sdk-0.1.2}/LICENSE +0 -0
- {backloop_sdk-0.1.0 → backloop_sdk-0.1.2}/src/backloop/_generated.py +0 -0
- {backloop_sdk-0.1.0 → backloop_sdk-0.1.2}/src/backloop/_http.py +0 -0
- {backloop_sdk-0.1.0 → backloop_sdk-0.1.2}/src/backloop/client.py +0 -0
- {backloop_sdk-0.1.0 → backloop_sdk-0.1.2}/src/backloop/forward.py +0 -0
- {backloop_sdk-0.1.0 → backloop_sdk-0.1.2}/src/backloop/ids.py +0 -0
- {backloop_sdk-0.1.0 → backloop_sdk-0.1.2}/src/backloop/integrations.py +0 -0
- {backloop_sdk-0.1.0 → backloop_sdk-0.1.2}/src/backloop/py.typed +0 -0
- {backloop_sdk-0.1.0 → backloop_sdk-0.1.2}/src/backloop/redact.py +0 -0
- {backloop_sdk-0.1.0 → backloop_sdk-0.1.2}/src/backloop/tool.py +0 -0
- {backloop_sdk-0.1.0 → backloop_sdk-0.1.2}/src/backloop/types.py +0 -0
- {backloop_sdk-0.1.0 → backloop_sdk-0.1.2}/src/backloop/validate.py +0 -0
- {backloop_sdk-0.1.0 → backloop_sdk-0.1.2}/tests/__init__.py +0 -0
- {backloop_sdk-0.1.0 → backloop_sdk-0.1.2}/tests/helpers.py +0 -0
- {backloop_sdk-0.1.0 → backloop_sdk-0.1.2}/tests/test_client.py +0 -0
- {backloop_sdk-0.1.0 → backloop_sdk-0.1.2}/tests/test_forward.py +0 -0
- {backloop_sdk-0.1.0 → backloop_sdk-0.1.2}/tests/test_ids.py +0 -0
- {backloop_sdk-0.1.0 → backloop_sdk-0.1.2}/tests/test_integrations.py +0 -0
- {backloop_sdk-0.1.0 → backloop_sdk-0.1.2}/tests/test_redact.py +0 -0
- {backloop_sdk-0.1.0 → backloop_sdk-0.1.2}/tests/test_tool.py +0 -0
- {backloop_sdk-0.1.0 → backloop_sdk-0.1.2}/tests/test_validate.py +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: backloop-sdk
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: Python SDK for the Agent Feedback Protocol: validate, send and receive structured feedback from AI agents.
|
|
5
5
|
Project-URL: Homepage, https://trybackloop.com
|
|
6
6
|
Project-URL: Specification, https://trybackloop.com/spec/
|
|
7
|
-
Project-URL: Repository, https://github.com/
|
|
8
|
-
Project-URL: Issues, https://github.com/
|
|
7
|
+
Project-URL: Repository, https://github.com/backloophq/agent-feedback-protocol
|
|
8
|
+
Project-URL: Issues, https://github.com/backloophq/agent-feedback-protocol/issues
|
|
9
9
|
License-Expression: Apache-2.0
|
|
10
10
|
License-File: LICENSE
|
|
11
11
|
Keywords: agent-feedback-protocol,ai-agents,api,feedback,llm,mcp
|
|
@@ -27,7 +27,7 @@ Description-Content-Type: text/markdown
|
|
|
27
27
|
Python SDK for the [Agent Feedback Protocol](https://trybackloop.com/spec/): AI agents `POST` structured feedback to `/feedback` on the services they use, so those services learn what agents were trying to do when they got stuck.
|
|
28
28
|
|
|
29
29
|
- Standard library only (Python 3.9+). Optional FastAPI and Flask helpers.
|
|
30
|
-
- Validates against the protocol's [JSON Schemas](https://github.com/
|
|
30
|
+
- Validates against the protocol's [JSON Schemas](https://github.com/backloophq/agent-feedback-protocol/tree/main/spec).
|
|
31
31
|
- Mirrors the TypeScript SDK (`@backloop/sdk`): same validation messages, error codes and redaction.
|
|
32
32
|
|
|
33
33
|
```sh
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Python SDK for the [Agent Feedback Protocol](https://trybackloop.com/spec/): AI agents `POST` structured feedback to `/feedback` on the services they use, so those services learn what agents were trying to do when they got stuck.
|
|
4
4
|
|
|
5
5
|
- Standard library only (Python 3.9+). Optional FastAPI and Flask helpers.
|
|
6
|
-
- Validates against the protocol's [JSON Schemas](https://github.com/
|
|
6
|
+
- Validates against the protocol's [JSON Schemas](https://github.com/backloophq/agent-feedback-protocol/tree/main/spec).
|
|
7
7
|
- Mirrors the TypeScript SDK (`@backloop/sdk`): same validation messages, error codes and redaction.
|
|
8
8
|
|
|
9
9
|
```sh
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "backloop-sdk"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.2"
|
|
8
8
|
description = "Python SDK for the Agent Feedback Protocol: validate, send and receive structured feedback from AI agents."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "Apache-2.0"
|
|
@@ -23,8 +23,8 @@ classifiers = [
|
|
|
23
23
|
[project.urls]
|
|
24
24
|
Homepage = "https://trybackloop.com"
|
|
25
25
|
Specification = "https://trybackloop.com/spec/"
|
|
26
|
-
Repository = "https://github.com/
|
|
27
|
-
Issues = "https://github.com/
|
|
26
|
+
Repository = "https://github.com/backloophq/agent-feedback-protocol"
|
|
27
|
+
Issues = "https://github.com/backloophq/agent-feedback-protocol/issues"
|
|
28
28
|
|
|
29
29
|
[project.optional-dependencies]
|
|
30
30
|
fastapi = ["fastapi>=0.100"]
|
|
@@ -12,6 +12,7 @@ from datetime import datetime, timezone
|
|
|
12
12
|
from typing import Any, Callable, Dict, List, Mapping, Optional, Tuple, Union
|
|
13
13
|
|
|
14
14
|
from .ids import new_id
|
|
15
|
+
from .redact import redact as _redact
|
|
15
16
|
from .types import (
|
|
16
17
|
DEFAULT_MAX_BYTES,
|
|
17
18
|
FEEDBACK_TYPES,
|
|
@@ -93,6 +94,9 @@ class FeedbackHandler:
|
|
|
93
94
|
account behind a framework request; used by the integrations.
|
|
94
95
|
- ``rate_limit`` is ``(max, window_seconds)`` per account (or per IP), or
|
|
95
96
|
None to disable it.
|
|
97
|
+
- ``redact`` removes secrets, card numbers and email addresses from what
|
|
98
|
+
agents send, before ``on_record`` sees it. True (default), False, or a
|
|
99
|
+
dict of ``redact()`` options. Not every agent uses a client that does.
|
|
96
100
|
"""
|
|
97
101
|
|
|
98
102
|
def __init__(
|
|
@@ -106,6 +110,7 @@ class FeedbackHandler:
|
|
|
106
110
|
public_endpoint: Optional[str] = None,
|
|
107
111
|
rate_limit: Optional[Tuple[int, float]] = (60, 60.0),
|
|
108
112
|
source: FeedbackSource = "http",
|
|
113
|
+
redact: Union[bool, Mapping[str, Any]] = True,
|
|
109
114
|
generate_id: Optional[Callable[[], str]] = None,
|
|
110
115
|
now: Optional[Callable[[], datetime]] = None,
|
|
111
116
|
) -> None:
|
|
@@ -118,6 +123,7 @@ class FeedbackHandler:
|
|
|
118
123
|
self.max_bytes = max_bytes
|
|
119
124
|
self.public_endpoint = public_endpoint
|
|
120
125
|
self.source = source
|
|
126
|
+
self.redact = redact
|
|
121
127
|
self._limiter = _RateLimiter(*rate_limit) if rate_limit else None
|
|
122
128
|
self._generate_id = generate_id or (lambda: new_id("fb"))
|
|
123
129
|
self._now = now or (lambda: datetime.now(timezone.utc))
|
|
@@ -163,6 +169,8 @@ class FeedbackHandler:
|
|
|
163
169
|
if account:
|
|
164
170
|
record["account"] = account
|
|
165
171
|
record["source"] = self.source or "http"
|
|
172
|
+
if self.redact is not False:
|
|
173
|
+
parsed = _redact(parsed, **(self.redact if isinstance(self.redact, Mapping) else {}))
|
|
166
174
|
record["feedback"] = parsed
|
|
167
175
|
|
|
168
176
|
try:
|
|
@@ -51,6 +51,19 @@ class HappyPathTest(unittest.TestCase):
|
|
|
51
51
|
[{"id": "fb_test", "received_at": "2026-09-28T10:04:11.123Z", "source": "http", "feedback": SUBMISSION}],
|
|
52
52
|
)
|
|
53
53
|
|
|
54
|
+
def test_removes_secrets_and_emails_whatever_client_sent_them(self):
|
|
55
|
+
leaky = dict(SUBMISSION, message="401 with Bearer 9f8e7d6c5b4a3f2e for jane@acme.test", metadata={"password": "hunter2", "plan": "team"})
|
|
56
|
+
handler, sink = make()
|
|
57
|
+
status, _, _ = handler.handle_submit(json.dumps(leaky).encode(), JSON)
|
|
58
|
+
self.assertEqual(status, 202)
|
|
59
|
+
feedback = sink.records[0]["feedback"]
|
|
60
|
+
self.assertEqual(feedback["message"], "401 with [REDACTED] for [REDACTED]")
|
|
61
|
+
self.assertEqual(feedback["metadata"], {"password": "[REDACTED]", "plan": "team"})
|
|
62
|
+
# Asked not to: as sent.
|
|
63
|
+
handler, sink = make(redact=False)
|
|
64
|
+
handler.handle_submit(json.dumps(leaky).encode(), JSON)
|
|
65
|
+
self.assertEqual(sink.records[0]["feedback"]["message"], leaky["message"])
|
|
66
|
+
|
|
54
67
|
def test_record_carries_service_account_and_source(self):
|
|
55
68
|
handler, sink = make(service="acme-api", source="mcp")
|
|
56
69
|
handler.handle_submit(BODY, JSON, "acct_abc")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|