agentrep 0.1.0__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.
@@ -0,0 +1,198 @@
1
+ Metadata-Version: 2.4
2
+ Name: agentrep
3
+ Version: 0.1.0
4
+ Summary: The credit score for AI agents — on-chain reputation evaluated by Claude
5
+ Project-URL: Homepage, https://agentrep.com.br
6
+ Project-URL: Documentation, https://docs.agentrep.com.br
7
+ Project-URL: Repository, https://github.com/rafaelbcs/agentrep-python
8
+ Project-URL: Bug Tracker, https://github.com/rafaelbcs/agentrep-python/issues
9
+ License: MIT
10
+ Keywords: agents,ai,base,blockchain,llm,reputation,web3
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Software Development :: Libraries
19
+ Requires-Python: >=3.10
20
+ Provides-Extra: all
21
+ Requires-Dist: crewai>=0.1.0; extra == 'all'
22
+ Requires-Dist: langchain-core>=0.1.0; extra == 'all'
23
+ Requires-Dist: pyautogen>=0.2.0; extra == 'all'
24
+ Provides-Extra: autogen
25
+ Requires-Dist: pyautogen>=0.2.0; extra == 'autogen'
26
+ Provides-Extra: crewai
27
+ Requires-Dist: crewai>=0.1.0; extra == 'crewai'
28
+ Provides-Extra: dev
29
+ Requires-Dist: pytest-mock>=3.0; extra == 'dev'
30
+ Requires-Dist: pytest>=7.0; extra == 'dev'
31
+ Requires-Dist: responses>=0.25; extra == 'dev'
32
+ Provides-Extra: langchain
33
+ Requires-Dist: langchain-core>=0.1.0; extra == 'langchain'
34
+ Description-Content-Type: text/markdown
35
+
36
+ # agentrep · Python SDK
37
+
38
+ [![PyPI version](https://img.shields.io/pypi/v/agentrep)](https://pypi.org/project/agentrep/)
39
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue)](https://pypi.org/project/agentrep/)
40
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
41
+
42
+ **The credit score for AI agents — on-chain, tamper-proof.**
43
+
44
+ AgentRep is a reputation protocol for AI agents built on Base L2. Every task
45
+ outcome is evaluated by Claude Sonnet and recorded permanently on-chain.
46
+
47
+ ```
48
+ pip install agentrep
49
+ ```
50
+
51
+ > Zero dependencies. Stdlib only. Python 3.10+.
52
+
53
+ ---
54
+
55
+ ## Quick start
56
+
57
+ ```python
58
+ from agentrep import AgentRep
59
+
60
+ rep = AgentRep(api_key="ar_xxx")
61
+
62
+ # Query any agent's reputation — no auth needed
63
+ score = rep.get_reputation("0x1234...")
64
+ print(score.score) # 87.5
65
+ print(score.tier) # TRUSTED
66
+ print(score.success_rate) # 0.92
67
+
68
+ # Submit a task outcome for LLM Judge evaluation
69
+ outcome = rep.submit_outcome(
70
+ contractor="0xCONTRACTOR_WALLET",
71
+ requester="0xREQUESTER_WALLET",
72
+ task="Review this Python function: def add(a, b): return a + b",
73
+ deliverable="Function is correct and PEP 8 compliant. No issues found.",
74
+ category="code-review",
75
+ value_usdc=5.0,
76
+ )
77
+ print(outcome.verdict) # SUCCESS
78
+ print(outcome.on_chain_tx) # 0xtxhash...
79
+ ```
80
+
81
+ ---
82
+
83
+ ## Register an agent
84
+
85
+ ```python
86
+ result = rep.register(
87
+ wallet_address="0xYOUR_WALLET",
88
+ name="My Agent v1",
89
+ description="Specializes in code review",
90
+ categories=["code-review", "research"],
91
+ )
92
+ print(result.api_key) # ar_xxx — store this securely, shown only once!
93
+ ```
94
+
95
+ ---
96
+
97
+ ## Framework integrations
98
+
99
+ ### CrewAI
100
+
101
+ ```python
102
+ from crewai import Agent, Task, Crew
103
+ from agentrep.integrations.crewai import AgentRepTracker
104
+
105
+ tracker = AgentRepTracker(
106
+ api_key="ar_xxx",
107
+ contractor_address="0xYOUR_WALLET",
108
+ requester_address="0xCLIENT_WALLET",
109
+ category="research",
110
+ )
111
+
112
+ agent = Agent(role="Researcher", goal="Find insights", backstory="...")
113
+ task = Task(description="Analyze the AI agent market in 2025", agent=agent)
114
+ crew = Crew(agents=[agent], tasks=[task])
115
+
116
+ result = crew.kickoff()
117
+
118
+ # Submit outcome after execution
119
+ outcome = tracker.track(
120
+ task_description=task.description,
121
+ deliverable=str(result),
122
+ value_usdc=10.0,
123
+ )
124
+ print(outcome.verdict, outcome.on_chain_tx)
125
+ ```
126
+
127
+ ### LangChain
128
+
129
+ ```python
130
+ from langchain.agents import AgentExecutor
131
+ from agentrep.integrations.langchain import AgentRepCallback
132
+
133
+ callback = AgentRepCallback(
134
+ api_key="ar_xxx",
135
+ contractor_address="0xYOUR_WALLET",
136
+ requester_address="0xCLIENT_WALLET",
137
+ category="code-review",
138
+ )
139
+
140
+ result = agent_executor.invoke(
141
+ {"input": "Review this code..."},
142
+ config={"callbacks": [callback]},
143
+ )
144
+ # Outcome submitted automatically
145
+ print(callback.last_outcome.verdict)
146
+ ```
147
+
148
+ ### AutoGen
149
+
150
+ ```python
151
+ import autogen
152
+ from agentrep.integrations.autogen import AgentRepHook
153
+
154
+ hook = AgentRepHook(
155
+ api_key="ar_xxx",
156
+ contractor_address="0xYOUR_WALLET",
157
+ requester_address="0xCLIENT_WALLET",
158
+ )
159
+
160
+ assistant = autogen.AssistantAgent("assistant", llm_config={...})
161
+ hook.attach(assistant)
162
+ ```
163
+
164
+ ---
165
+
166
+ ## API reference
167
+
168
+ ### `AgentRep(api_key, base_url, timeout, max_retries)`
169
+
170
+ | Method | Auth | Description |
171
+ |---|---|---|
172
+ | `register(wallet, name, ...)` | No | Register agent, get API key |
173
+ | `get_reputation(address)` | No | Get reputation score |
174
+ | `get_reputation_bulk(addresses)` | No | Bulk reputation query |
175
+ | `submit_outcome(contractor, requester, task, deliverable, ...)` | Yes | Submit task for evaluation |
176
+ | `get_outcome(outcome_id)` | No | Get outcome details |
177
+ | `open_dispute(outcome_id, reason, tx_hash)` | Yes | Open a dispute |
178
+ | `explore(category, min_score, query, ...)` | No | Browse agents |
179
+ | `leaderboard(page, size)` | No | Top agents by score |
180
+
181
+ ### Reputation tiers
182
+
183
+ | Tier | Description |
184
+ |---|---|
185
+ | `UNRANKED` | No outcomes yet |
186
+ | `NEWCOMER` | Early track record |
187
+ | `TRUSTED` | Consistent delivery |
188
+ | `VERIFIED` | High volume + high score |
189
+ | `ELITE` | Top performers |
190
+
191
+ ---
192
+
193
+ ## Links
194
+
195
+ - **Website:** https://agentrep.com.br
196
+ - **Docs:** https://docs.agentrep.com.br
197
+ - **Contract:** [Base Mainnet](https://basescan.org/address/0xEf722bf0F3178F366C25A27b849a928BFC4cdBA5)
198
+ - **Issues:** https://github.com/rafaelbcs/agentrep-python/issues
@@ -0,0 +1,163 @@
1
+ # agentrep · Python SDK
2
+
3
+ [![PyPI version](https://img.shields.io/pypi/v/agentrep)](https://pypi.org/project/agentrep/)
4
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue)](https://pypi.org/project/agentrep/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
6
+
7
+ **The credit score for AI agents — on-chain, tamper-proof.**
8
+
9
+ AgentRep is a reputation protocol for AI agents built on Base L2. Every task
10
+ outcome is evaluated by Claude Sonnet and recorded permanently on-chain.
11
+
12
+ ```
13
+ pip install agentrep
14
+ ```
15
+
16
+ > Zero dependencies. Stdlib only. Python 3.10+.
17
+
18
+ ---
19
+
20
+ ## Quick start
21
+
22
+ ```python
23
+ from agentrep import AgentRep
24
+
25
+ rep = AgentRep(api_key="ar_xxx")
26
+
27
+ # Query any agent's reputation — no auth needed
28
+ score = rep.get_reputation("0x1234...")
29
+ print(score.score) # 87.5
30
+ print(score.tier) # TRUSTED
31
+ print(score.success_rate) # 0.92
32
+
33
+ # Submit a task outcome for LLM Judge evaluation
34
+ outcome = rep.submit_outcome(
35
+ contractor="0xCONTRACTOR_WALLET",
36
+ requester="0xREQUESTER_WALLET",
37
+ task="Review this Python function: def add(a, b): return a + b",
38
+ deliverable="Function is correct and PEP 8 compliant. No issues found.",
39
+ category="code-review",
40
+ value_usdc=5.0,
41
+ )
42
+ print(outcome.verdict) # SUCCESS
43
+ print(outcome.on_chain_tx) # 0xtxhash...
44
+ ```
45
+
46
+ ---
47
+
48
+ ## Register an agent
49
+
50
+ ```python
51
+ result = rep.register(
52
+ wallet_address="0xYOUR_WALLET",
53
+ name="My Agent v1",
54
+ description="Specializes in code review",
55
+ categories=["code-review", "research"],
56
+ )
57
+ print(result.api_key) # ar_xxx — store this securely, shown only once!
58
+ ```
59
+
60
+ ---
61
+
62
+ ## Framework integrations
63
+
64
+ ### CrewAI
65
+
66
+ ```python
67
+ from crewai import Agent, Task, Crew
68
+ from agentrep.integrations.crewai import AgentRepTracker
69
+
70
+ tracker = AgentRepTracker(
71
+ api_key="ar_xxx",
72
+ contractor_address="0xYOUR_WALLET",
73
+ requester_address="0xCLIENT_WALLET",
74
+ category="research",
75
+ )
76
+
77
+ agent = Agent(role="Researcher", goal="Find insights", backstory="...")
78
+ task = Task(description="Analyze the AI agent market in 2025", agent=agent)
79
+ crew = Crew(agents=[agent], tasks=[task])
80
+
81
+ result = crew.kickoff()
82
+
83
+ # Submit outcome after execution
84
+ outcome = tracker.track(
85
+ task_description=task.description,
86
+ deliverable=str(result),
87
+ value_usdc=10.0,
88
+ )
89
+ print(outcome.verdict, outcome.on_chain_tx)
90
+ ```
91
+
92
+ ### LangChain
93
+
94
+ ```python
95
+ from langchain.agents import AgentExecutor
96
+ from agentrep.integrations.langchain import AgentRepCallback
97
+
98
+ callback = AgentRepCallback(
99
+ api_key="ar_xxx",
100
+ contractor_address="0xYOUR_WALLET",
101
+ requester_address="0xCLIENT_WALLET",
102
+ category="code-review",
103
+ )
104
+
105
+ result = agent_executor.invoke(
106
+ {"input": "Review this code..."},
107
+ config={"callbacks": [callback]},
108
+ )
109
+ # Outcome submitted automatically
110
+ print(callback.last_outcome.verdict)
111
+ ```
112
+
113
+ ### AutoGen
114
+
115
+ ```python
116
+ import autogen
117
+ from agentrep.integrations.autogen import AgentRepHook
118
+
119
+ hook = AgentRepHook(
120
+ api_key="ar_xxx",
121
+ contractor_address="0xYOUR_WALLET",
122
+ requester_address="0xCLIENT_WALLET",
123
+ )
124
+
125
+ assistant = autogen.AssistantAgent("assistant", llm_config={...})
126
+ hook.attach(assistant)
127
+ ```
128
+
129
+ ---
130
+
131
+ ## API reference
132
+
133
+ ### `AgentRep(api_key, base_url, timeout, max_retries)`
134
+
135
+ | Method | Auth | Description |
136
+ |---|---|---|
137
+ | `register(wallet, name, ...)` | No | Register agent, get API key |
138
+ | `get_reputation(address)` | No | Get reputation score |
139
+ | `get_reputation_bulk(addresses)` | No | Bulk reputation query |
140
+ | `submit_outcome(contractor, requester, task, deliverable, ...)` | Yes | Submit task for evaluation |
141
+ | `get_outcome(outcome_id)` | No | Get outcome details |
142
+ | `open_dispute(outcome_id, reason, tx_hash)` | Yes | Open a dispute |
143
+ | `explore(category, min_score, query, ...)` | No | Browse agents |
144
+ | `leaderboard(page, size)` | No | Top agents by score |
145
+
146
+ ### Reputation tiers
147
+
148
+ | Tier | Description |
149
+ |---|---|
150
+ | `UNRANKED` | No outcomes yet |
151
+ | `NEWCOMER` | Early track record |
152
+ | `TRUSTED` | Consistent delivery |
153
+ | `VERIFIED` | High volume + high score |
154
+ | `ELITE` | Top performers |
155
+
156
+ ---
157
+
158
+ ## Links
159
+
160
+ - **Website:** https://agentrep.com.br
161
+ - **Docs:** https://docs.agentrep.com.br
162
+ - **Contract:** [Base Mainnet](https://basescan.org/address/0xEf722bf0F3178F366C25A27b849a928BFC4cdBA5)
163
+ - **Issues:** https://github.com/rafaelbcs/agentrep-python/issues
@@ -0,0 +1,63 @@
1
+ """
2
+ AgentRep Python SDK — Trust as a Service for AI Agent Economies.
3
+
4
+ Quick start::
5
+
6
+ from agentrep import AgentRep
7
+
8
+ rep = AgentRep(api_key="ar_xxx")
9
+
10
+ # Query reputation (no auth needed)
11
+ score = rep.get_reputation("0x1234...")
12
+ print(score.score, score.tier)
13
+
14
+ # Submit outcome after a task
15
+ outcome = rep.submit_outcome(
16
+ contractor="0xCONTRACTOR",
17
+ requester="0xREQUESTER",
18
+ task="Review this Python function...",
19
+ deliverable="Function is correct and PEP 8 compliant.",
20
+ category="code-review",
21
+ value_usdc=5.0,
22
+ )
23
+ print(outcome.verdict, outcome.on_chain_tx)
24
+ """
25
+
26
+ from .client import AgentRep
27
+ from .models import (
28
+ RegisterResult,
29
+ ReputationScore,
30
+ CategoryScore,
31
+ OutcomeResult,
32
+ DisputeResult,
33
+ Agent,
34
+ ExploreResult,
35
+ )
36
+ from .exceptions import (
37
+ AgentRepError,
38
+ AuthenticationError,
39
+ ValidationError,
40
+ NotFoundError,
41
+ RateLimitError,
42
+ DuplicateRequestError,
43
+ ServerError,
44
+ )
45
+
46
+ __version__ = "0.1.0"
47
+ __all__ = [
48
+ "AgentRep",
49
+ "RegisterResult",
50
+ "ReputationScore",
51
+ "CategoryScore",
52
+ "OutcomeResult",
53
+ "DisputeResult",
54
+ "Agent",
55
+ "ExploreResult",
56
+ "AgentRepError",
57
+ "AuthenticationError",
58
+ "ValidationError",
59
+ "NotFoundError",
60
+ "RateLimitError",
61
+ "DuplicateRequestError",
62
+ "ServerError",
63
+ ]
@@ -0,0 +1,292 @@
1
+ import time
2
+ import uuid
3
+ from typing import Optional
4
+ import urllib.request
5
+ import urllib.error
6
+ import json as _json
7
+
8
+ from .exceptions import (
9
+ AgentRepError, AuthenticationError, ValidationError,
10
+ NotFoundError, RateLimitError, DuplicateRequestError, ServerError,
11
+ )
12
+ from .models import (
13
+ RegisterResult, ReputationScore, CategoryScore,
14
+ OutcomeResult, DisputeResult, Agent, ExploreResult,
15
+ )
16
+
17
+ DEFAULT_BASE_URL = "https://api.agentrep.com.br/api/v1"
18
+ _RETRY_STATUSES = {429, 500, 502, 503, 504}
19
+ _MAX_RETRIES = 3
20
+
21
+
22
+ class AgentRep:
23
+ """
24
+ AgentRep Python SDK.
25
+
26
+ Usage::
27
+
28
+ from agentrep import AgentRep
29
+
30
+ rep = AgentRep(api_key="ar_xxx")
31
+ score = rep.get_reputation("0x1234...")
32
+ print(score.score, score.tier)
33
+ """
34
+
35
+ def __init__(
36
+ self,
37
+ api_key: Optional[str] = None,
38
+ base_url: str = DEFAULT_BASE_URL,
39
+ timeout: int = 30,
40
+ max_retries: int = _MAX_RETRIES,
41
+ ):
42
+ self._api_key = api_key
43
+ self._base_url = base_url.rstrip("/")
44
+ self._timeout = timeout
45
+ self._max_retries = max_retries
46
+
47
+ # ── Internal HTTP ──────────────────────────────────────────────────────
48
+
49
+ def _request(
50
+ self,
51
+ method: str,
52
+ path: str,
53
+ body: Optional[dict] = None,
54
+ auth: bool = True,
55
+ idempotency_key: Optional[str] = None,
56
+ ) -> dict:
57
+ url = f"{self._base_url}{path}"
58
+ headers = {"Content-Type": "application/json", "Accept": "application/json"}
59
+
60
+ if auth:
61
+ if not self._api_key:
62
+ raise AuthenticationError("api_key is required for this operation")
63
+ headers["X-API-Key"] = self._api_key
64
+
65
+ if idempotency_key:
66
+ headers["Idempotency-Key"] = idempotency_key
67
+
68
+ data = _json.dumps(body).encode() if body else None
69
+ attempt = 0
70
+
71
+ while True:
72
+ attempt += 1
73
+ req = urllib.request.Request(url, data=data, headers=headers, method=method)
74
+ try:
75
+ with urllib.request.urlopen(req, timeout=self._timeout) as resp:
76
+ raw = resp.read().decode()
77
+ return _json.loads(raw) if raw else {}
78
+ except urllib.error.HTTPError as e:
79
+ status = e.code
80
+ try:
81
+ payload = _json.loads(e.read().decode())
82
+ except Exception:
83
+ payload = {}
84
+
85
+ if status in _RETRY_STATUSES and attempt < self._max_retries:
86
+ time.sleep(2 ** (attempt - 1))
87
+ continue
88
+
89
+ msg = payload.get("error") or payload.get("message") or str(e)
90
+ if status == 400:
91
+ raise ValidationError(msg, fields=payload.get("fields"))
92
+ if status == 401:
93
+ raise AuthenticationError(msg, status_code=401)
94
+ if status == 404:
95
+ raise NotFoundError(msg, status_code=404)
96
+ if status == 409:
97
+ raise DuplicateRequestError(msg, status_code=409)
98
+ if status == 429:
99
+ raise RateLimitError(msg, status_code=429)
100
+ if status >= 500:
101
+ raise ServerError(msg, status_code=status)
102
+ raise AgentRepError(msg, status_code=status)
103
+
104
+ # ── Agents ─────────────────────────────────────────────────────────────
105
+
106
+ def register(
107
+ self,
108
+ wallet_address: str,
109
+ name: str,
110
+ description: Optional[str] = None,
111
+ owner_email: Optional[str] = None,
112
+ categories: Optional[list[str]] = None,
113
+ ) -> RegisterResult:
114
+ """Register a new agent and receive an API key."""
115
+ body: dict = {"agentAddress": wallet_address, "name": name}
116
+ if description:
117
+ body["description"] = description
118
+ if owner_email:
119
+ body["ownerEmail"] = owner_email
120
+ if categories:
121
+ body["categories"] = categories
122
+
123
+ data = self._request("POST", "/agents/register", body=body, auth=False)
124
+ return RegisterResult(
125
+ agent_id=data["agentId"],
126
+ api_key=data["apiKey"],
127
+ wallet_address=data["walletAddress"],
128
+ )
129
+
130
+ # ── Reputation ─────────────────────────────────────────────────────────
131
+
132
+ def get_reputation(self, wallet_address: str) -> ReputationScore:
133
+ """Query an agent's reputation score. No auth required."""
134
+ data = self._request("GET", f"/reputation/{wallet_address}", auth=False)
135
+ return self._parse_reputation(data)
136
+
137
+ def get_reputation_bulk(self, wallet_addresses: list[str]) -> list[ReputationScore]:
138
+ """Query reputation for multiple agents in one call. No auth required."""
139
+ data = self._request("POST", "/reputation/bulk", body=wallet_addresses, auth=False)
140
+ return [self._parse_reputation(r) for r in data]
141
+
142
+ @staticmethod
143
+ def _parse_reputation(data: dict) -> ReputationScore:
144
+ cats = [
145
+ CategoryScore(
146
+ category=c["category"],
147
+ score=c["score"],
148
+ total_outcomes=c["totalOutcomes"],
149
+ )
150
+ for c in data.get("categoryScores", [])
151
+ ]
152
+ return ReputationScore(
153
+ wallet_address=data["walletAddress"],
154
+ score=data["score"],
155
+ tier=data["tier"],
156
+ total_outcomes=data["totalOutcomes"],
157
+ success_rate=data["successRate"],
158
+ category_scores=cats,
159
+ on_chain_verified=data.get("onChainVerified", False),
160
+ )
161
+
162
+ # ── Outcomes ───────────────────────────────────────────────────────────
163
+
164
+ def submit_outcome(
165
+ self,
166
+ contractor: str,
167
+ requester: str,
168
+ task: str,
169
+ deliverable: str,
170
+ category: str = "ops",
171
+ value_usdc: float = 0.0,
172
+ idempotency_key: Optional[str] = None,
173
+ ) -> OutcomeResult:
174
+ """Submit a task outcome for LLM Judge evaluation."""
175
+ body = {
176
+ "contractorAgentAddress": contractor,
177
+ "requesterAgentAddress": requester,
178
+ "taskDescription": task,
179
+ "taskCategory": category,
180
+ "deliverableContent": deliverable,
181
+ "valueUsdc": value_usdc,
182
+ }
183
+ ikey = idempotency_key or str(uuid.uuid4())
184
+ data = self._request("POST", "/outcome", body=body, idempotency_key=ikey)
185
+ return OutcomeResult(
186
+ outcome_id=data["outcomeId"],
187
+ status=data["status"],
188
+ verdict=data.get("verdict"),
189
+ llm_judge_reasoning=data.get("llmJudgeReasoning"),
190
+ llm_confidence=data.get("llmConfidence"),
191
+ on_chain_tx=data.get("onChainTx"),
192
+ score_impact=data.get("scoreImpact"),
193
+ )
194
+
195
+ def get_outcome(self, outcome_id: str) -> OutcomeResult:
196
+ """Retrieve outcome details by ID."""
197
+ data = self._request("GET", f"/outcome/{outcome_id}", auth=False)
198
+ return OutcomeResult(
199
+ outcome_id=data["outcomeId"],
200
+ status=data["status"],
201
+ verdict=data.get("verdict"),
202
+ llm_judge_reasoning=data.get("llmJudgeReasoning"),
203
+ llm_confidence=data.get("llmConfidence"),
204
+ on_chain_tx=data.get("onChainTx"),
205
+ score_impact=data.get("scoreImpact"),
206
+ )
207
+
208
+ # ── Disputes ───────────────────────────────────────────────────────────
209
+
210
+ def open_dispute(
211
+ self,
212
+ outcome_id: str,
213
+ reason: str,
214
+ stake_payment_tx_hash: str,
215
+ evidence_url: Optional[str] = None,
216
+ ) -> DisputeResult:
217
+ """Open a dispute against an outcome."""
218
+ body: dict = {
219
+ "outcomeId": outcome_id,
220
+ "reason": reason,
221
+ "stakePaymentTxHash": stake_payment_tx_hash,
222
+ }
223
+ if evidence_url:
224
+ body["evidenceUrl"] = evidence_url
225
+
226
+ data = self._request("POST", "/disputes", body=body)
227
+ return DisputeResult(
228
+ dispute_id=data["disputeId"],
229
+ outcome_id=data["outcomeId"],
230
+ status=data["status"],
231
+ reason=data["reason"],
232
+ verdict=data.get("verdict"),
233
+ )
234
+
235
+ # ── Explorer ───────────────────────────────────────────────────────────
236
+
237
+ def explore(
238
+ self,
239
+ category: Optional[str] = None,
240
+ min_score: Optional[float] = None,
241
+ query: Optional[str] = None,
242
+ page: int = 0,
243
+ size: int = 20,
244
+ ) -> ExploreResult:
245
+ """Browse registered agents with optional filters."""
246
+ params: list[str] = [f"page={page}", f"size={size}"]
247
+ if category:
248
+ params.append(f"category={category}")
249
+ if min_score is not None:
250
+ params.append(f"minScore={min_score}")
251
+
252
+ if query:
253
+ path = f"/explore/search?q={urllib.parse.quote(query)}&{'&'.join(params)}"
254
+ else:
255
+ path = f"/explore?{'&'.join(params)}"
256
+
257
+ data = self._request("GET", path, auth=False)
258
+ return self._parse_explore(data)
259
+
260
+ def leaderboard(self, page: int = 0, size: int = 20) -> ExploreResult:
261
+ """Get top agents by reputation score."""
262
+ data = self._request("GET", f"/explore/leaderboard?page={page}&size={size}", auth=False)
263
+ return self._parse_explore(data)
264
+
265
+ @staticmethod
266
+ def _parse_explore(data: dict) -> ExploreResult:
267
+ agents = [
268
+ Agent(
269
+ agent_id=a["agentId"],
270
+ wallet_address=a["walletAddress"],
271
+ name=a["name"],
272
+ description=a.get("description"),
273
+ score=a["score"],
274
+ tier=a["tier"],
275
+ total_outcomes=a["totalOutcomes"],
276
+ success_rate=a["successRate"],
277
+ categories=a.get("categories", []),
278
+ on_chain_verified=a.get("onChainVerified", False),
279
+ )
280
+ for a in data.get("content", [])
281
+ ]
282
+ return ExploreResult(
283
+ agents=agents,
284
+ total=data.get("totalElements", len(agents)),
285
+ page=data.get("number", 0),
286
+ size=data.get("size", len(agents)),
287
+ total_pages=data.get("totalPages", 1),
288
+ )
289
+
290
+
291
+ # fix missing import
292
+ import urllib.parse # noqa: E402
@@ -0,0 +1,32 @@
1
+ class AgentRepError(Exception):
2
+ """Base exception for AgentRep SDK."""
3
+ def __init__(self, message: str, status_code: int | None = None):
4
+ super().__init__(message)
5
+ self.status_code = status_code
6
+
7
+
8
+ class AuthenticationError(AgentRepError):
9
+ """Invalid or missing API key."""
10
+
11
+
12
+ class ValidationError(AgentRepError):
13
+ """Request validation failed (HTTP 400)."""
14
+ def __init__(self, message: str, fields: dict | None = None):
15
+ super().__init__(message, status_code=400)
16
+ self.fields = fields or {}
17
+
18
+
19
+ class NotFoundError(AgentRepError):
20
+ """Resource not found (HTTP 404)."""
21
+
22
+
23
+ class RateLimitError(AgentRepError):
24
+ """Rate limit exceeded (HTTP 429)."""
25
+
26
+
27
+ class DuplicateRequestError(AgentRepError):
28
+ """Duplicate idempotency key (HTTP 409)."""
29
+
30
+
31
+ class ServerError(AgentRepError):
32
+ """Unexpected server error (HTTP 5xx)."""
@@ -0,0 +1 @@
1
+ """Native integrations for popular AI agent frameworks."""
@@ -0,0 +1,87 @@
1
+ """
2
+ AgentRep integration for AutoGen.
3
+
4
+ Usage::
5
+
6
+ import autogen
7
+ from agentrep.integrations.autogen import AgentRepHook
8
+
9
+ hook = AgentRepHook(
10
+ api_key="ar_xxx",
11
+ contractor_address="0xYOUR_AGENT_WALLET",
12
+ requester_address="0xREQUESTER_WALLET",
13
+ category="research",
14
+ )
15
+
16
+ assistant = autogen.AssistantAgent("assistant", llm_config={...})
17
+ hook.attach(assistant)
18
+
19
+ user_proxy = autogen.UserProxyAgent("user_proxy", ...)
20
+ user_proxy.initiate_chat(assistant, message="Analyze this dataset...")
21
+ # Outcomes submitted automatically after each reply
22
+ """
23
+
24
+ from __future__ import annotations
25
+ from typing import Any, Optional
26
+
27
+ from ..client import AgentRep
28
+
29
+
30
+ class AgentRepHook:
31
+ """
32
+ AutoGen hook that submits outcomes to AgentRep after each agent reply.
33
+
34
+ Attaches to an AutoGen ConversableAgent via register_reply or hook.
35
+ Install autogen separately: pip install pyautogen
36
+ """
37
+
38
+ def __init__(
39
+ self,
40
+ api_key: str,
41
+ contractor_address: str,
42
+ requester_address: str,
43
+ category: str = "ops",
44
+ value_usdc: float = 0.0,
45
+ base_url: Optional[str] = None,
46
+ ):
47
+ kwargs: dict = {"api_key": api_key}
48
+ if base_url:
49
+ kwargs["base_url"] = base_url
50
+ self._client = AgentRep(**kwargs)
51
+ self._contractor = contractor_address
52
+ self._requester = requester_address
53
+ self._category = category
54
+ self._value_usdc = value_usdc
55
+
56
+ def attach(self, agent: Any) -> None:
57
+ """Attach this hook to an AutoGen ConversableAgent."""
58
+ agent.register_hook(
59
+ hookable_method="process_last_received_message",
60
+ hook=self._on_message,
61
+ )
62
+
63
+ def _on_message(self, message: str) -> str:
64
+ """Hook called after each message — submit as outcome."""
65
+ try:
66
+ self._client.submit_outcome(
67
+ contractor=self._contractor,
68
+ requester=self._requester,
69
+ task="AutoGen agent reply",
70
+ deliverable=message,
71
+ category=self._category,
72
+ value_usdc=self._value_usdc,
73
+ )
74
+ except Exception:
75
+ pass # fail-open
76
+ return message
77
+
78
+ def submit(self, task: str, deliverable: str, value_usdc: Optional[float] = None):
79
+ """Manually submit an outcome."""
80
+ return self._client.submit_outcome(
81
+ contractor=self._contractor,
82
+ requester=self._requester,
83
+ task=task,
84
+ deliverable=deliverable,
85
+ category=self._category,
86
+ value_usdc=value_usdc if value_usdc is not None else self._value_usdc,
87
+ )
@@ -0,0 +1,80 @@
1
+ """
2
+ AgentRep integration for CrewAI.
3
+
4
+ Usage::
5
+
6
+ from crewai import Agent, Task, Crew
7
+ from agentrep.integrations.crewai import AgentRepTracker
8
+
9
+ tracker = AgentRepTracker(
10
+ api_key="ar_xxx",
11
+ contractor_address="0xYOUR_AGENT_WALLET",
12
+ requester_address="0xREQUESTER_WALLET",
13
+ category="research",
14
+ )
15
+
16
+ my_agent = Agent(role="Researcher", goal="...", backstory="...")
17
+ task = Task(description="Analyze X", agent=my_agent)
18
+
19
+ crew = Crew(agents=[my_agent], tasks=[task])
20
+ result = crew.kickoff()
21
+
22
+ # Submit the outcome after execution
23
+ outcome = tracker.track(
24
+ task_description=task.description,
25
+ deliverable=str(result),
26
+ value_usdc=5.0,
27
+ )
28
+ print(outcome.verdict, outcome.on_chain_tx)
29
+ """
30
+
31
+ from __future__ import annotations
32
+ from typing import TYPE_CHECKING, Optional
33
+
34
+ if TYPE_CHECKING:
35
+ pass
36
+
37
+ from ..client import AgentRep
38
+ from ..models import OutcomeResult
39
+
40
+
41
+ class AgentRepTracker:
42
+ """
43
+ Submits CrewAI task outcomes to AgentRep after execution.
44
+
45
+ This is a lightweight wrapper — call `tracker.track()` after `crew.kickoff()`.
46
+ No monkey-patching, no hooks required.
47
+ """
48
+
49
+ def __init__(
50
+ self,
51
+ api_key: str,
52
+ contractor_address: str,
53
+ requester_address: str,
54
+ category: str = "ops",
55
+ base_url: Optional[str] = None,
56
+ ):
57
+ kwargs = {"api_key": api_key}
58
+ if base_url:
59
+ kwargs["base_url"] = base_url
60
+ self._client = AgentRep(**kwargs)
61
+ self._contractor = contractor_address
62
+ self._requester = requester_address
63
+ self._category = category
64
+
65
+ def track(
66
+ self,
67
+ task_description: str,
68
+ deliverable: str,
69
+ value_usdc: float = 0.0,
70
+ category: Optional[str] = None,
71
+ ) -> OutcomeResult:
72
+ """Submit a completed CrewAI task to AgentRep for evaluation."""
73
+ return self._client.submit_outcome(
74
+ contractor=self._contractor,
75
+ requester=self._requester,
76
+ task=task_description,
77
+ deliverable=deliverable,
78
+ category=category or self._category,
79
+ value_usdc=value_usdc,
80
+ )
@@ -0,0 +1,99 @@
1
+ """
2
+ AgentRep integration for LangChain.
3
+
4
+ Usage::
5
+
6
+ from langchain.agents import AgentExecutor
7
+ from agentrep.integrations.langchain import AgentRepCallback
8
+
9
+ callback = AgentRepCallback(
10
+ api_key="ar_xxx",
11
+ contractor_address="0xYOUR_AGENT_WALLET",
12
+ requester_address="0xREQUESTER_WALLET",
13
+ category="code-review",
14
+ )
15
+
16
+ agent_executor = AgentExecutor(agent=agent, tools=tools)
17
+ result = agent_executor.invoke(
18
+ {"input": "Review this code..."},
19
+ config={"callbacks": [callback]},
20
+ )
21
+ # Outcome is submitted automatically on chain end
22
+ """
23
+
24
+ from __future__ import annotations
25
+ from typing import Any, Optional
26
+ from uuid import UUID
27
+
28
+ from ..client import AgentRep
29
+ from ..models import OutcomeResult
30
+
31
+
32
+ class AgentRepCallback:
33
+ """
34
+ LangChain callback that automatically submits outcomes to AgentRep
35
+ when a chain completes.
36
+
37
+ Compatible with LangChain's BaseCallbackHandler interface.
38
+ Install langchain separately: pip install langchain
39
+ """
40
+
41
+ def __init__(
42
+ self,
43
+ api_key: str,
44
+ contractor_address: str,
45
+ requester_address: str,
46
+ category: str = "ops",
47
+ value_usdc: float = 0.0,
48
+ base_url: Optional[str] = None,
49
+ ):
50
+ kwargs: dict = {"api_key": api_key}
51
+ if base_url:
52
+ kwargs["base_url"] = base_url
53
+ self._client = AgentRep(**kwargs)
54
+ self._contractor = contractor_address
55
+ self._requester = requester_address
56
+ self._category = category
57
+ self._value_usdc = value_usdc
58
+ self._last_input: Optional[str] = None
59
+ self.last_outcome: Optional[OutcomeResult] = None
60
+
61
+ # LangChain callback interface
62
+
63
+ def on_chain_start(
64
+ self, serialized: dict, inputs: dict, *, run_id: UUID, **kwargs: Any
65
+ ) -> None:
66
+ self._last_input = str(inputs.get("input") or inputs)
67
+
68
+ def on_chain_end(
69
+ self, outputs: dict, *, run_id: UUID, **kwargs: Any
70
+ ) -> None:
71
+ if not self._last_input:
72
+ return
73
+ deliverable = str(outputs.get("output") or outputs)
74
+ try:
75
+ self.last_outcome = self._client.submit_outcome(
76
+ contractor=self._contractor,
77
+ requester=self._requester,
78
+ task=self._last_input,
79
+ deliverable=deliverable,
80
+ category=self._category,
81
+ value_usdc=self._value_usdc,
82
+ )
83
+ except Exception:
84
+ pass # fail-open — don't break the chain
85
+
86
+ def on_chain_error(self, error: Exception, *, run_id: UUID, **kwargs: Any) -> None:
87
+ if not self._last_input:
88
+ return
89
+ try:
90
+ self._client.submit_outcome(
91
+ contractor=self._contractor,
92
+ requester=self._requester,
93
+ task=self._last_input,
94
+ deliverable=f"[ERROR] {error}",
95
+ category=self._category,
96
+ value_usdc=self._value_usdc,
97
+ )
98
+ except Exception:
99
+ pass
@@ -0,0 +1,70 @@
1
+ from dataclasses import dataclass, field
2
+ from typing import Optional
3
+
4
+
5
+ @dataclass
6
+ class RegisterResult:
7
+ agent_id: str
8
+ api_key: str
9
+ wallet_address: str
10
+
11
+
12
+ @dataclass
13
+ class CategoryScore:
14
+ category: str
15
+ score: float
16
+ total_outcomes: int
17
+
18
+
19
+ @dataclass
20
+ class ReputationScore:
21
+ wallet_address: str
22
+ score: float
23
+ tier: str
24
+ total_outcomes: int
25
+ success_rate: float
26
+ category_scores: list[CategoryScore] = field(default_factory=list)
27
+ on_chain_verified: bool = False
28
+
29
+
30
+ @dataclass
31
+ class OutcomeResult:
32
+ outcome_id: str
33
+ status: str
34
+ verdict: Optional[str] = None
35
+ llm_judge_reasoning: Optional[str] = None
36
+ llm_confidence: Optional[float] = None
37
+ on_chain_tx: Optional[str] = None
38
+ score_impact: Optional[float] = None
39
+
40
+
41
+ @dataclass
42
+ class DisputeResult:
43
+ dispute_id: str
44
+ outcome_id: str
45
+ status: str
46
+ reason: str
47
+ verdict: Optional[str] = None
48
+
49
+
50
+ @dataclass
51
+ class Agent:
52
+ agent_id: str
53
+ wallet_address: str
54
+ name: str
55
+ description: Optional[str]
56
+ score: float
57
+ tier: str
58
+ total_outcomes: int
59
+ success_rate: float
60
+ categories: list[str] = field(default_factory=list)
61
+ on_chain_verified: bool = False
62
+
63
+
64
+ @dataclass
65
+ class ExploreResult:
66
+ agents: list[Agent]
67
+ total: int
68
+ page: int
69
+ size: int
70
+ total_pages: int
@@ -0,0 +1,39 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "agentrep"
7
+ version = "0.1.0"
8
+ description = "The credit score for AI agents — on-chain reputation evaluated by Claude"
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ requires-python = ">=3.10"
12
+ keywords = ["ai", "agents", "reputation", "web3", "base", "blockchain", "llm"]
13
+ classifiers = [
14
+ "Development Status :: 4 - Beta",
15
+ "Intended Audience :: Developers",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.10",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ "Topic :: Software Development :: Libraries",
22
+ ]
23
+ dependencies = [] # zero required dependencies — stdlib only
24
+
25
+ [project.optional-dependencies]
26
+ crewai = ["crewai>=0.1.0"]
27
+ langchain = ["langchain-core>=0.1.0"]
28
+ autogen = ["pyautogen>=0.2.0"]
29
+ all = ["crewai>=0.1.0", "langchain-core>=0.1.0", "pyautogen>=0.2.0"]
30
+ dev = ["pytest>=7.0", "pytest-mock>=3.0", "responses>=0.25"]
31
+
32
+ [project.urls]
33
+ Homepage = "https://agentrep.com.br"
34
+ Documentation = "https://docs.agentrep.com.br"
35
+ Repository = "https://github.com/rafaelbcs/agentrep-python"
36
+ "Bug Tracker" = "https://github.com/rafaelbcs/agentrep-python/issues"
37
+
38
+ [tool.hatch.build.targets.wheel]
39
+ packages = ["agentrep"]
@@ -0,0 +1,142 @@
1
+ """Unit tests for AgentRep SDK — uses urllib mocking, no external deps."""
2
+ import json
3
+ import unittest
4
+ from io import BytesIO
5
+ from unittest.mock import patch, MagicMock
6
+ from urllib.error import HTTPError
7
+
8
+ from agentrep import AgentRep
9
+ from agentrep.exceptions import (
10
+ AuthenticationError, ValidationError, NotFoundError, RateLimitError
11
+ )
12
+
13
+
14
+ def _mock_response(data: dict, status: int = 200):
15
+ """Create a mock urllib response."""
16
+ body = json.dumps(data).encode()
17
+ mock = MagicMock()
18
+ mock.__enter__ = lambda s: s
19
+ mock.__exit__ = MagicMock(return_value=False)
20
+ mock.read.return_value = body
21
+ mock.status = status
22
+ return mock
23
+
24
+
25
+ def _http_error(status: int, data: dict):
26
+ body = json.dumps(data).encode()
27
+ err = HTTPError(url="", code=status, msg="", hdrs={}, fp=BytesIO(body))
28
+ return err
29
+
30
+
31
+ class TestGetReputation(unittest.TestCase):
32
+ @patch("urllib.request.urlopen")
33
+ def test_get_reputation_success(self, mock_open):
34
+ mock_open.return_value = _mock_response({
35
+ "walletAddress": "0xabc",
36
+ "score": 85.0,
37
+ "tier": "TRUSTED",
38
+ "totalOutcomes": 10,
39
+ "successRate": 0.9,
40
+ "categoryScores": [
41
+ {"category": "code-review", "score": 90.0, "totalOutcomes": 5}
42
+ ],
43
+ "onChainVerified": True,
44
+ })
45
+ rep = AgentRep()
46
+ score = rep.get_reputation("0xabc")
47
+
48
+ self.assertEqual(score.score, 85.0)
49
+ self.assertEqual(score.tier, "TRUSTED")
50
+ self.assertEqual(score.total_outcomes, 10)
51
+ self.assertEqual(len(score.category_scores), 1)
52
+ self.assertEqual(score.category_scores[0].category, "code-review")
53
+
54
+ @patch("urllib.request.urlopen")
55
+ def test_get_reputation_not_found(self, mock_open):
56
+ mock_open.side_effect = _http_error(404, {"error": "Agent not found"})
57
+ rep = AgentRep()
58
+ with self.assertRaises(NotFoundError):
59
+ rep.get_reputation("0xdeadbeef")
60
+
61
+
62
+ class TestSubmitOutcome(unittest.TestCase):
63
+ @patch("urllib.request.urlopen")
64
+ def test_submit_outcome_success(self, mock_open):
65
+ mock_open.return_value = _mock_response({
66
+ "outcomeId": "abc-123",
67
+ "status": "RESOLVED",
68
+ "verdict": "SUCCESS",
69
+ "llmJudgeReasoning": "Task completed correctly.",
70
+ "llmConfidence": 0.95,
71
+ "onChainTx": "0xtxhash",
72
+ "scoreImpact": 2.5,
73
+ })
74
+ rep = AgentRep(api_key="ar_test")
75
+ outcome = rep.submit_outcome(
76
+ contractor="0xA",
77
+ requester="0xB",
78
+ task="Write a hello world",
79
+ deliverable="print('hello world')",
80
+ category="code-review",
81
+ )
82
+
83
+ self.assertEqual(outcome.verdict, "SUCCESS")
84
+ self.assertEqual(outcome.on_chain_tx, "0xtxhash")
85
+
86
+ def test_submit_outcome_requires_api_key(self):
87
+ rep = AgentRep() # no api_key
88
+ with self.assertRaises(AuthenticationError):
89
+ rep.submit_outcome("0xA", "0xB", "task", "deliverable")
90
+
91
+ @patch("urllib.request.urlopen")
92
+ def test_submit_outcome_validation_error(self, mock_open):
93
+ mock_open.side_effect = _http_error(400, {
94
+ "error": "Validation failed",
95
+ "fields": {"agentAddress": "Invalid EVM wallet address"},
96
+ })
97
+ rep = AgentRep(api_key="ar_test")
98
+ with self.assertRaises(ValidationError) as ctx:
99
+ rep.submit_outcome("invalid", "0xB", "task", "deliverable")
100
+ self.assertIn("agentAddress", ctx.exception.fields)
101
+
102
+
103
+ class TestRegister(unittest.TestCase):
104
+ @patch("urllib.request.urlopen")
105
+ def test_register_success(self, mock_open):
106
+ mock_open.return_value = _mock_response({
107
+ "agentId": "id-1",
108
+ "apiKey": "ar_abc123",
109
+ "walletAddress": "0xabc",
110
+ })
111
+ rep = AgentRep()
112
+ result = rep.register(
113
+ wallet_address="0xabc",
114
+ name="Test Agent",
115
+ categories=["code-review"],
116
+ )
117
+ self.assertEqual(result.api_key, "ar_abc123")
118
+ self.assertEqual(result.wallet_address, "0xabc")
119
+
120
+
121
+ class TestLeaderboard(unittest.TestCase):
122
+ @patch("urllib.request.urlopen")
123
+ def test_leaderboard(self, mock_open):
124
+ mock_open.return_value = _mock_response({
125
+ "content": [
126
+ {
127
+ "agentId": "1", "walletAddress": "0xA", "name": "Alpha",
128
+ "description": None, "score": 95.0, "tier": "ELITE",
129
+ "totalOutcomes": 50, "successRate": 0.98,
130
+ "categories": ["research"], "onChainVerified": True,
131
+ }
132
+ ],
133
+ "totalElements": 1, "number": 0, "size": 20, "totalPages": 1,
134
+ })
135
+ rep = AgentRep()
136
+ result = rep.leaderboard()
137
+ self.assertEqual(len(result.agents), 1)
138
+ self.assertEqual(result.agents[0].tier, "ELITE")
139
+
140
+
141
+ if __name__ == "__main__":
142
+ unittest.main()