loopuman 1.0.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.
- loopuman-1.0.0/PKG-INFO +23 -0
- loopuman-1.0.0/README.md +88 -0
- loopuman-1.0.0/loopuman/__init__.py +263 -0
- loopuman-1.0.0/loopuman/langchain_tool.py +61 -0
- loopuman-1.0.0/loopuman.egg-info/PKG-INFO +23 -0
- loopuman-1.0.0/loopuman.egg-info/SOURCES.txt +9 -0
- loopuman-1.0.0/loopuman.egg-info/dependency_links.txt +1 -0
- loopuman-1.0.0/loopuman.egg-info/requires.txt +1 -0
- loopuman-1.0.0/loopuman.egg-info/top_level.txt +1 -0
- loopuman-1.0.0/setup.cfg +4 -0
- loopuman-1.0.0/setup.py +21 -0
loopuman-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: loopuman
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: The Human API for AI - Give your AI agents instant access to humans
|
|
5
|
+
Home-page: https://github.com/loopuman/loopuman-python
|
|
6
|
+
Author: Loopuman
|
|
7
|
+
Author-email: hello@loopuman.com
|
|
8
|
+
Keywords: ai human-in-the-loop microtasks langchain agents
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
14
|
+
Requires-Python: >=3.7
|
|
15
|
+
Requires-Dist: requests>=2.25.0
|
|
16
|
+
Dynamic: author
|
|
17
|
+
Dynamic: author-email
|
|
18
|
+
Dynamic: classifier
|
|
19
|
+
Dynamic: home-page
|
|
20
|
+
Dynamic: keywords
|
|
21
|
+
Dynamic: requires-dist
|
|
22
|
+
Dynamic: requires-python
|
|
23
|
+
Dynamic: summary
|
loopuman-1.0.0/README.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Loopuman Python SDK
|
|
2
|
+
|
|
3
|
+
**The Human API for AI** - Give your AI agents instant access to humans.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
```bash
|
|
7
|
+
pip install loopuman
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Quick Start
|
|
11
|
+
```python
|
|
12
|
+
from loopuman import Loopuman
|
|
13
|
+
|
|
14
|
+
client = Loopuman(api_key="your_key")
|
|
15
|
+
|
|
16
|
+
# Ask a human and wait for response
|
|
17
|
+
result = client.ask("Is this content appropriate for children?")
|
|
18
|
+
print(result.response) # "Yes" or "No" from human
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Use Cases
|
|
22
|
+
|
|
23
|
+
### Content Moderation
|
|
24
|
+
```python
|
|
25
|
+
result = client.ask(
|
|
26
|
+
question="Is this image appropriate?",
|
|
27
|
+
context="Image shows: a sunset over mountains",
|
|
28
|
+
budget_cents=25
|
|
29
|
+
)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Fact Verification
|
|
33
|
+
```python
|
|
34
|
+
result = client.ask(
|
|
35
|
+
question="Is this claim accurate: 'The Eiffel Tower is 300m tall'",
|
|
36
|
+
budget_cents=50
|
|
37
|
+
)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### AI Agent Oversight
|
|
41
|
+
```python
|
|
42
|
+
# Before sending an important message
|
|
43
|
+
result = client.ask(
|
|
44
|
+
question=f"Should I send this message? '{draft_message}'",
|
|
45
|
+
budget_cents=30
|
|
46
|
+
)
|
|
47
|
+
if "yes" in result.response.lower():
|
|
48
|
+
send_message(draft_message)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## LangChain Integration
|
|
52
|
+
```python
|
|
53
|
+
from loopuman.langchain_tool import LoopumanTool
|
|
54
|
+
from langchain.agents import initialize_agent
|
|
55
|
+
|
|
56
|
+
tool = LoopumanTool(api_key="your_key")
|
|
57
|
+
agent = initialize_agent([tool], llm, agent="zero-shot-react-description")
|
|
58
|
+
|
|
59
|
+
# Agent can now ask humans for help
|
|
60
|
+
response = agent.run("Verify if this product review is genuine: '...'")
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Async Tasks (with webhooks)
|
|
64
|
+
```python
|
|
65
|
+
# Create task and receive webhook when complete
|
|
66
|
+
task = client.create_task(
|
|
67
|
+
title="Review this document",
|
|
68
|
+
description="Check for errors",
|
|
69
|
+
budget_cents=100,
|
|
70
|
+
webhook_url="https://your-app.com/webhook"
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
print(f"Task created: {task.id}")
|
|
74
|
+
# Your webhook will receive the result when a human completes it
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Pricing
|
|
78
|
+
|
|
79
|
+
- Minimum: $0.10 per task
|
|
80
|
+
- Typical: $0.25 - $1.00 per task
|
|
81
|
+
- You pay budget + 20% platform fee
|
|
82
|
+
- Workers receive budget - 20%
|
|
83
|
+
|
|
84
|
+
## Links
|
|
85
|
+
|
|
86
|
+
- Website: https://loopuman.com
|
|
87
|
+
- API Docs: https://loopuman.com/docs
|
|
88
|
+
- Dashboard: https://loopuman.com/dashboard
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Loopuman Python SDK
|
|
3
|
+
The Human API for AI - Give your AI agents instant access to humans
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
from loopuman import Loopuman
|
|
7
|
+
|
|
8
|
+
client = Loopuman(api_key="your_key")
|
|
9
|
+
|
|
10
|
+
# Synchronous - waits for human response
|
|
11
|
+
result = client.ask("Is this content appropriate?", budget_cents=50)
|
|
12
|
+
print(result.response)
|
|
13
|
+
|
|
14
|
+
# Async - returns immediately, check later
|
|
15
|
+
task = client.create_task("Review this document", budget_cents=100)
|
|
16
|
+
# ... later ...
|
|
17
|
+
result = client.get_result(task.id)
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import requests
|
|
21
|
+
from dataclasses import dataclass
|
|
22
|
+
from typing import Optional, List, Dict, Any
|
|
23
|
+
|
|
24
|
+
__version__ = "1.0.0"
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class TaskResult:
|
|
28
|
+
status: str # "completed", "timeout", "pending"
|
|
29
|
+
task_id: str
|
|
30
|
+
response: Optional[str] = None
|
|
31
|
+
worker_id: Optional[str] = None
|
|
32
|
+
completed_in_seconds: Optional[int] = None
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class Task:
|
|
36
|
+
id: str
|
|
37
|
+
title: str
|
|
38
|
+
status: str
|
|
39
|
+
budget: int
|
|
40
|
+
|
|
41
|
+
class LoopumanError(Exception):
|
|
42
|
+
"""Base exception for Loopuman errors"""
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
class Loopuman:
|
|
46
|
+
"""
|
|
47
|
+
Loopuman client - The Human API for AI
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
api_key: Your Loopuman API key
|
|
51
|
+
base_url: API base URL (default: https://api.loopuman.com)
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def __init__(self, api_key: str, base_url: str = "https://api.loopuman.com"):
|
|
55
|
+
self.api_key = api_key
|
|
56
|
+
self.base_url = base_url.rstrip("/")
|
|
57
|
+
self._session = requests.Session()
|
|
58
|
+
self._session.headers.update({
|
|
59
|
+
"X-API-Key": api_key,
|
|
60
|
+
"Content-Type": "application/json"
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
def ask(
|
|
64
|
+
self,
|
|
65
|
+
question: str,
|
|
66
|
+
context: str = "",
|
|
67
|
+
budget_cents: int = 50,
|
|
68
|
+
timeout_seconds: int = 300,
|
|
69
|
+
auto_approve: bool = True
|
|
70
|
+
) -> TaskResult:
|
|
71
|
+
"""
|
|
72
|
+
Ask a human a question and wait for the response.
|
|
73
|
+
|
|
74
|
+
This is a synchronous call - it blocks until a human responds
|
|
75
|
+
or the timeout is reached.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
question: The question to ask
|
|
79
|
+
context: Additional context for the human
|
|
80
|
+
budget_cents: Payment in cents (min 10, typical 25-100)
|
|
81
|
+
timeout_seconds: Max time to wait (default 5 minutes)
|
|
82
|
+
auto_approve: Auto-approve the submission (default True)
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
TaskResult with the human's response
|
|
86
|
+
|
|
87
|
+
Example:
|
|
88
|
+
result = client.ask("Is this image appropriate?")
|
|
89
|
+
if result.status == "completed":
|
|
90
|
+
print(f"Human said: {result.response}")
|
|
91
|
+
"""
|
|
92
|
+
response = self._session.post(
|
|
93
|
+
f"{self.base_url}/api/v1/tasks/sync",
|
|
94
|
+
json={
|
|
95
|
+
"title": question,
|
|
96
|
+
"description": context,
|
|
97
|
+
"budget": budget_cents,
|
|
98
|
+
"timeout_seconds": timeout_seconds,
|
|
99
|
+
"auto_approve": auto_approve
|
|
100
|
+
}
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
if response.status_code != 200:
|
|
104
|
+
raise LoopumanError(f"API error: {response.text}")
|
|
105
|
+
|
|
106
|
+
data = response.json()
|
|
107
|
+
return TaskResult(
|
|
108
|
+
status=data.get("status", "error"),
|
|
109
|
+
task_id=data.get("task_id", ""),
|
|
110
|
+
response=data.get("response"),
|
|
111
|
+
worker_id=data.get("worker_id"),
|
|
112
|
+
completed_in_seconds=data.get("completed_in_seconds")
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
def create_task(
|
|
116
|
+
self,
|
|
117
|
+
title: str,
|
|
118
|
+
description: str = "",
|
|
119
|
+
category: str = "ai_training",
|
|
120
|
+
budget_cents: int = 50,
|
|
121
|
+
webhook_url: Optional[str] = None
|
|
122
|
+
) -> Task:
|
|
123
|
+
"""
|
|
124
|
+
Create a task asynchronously.
|
|
125
|
+
|
|
126
|
+
Use this when you don't need to wait for the response inline.
|
|
127
|
+
You can check for results later or receive a webhook.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
title: Task title
|
|
131
|
+
description: Task description
|
|
132
|
+
category: Task category (default: ai_training)
|
|
133
|
+
budget_cents: Payment in cents
|
|
134
|
+
webhook_url: URL to receive completion webhook
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
Task object with the task ID
|
|
138
|
+
"""
|
|
139
|
+
payload = {
|
|
140
|
+
"tasks": [{
|
|
141
|
+
"title": title,
|
|
142
|
+
"description": description,
|
|
143
|
+
"category": category,
|
|
144
|
+
"budget": budget_cents
|
|
145
|
+
}]
|
|
146
|
+
}
|
|
147
|
+
if webhook_url:
|
|
148
|
+
payload["webhook_url"] = webhook_url
|
|
149
|
+
|
|
150
|
+
response = self._session.post(
|
|
151
|
+
f"{self.base_url}/api/v1/tasks/bulk",
|
|
152
|
+
json=payload
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
if response.status_code != 200:
|
|
156
|
+
raise LoopumanError(f"API error: {response.text}")
|
|
157
|
+
|
|
158
|
+
data = response.json()
|
|
159
|
+
task_id = data.get("task_ids", [None])[0]
|
|
160
|
+
|
|
161
|
+
return Task(
|
|
162
|
+
id=task_id,
|
|
163
|
+
title=title,
|
|
164
|
+
status="active",
|
|
165
|
+
budget=budget_cents
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
def get_result(self, task_id: str) -> Optional[TaskResult]:
|
|
169
|
+
"""
|
|
170
|
+
Check if a task has been completed.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
task_id: The task ID to check
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
TaskResult if completed, None if still pending
|
|
177
|
+
"""
|
|
178
|
+
response = self._session.get(
|
|
179
|
+
f"{self.base_url}/api/v1/tasks/{task_id}"
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
if response.status_code != 200:
|
|
183
|
+
raise LoopumanError(f"API error: {response.text}")
|
|
184
|
+
|
|
185
|
+
data = response.json()
|
|
186
|
+
if data.get("submissions"):
|
|
187
|
+
sub = data["submissions"][0]
|
|
188
|
+
return TaskResult(
|
|
189
|
+
status="completed",
|
|
190
|
+
task_id=task_id,
|
|
191
|
+
response=sub.get("content"),
|
|
192
|
+
worker_id=sub.get("worker_id")
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
return None
|
|
196
|
+
|
|
197
|
+
def bulk_create(
|
|
198
|
+
self,
|
|
199
|
+
tasks: List[Dict[str, Any]],
|
|
200
|
+
webhook_url: Optional[str] = None
|
|
201
|
+
) -> Dict[str, Any]:
|
|
202
|
+
"""
|
|
203
|
+
Create multiple tasks at once.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
tasks: List of task dicts with title, description, budget
|
|
207
|
+
webhook_url: URL to receive completion webhooks
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
Dict with batch_id and task_ids
|
|
211
|
+
|
|
212
|
+
Example:
|
|
213
|
+
result = client.bulk_create([
|
|
214
|
+
{"title": "Review image 1", "budget": 25},
|
|
215
|
+
{"title": "Review image 2", "budget": 25},
|
|
216
|
+
])
|
|
217
|
+
"""
|
|
218
|
+
payload = {"tasks": tasks}
|
|
219
|
+
if webhook_url:
|
|
220
|
+
payload["webhook_url"] = webhook_url
|
|
221
|
+
|
|
222
|
+
response = self._session.post(
|
|
223
|
+
f"{self.base_url}/api/v1/tasks/bulk",
|
|
224
|
+
json=payload
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
if response.status_code != 200:
|
|
228
|
+
raise LoopumanError(f"API error: {response.text}")
|
|
229
|
+
|
|
230
|
+
return response.json()
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
# LangChain Tool Integration
|
|
234
|
+
def get_langchain_tool():
|
|
235
|
+
"""
|
|
236
|
+
Get a LangChain-compatible tool for human-in-the-loop.
|
|
237
|
+
|
|
238
|
+
Usage:
|
|
239
|
+
from loopuman import get_langchain_tool
|
|
240
|
+
|
|
241
|
+
human_tool = get_langchain_tool()
|
|
242
|
+
agent = initialize_agent([human_tool, ...], llm)
|
|
243
|
+
"""
|
|
244
|
+
try:
|
|
245
|
+
from langchain.tools import Tool
|
|
246
|
+
except ImportError:
|
|
247
|
+
raise ImportError("langchain is required: pip install langchain")
|
|
248
|
+
|
|
249
|
+
import os
|
|
250
|
+
client = Loopuman(api_key=os.environ.get("LOOPUMAN_API_KEY", ""))
|
|
251
|
+
|
|
252
|
+
def ask_human(query: str) -> str:
|
|
253
|
+
"""Ask a human for help with verification or judgment."""
|
|
254
|
+
result = client.ask(query, timeout_seconds=300)
|
|
255
|
+
if result.status == "completed":
|
|
256
|
+
return result.response
|
|
257
|
+
return f"No human responded. Task ID: {result.task_id}"
|
|
258
|
+
|
|
259
|
+
return Tool(
|
|
260
|
+
name="AskHuman",
|
|
261
|
+
func=ask_human,
|
|
262
|
+
description="Ask a human for help with verification, judgment, or subjective evaluation. Use when AI is uncertain or needs human oversight."
|
|
263
|
+
)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""
|
|
2
|
+
LangChain integration for Loopuman
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
from loopuman.langchain_tool import LoopumanTool
|
|
6
|
+
|
|
7
|
+
tool = LoopumanTool(api_key="your_key")
|
|
8
|
+
agent = initialize_agent([tool], llm)
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
from typing import Optional
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
from langchain.tools import BaseTool
|
|
16
|
+
from pydantic import Field
|
|
17
|
+
except ImportError:
|
|
18
|
+
raise ImportError("langchain is required: pip install langchain")
|
|
19
|
+
|
|
20
|
+
from . import Loopuman
|
|
21
|
+
|
|
22
|
+
class LoopumanTool(BaseTool):
|
|
23
|
+
"""LangChain tool that gives agents access to human workers."""
|
|
24
|
+
|
|
25
|
+
name: str = "ask_human"
|
|
26
|
+
description: str = """Ask a human worker to help with a task.
|
|
27
|
+
Use this tool when you need:
|
|
28
|
+
- Verification of facts or content
|
|
29
|
+
- Subjective judgment (is this appropriate, offensive, accurate?)
|
|
30
|
+
- Real-world information (what's at this location?)
|
|
31
|
+
- Human oversight for important decisions
|
|
32
|
+
|
|
33
|
+
Input should be a clear question or task description.
|
|
34
|
+
Returns the human's response as a string."""
|
|
35
|
+
|
|
36
|
+
client: Loopuman = Field(default=None, exclude=True)
|
|
37
|
+
budget_cents: int = 50
|
|
38
|
+
timeout_seconds: int = 300
|
|
39
|
+
|
|
40
|
+
def __init__(self, api_key: Optional[str] = None, **kwargs):
|
|
41
|
+
super().__init__(**kwargs)
|
|
42
|
+
key = api_key or os.environ.get("LOOPUMAN_API_KEY")
|
|
43
|
+
if not key:
|
|
44
|
+
raise ValueError("LOOPUMAN_API_KEY required")
|
|
45
|
+
self.client = Loopuman(api_key=key)
|
|
46
|
+
|
|
47
|
+
def _run(self, query: str) -> str:
|
|
48
|
+
"""Execute the tool."""
|
|
49
|
+
result = self.client.ask(
|
|
50
|
+
question=query,
|
|
51
|
+
budget_cents=self.budget_cents,
|
|
52
|
+
timeout_seconds=self.timeout_seconds
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
if result.status == "completed":
|
|
56
|
+
return result.response
|
|
57
|
+
return f"No human responded within {self.timeout_seconds}s. Task ID: {result.task_id}"
|
|
58
|
+
|
|
59
|
+
async def _arun(self, query: str) -> str:
|
|
60
|
+
"""Async not implemented yet."""
|
|
61
|
+
return self._run(query)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: loopuman
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: The Human API for AI - Give your AI agents instant access to humans
|
|
5
|
+
Home-page: https://github.com/loopuman/loopuman-python
|
|
6
|
+
Author: Loopuman
|
|
7
|
+
Author-email: hello@loopuman.com
|
|
8
|
+
Keywords: ai human-in-the-loop microtasks langchain agents
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
14
|
+
Requires-Python: >=3.7
|
|
15
|
+
Requires-Dist: requests>=2.25.0
|
|
16
|
+
Dynamic: author
|
|
17
|
+
Dynamic: author-email
|
|
18
|
+
Dynamic: classifier
|
|
19
|
+
Dynamic: home-page
|
|
20
|
+
Dynamic: keywords
|
|
21
|
+
Dynamic: requires-dist
|
|
22
|
+
Dynamic: requires-python
|
|
23
|
+
Dynamic: summary
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
requests>=2.25.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
loopuman
|
loopuman-1.0.0/setup.cfg
ADDED
loopuman-1.0.0/setup.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
setup(
|
|
4
|
+
name="loopuman",
|
|
5
|
+
version="1.0.0",
|
|
6
|
+
description="The Human API for AI - Give your AI agents instant access to humans",
|
|
7
|
+
author="Loopuman",
|
|
8
|
+
author_email="hello@loopuman.com",
|
|
9
|
+
url="https://github.com/loopuman/loopuman-python",
|
|
10
|
+
packages=find_packages(),
|
|
11
|
+
install_requires=["requests>=2.25.0"],
|
|
12
|
+
python_requires=">=3.7",
|
|
13
|
+
classifiers=[
|
|
14
|
+
"Development Status :: 4 - Beta",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
19
|
+
],
|
|
20
|
+
keywords="ai human-in-the-loop microtasks langchain agents",
|
|
21
|
+
)
|