fetch-hive-sdk 0.1.0__py3-none-any.whl

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,23 @@
1
+ """
2
+ fetch_hive_sdk — Official Python SDK for the Fetch Hive API.
3
+
4
+ Quick start::
5
+
6
+ from fetch_hive_sdk import FetchHive
7
+
8
+ client = FetchHive(api_key="fhk_...")
9
+
10
+ # Non-streaming
11
+ result = client.invoke_agent(agent="my-agent", message="Hello")
12
+ print(result["response"])
13
+
14
+ # Streaming
15
+ for chunk in client.invoke_agent_stream(agent="my-agent", message="Hello"):
16
+ if chunk.get("type") == "delta":
17
+ print(chunk.get("content", ""), end="", flush=True)
18
+ """
19
+
20
+ from .client import FetchHive
21
+ from .streaming import aiter_sse, iter_sse
22
+
23
+ __all__ = ["FetchHive", "iter_sse", "aiter_sse"]
@@ -0,0 +1,250 @@
1
+ """
2
+ client.py
3
+
4
+ Idiomatic facade for the Fetch Hive API.
5
+
6
+ Usage::
7
+
8
+ from fetch_hive_sdk import FetchHive
9
+
10
+ client = FetchHive(api_key="fhk_...")
11
+
12
+ # Non-streaming prompt
13
+ result = client.invoke_prompt(deployment="my-prompt", inputs={"name": "Alice"})
14
+ print(result["response"])
15
+
16
+ # Streaming agent
17
+ for chunk in client.invoke_agent_stream(agent="my-agent", message="Hello"):
18
+ if chunk.get("type") == "delta":
19
+ print(chunk.get("content", ""), end="", flush=True)
20
+
21
+ Async::
22
+
23
+ async for chunk in client.ainvoke_agent_stream(agent="my-agent", message="Hello"):
24
+ ...
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ import os
30
+ from typing import Any, AsyncIterator, Generator, Iterator
31
+
32
+ import httpx
33
+
34
+ from .streaming import aiter_sse, iter_sse
35
+
36
+ DEFAULT_BASE_URL = "https://api.fetchhive.com/v1"
37
+
38
+
39
+ class FetchHive:
40
+ """
41
+ Fetch Hive API client.
42
+
43
+ Args:
44
+ api_key: Bearer token from the Fetch Hive dashboard.
45
+ Defaults to the ``FETCH_HIVE_API_KEY`` environment variable.
46
+ base_url: API base URL. Defaults to ``https://api.fetchhive.com/v1``.
47
+ timeout: Request timeout in seconds (default: 120).
48
+ """
49
+
50
+ def __init__(
51
+ self,
52
+ api_key: str | None = None,
53
+ base_url: str = DEFAULT_BASE_URL,
54
+ timeout: float = 120.0,
55
+ ) -> None:
56
+ resolved_key = api_key or os.environ.get("FETCH_HIVE_API_KEY")
57
+ if not resolved_key:
58
+ raise ValueError(
59
+ "api_key is required. Pass it explicitly or set the "
60
+ "FETCH_HIVE_API_KEY environment variable."
61
+ )
62
+ self._api_key = resolved_key
63
+ self._base_url = base_url.rstrip("/")
64
+ self._timeout = timeout
65
+
66
+ @property
67
+ def _headers(self) -> dict[str, str]:
68
+ return {
69
+ "Authorization": f"Bearer {self._api_key}",
70
+ "Content-Type": "application/json",
71
+ }
72
+
73
+ def _url(self, path: str) -> str:
74
+ return f"{self._base_url}{path}"
75
+
76
+ # ── Prompt ────────────────────────────────────────────────────────────────
77
+
78
+ def invoke_prompt(
79
+ self,
80
+ *,
81
+ deployment: str,
82
+ variant: str = "",
83
+ inputs: dict[str, Any] | None = None,
84
+ user: str | None = None,
85
+ ) -> dict[str, Any]:
86
+ """Invoke a prompt deployment and return the full response."""
87
+ body: dict[str, Any] = {"deployment": deployment, "streaming": False}
88
+ if variant:
89
+ body["variant"] = variant
90
+ if inputs is not None:
91
+ body["inputs"] = inputs
92
+ if user is not None:
93
+ body["user"] = user
94
+
95
+ with httpx.Client(timeout=self._timeout) as client:
96
+ resp = client.post(self._url("/invoke"), headers=self._headers, json=body)
97
+ resp.raise_for_status()
98
+ return resp.json()
99
+
100
+ def invoke_prompt_stream(
101
+ self,
102
+ *,
103
+ deployment: str,
104
+ variant: str = "",
105
+ inputs: dict[str, Any] | None = None,
106
+ user: str | None = None,
107
+ ) -> Generator[dict[str, Any], None, None]:
108
+ """Invoke a prompt deployment and stream SSE events."""
109
+ body: dict[str, Any] = {"deployment": deployment, "streaming": True}
110
+ if variant:
111
+ body["variant"] = variant
112
+ if inputs is not None:
113
+ body["inputs"] = inputs
114
+ if user is not None:
115
+ body["user"] = user
116
+
117
+ with httpx.Client(timeout=self._timeout) as client:
118
+ with client.stream("POST", self._url("/invoke"), headers=self._headers, json=body) as resp:
119
+ yield from iter_sse(resp)
120
+
121
+ async def ainvoke_prompt_stream(
122
+ self,
123
+ *,
124
+ deployment: str,
125
+ variant: str = "",
126
+ inputs: dict[str, Any] | None = None,
127
+ user: str | None = None,
128
+ ) -> AsyncIterator[dict[str, Any]]:
129
+ """Async: invoke a prompt deployment and stream SSE events."""
130
+ body: dict[str, Any] = {"deployment": deployment, "streaming": True}
131
+ if variant:
132
+ body["variant"] = variant
133
+ if inputs is not None:
134
+ body["inputs"] = inputs
135
+ if user is not None:
136
+ body["user"] = user
137
+
138
+ async with httpx.AsyncClient(timeout=self._timeout) as client:
139
+ async with client.stream("POST", self._url("/invoke"), headers=self._headers, json=body) as resp:
140
+ async for chunk in aiter_sse(resp):
141
+ yield chunk
142
+
143
+ # ── Workflow ──────────────────────────────────────────────────────────────
144
+
145
+ def invoke_workflow(
146
+ self,
147
+ *,
148
+ deployment: str,
149
+ variant: str = "",
150
+ inputs: dict[str, Any] | None = None,
151
+ async_mode: bool = False,
152
+ callback_url: str | None = None,
153
+ user: str | None = None,
154
+ ) -> dict[str, Any]:
155
+ """Invoke a workflow deployment (sync or async)."""
156
+ body: dict[str, Any] = {"deployment": deployment}
157
+ if variant:
158
+ body["variant"] = variant
159
+ if inputs is not None:
160
+ body["inputs"] = inputs
161
+ if user is not None:
162
+ body["user"] = user
163
+ if async_mode:
164
+ body["async"] = {"enabled": True}
165
+ if callback_url:
166
+ body["async"]["callback_url"] = callback_url
167
+
168
+ with httpx.Client(timeout=self._timeout) as client:
169
+ resp = client.post(self._url("/workflow/invoke"), headers=self._headers, json=body)
170
+ resp.raise_for_status()
171
+ return resp.json()
172
+
173
+ # ── Agent ─────────────────────────────────────────────────────────────────
174
+
175
+ def invoke_agent(
176
+ self,
177
+ *,
178
+ agent: str,
179
+ message: str,
180
+ thread_id: str = "",
181
+ user: str | None = None,
182
+ messages: list[dict[str, Any]] | None = None,
183
+ image_urls: list[str] | None = None,
184
+ ) -> dict[str, Any]:
185
+ """Send a message to an agent and return the full response."""
186
+ body: dict[str, Any] = {"agent": agent, "message": message, "streaming": False}
187
+ if thread_id:
188
+ body["thread_id"] = thread_id
189
+ if user is not None:
190
+ body["user"] = user
191
+ if messages is not None:
192
+ body["messages"] = messages
193
+ if image_urls:
194
+ body["image_urls"] = image_urls
195
+
196
+ with httpx.Client(timeout=self._timeout) as client:
197
+ resp = client.post(self._url("/agent/invoke"), headers=self._headers, json=body)
198
+ resp.raise_for_status()
199
+ return resp.json()
200
+
201
+ def invoke_agent_stream(
202
+ self,
203
+ *,
204
+ agent: str,
205
+ message: str,
206
+ thread_id: str = "",
207
+ user: str | None = None,
208
+ messages: list[dict[str, Any]] | None = None,
209
+ image_urls: list[str] | None = None,
210
+ ) -> Generator[dict[str, Any], None, None]:
211
+ """Send a message to an agent and stream SSE events."""
212
+ body: dict[str, Any] = {"agent": agent, "message": message, "streaming": True}
213
+ if thread_id:
214
+ body["thread_id"] = thread_id
215
+ if user is not None:
216
+ body["user"] = user
217
+ if messages is not None:
218
+ body["messages"] = messages
219
+ if image_urls:
220
+ body["image_urls"] = image_urls
221
+
222
+ with httpx.Client(timeout=self._timeout) as client:
223
+ with client.stream("POST", self._url("/agent/invoke"), headers=self._headers, json=body) as resp:
224
+ yield from iter_sse(resp)
225
+
226
+ async def ainvoke_agent_stream(
227
+ self,
228
+ *,
229
+ agent: str,
230
+ message: str,
231
+ thread_id: str = "",
232
+ user: str | None = None,
233
+ messages: list[dict[str, Any]] | None = None,
234
+ image_urls: list[str] | None = None,
235
+ ) -> AsyncIterator[dict[str, Any]]:
236
+ """Async: send a message to an agent and stream SSE events."""
237
+ body: dict[str, Any] = {"agent": agent, "message": message, "streaming": True}
238
+ if thread_id:
239
+ body["thread_id"] = thread_id
240
+ if user is not None:
241
+ body["user"] = user
242
+ if messages is not None:
243
+ body["messages"] = messages
244
+ if image_urls:
245
+ body["image_urls"] = image_urls
246
+
247
+ async with httpx.AsyncClient(timeout=self._timeout) as client:
248
+ async with client.stream("POST", self._url("/agent/invoke"), headers=self._headers, json=body) as resp:
249
+ async for chunk in aiter_sse(resp):
250
+ yield chunk
@@ -0,0 +1,90 @@
1
+ """
2
+ streaming.py
3
+
4
+ Lightweight Server-Sent Events (SSE) parser for streaming Fetch Hive responses.
5
+
6
+ Provides both synchronous and asynchronous generators.
7
+
8
+ Sync example::
9
+
10
+ import httpx
11
+ from fetch_hive_sdk.streaming import iter_sse
12
+
13
+ with httpx.stream("POST", url, headers=headers, json=body) as response:
14
+ for chunk in iter_sse(response):
15
+ if chunk.get("type") == "delta":
16
+ print(chunk.get("content", ""), end="", flush=True)
17
+
18
+ Async example::
19
+
20
+ import httpx
21
+ from fetch_hive_sdk.streaming import aiter_sse
22
+
23
+ async with httpx.AsyncClient() as client:
24
+ async with client.stream("POST", url, headers=headers, json=body) as response:
25
+ async for chunk in aiter_sse(response):
26
+ if chunk.get("type") == "delta":
27
+ print(chunk.get("content", ""), end="", flush=True)
28
+ """
29
+
30
+ from __future__ import annotations
31
+
32
+ import json
33
+ from typing import Any, AsyncIterator, Generator, Iterator
34
+
35
+ import httpx
36
+
37
+
38
+ def _parse_line(line: str) -> dict[str, Any] | None:
39
+ """Parse a single SSE data line. Returns None for non-data lines or [DONE]."""
40
+ if not line.startswith("data: "):
41
+ return None
42
+ payload = line[6:]
43
+ if payload.strip() == "[DONE]":
44
+ return None
45
+ try:
46
+ return json.loads(payload)
47
+ except json.JSONDecodeError:
48
+ return None
49
+
50
+
51
+ def iter_sse(response: httpx.Response) -> Generator[dict[str, Any], None, None]:
52
+ """
53
+ Synchronous generator that yields parsed SSE events from an httpx streaming
54
+ response.
55
+
56
+ Stops at ``data: [DONE]``.
57
+ """
58
+ response.raise_for_status()
59
+ buf = ""
60
+ for chunk in response.iter_text():
61
+ buf += chunk
62
+ while "\n" in buf:
63
+ line, buf = buf.split("\n", 1)
64
+ line = line.rstrip("\r")
65
+ event = _parse_line(line)
66
+ if event is None and line.strip() == "data: [DONE]":
67
+ return
68
+ if event is not None:
69
+ yield event
70
+
71
+
72
+ async def aiter_sse(response: httpx.Response) -> AsyncIterator[dict[str, Any]]:
73
+ """
74
+ Asynchronous generator that yields parsed SSE events from an httpx streaming
75
+ response.
76
+
77
+ Stops at ``data: [DONE]``.
78
+ """
79
+ response.raise_for_status()
80
+ buf = ""
81
+ async for chunk in response.aiter_text():
82
+ buf += chunk
83
+ while "\n" in buf:
84
+ line, buf = buf.split("\n", 1)
85
+ line = line.rstrip("\r")
86
+ if line.strip() == "data: [DONE]":
87
+ return
88
+ event = _parse_line(line)
89
+ if event is not None:
90
+ yield event
@@ -0,0 +1,147 @@
1
+ Metadata-Version: 2.4
2
+ Name: fetch-hive-sdk
3
+ Version: 0.1.0
4
+ Summary: Official Python SDK for the Fetch Hive API
5
+ Project-URL: Homepage, https://fetchhive.com
6
+ Project-URL: Repository, https://github.com/Fetch-Hive/python-sdk
7
+ Project-URL: Documentation, https://docs.fetchhive.com
8
+ Author-email: Fetch Hive <hello@fetchhive.com>
9
+ License: MIT
10
+ Keywords: agents,ai,fetchhive,sdk,workflows
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.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Requires-Python: >=3.9
20
+ Requires-Dist: httpx>=0.25.0
21
+ Provides-Extra: dev
22
+ Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
23
+ Requires-Dist: pytest>=7.0; extra == 'dev'
24
+ Requires-Dist: respx>=0.20; extra == 'dev'
25
+ Description-Content-Type: text/markdown
26
+
27
+ # fetch-hive-sdk
28
+
29
+ Official Python SDK for [Fetch Hive](https://fetchhive.com) — invoke AI prompts, workflows, and agents from your application.
30
+
31
+ [![PyPI version](https://badge.fury.io/py/fetch-hive-sdk.svg)](https://pypi.org/project/fetch-hive-sdk/)
32
+
33
+ ## Installation
34
+
35
+ ```bash
36
+ pip install fetch-hive-sdk
37
+ ```
38
+
39
+ ## Quick start
40
+
41
+ ```python
42
+ from fetch_hive_sdk import FetchHive
43
+
44
+ client = FetchHive(api_key="fhk_...")
45
+ # or: client = FetchHive() # reads FETCH_HIVE_API_KEY env var
46
+ ```
47
+
48
+ Get your API key from the [Fetch Hive dashboard](https://app.fetchhive.com).
49
+
50
+ ## Invoke a prompt
51
+
52
+ ```python
53
+ result = client.invoke_prompt(
54
+ deployment="my-prompt",
55
+ inputs={"name": "Alice", "topic": "machine learning"},
56
+ )
57
+ print(result["response"])
58
+ ```
59
+
60
+ ## Invoke a prompt (streaming)
61
+
62
+ ```python
63
+ for chunk in client.invoke_prompt_stream(
64
+ deployment="my-prompt",
65
+ inputs={"name": "Alice"},
66
+ ):
67
+ if chunk.get("type") == "delta":
68
+ print(chunk.get("content", ""), end="", flush=True)
69
+ ```
70
+
71
+ ## Invoke a workflow
72
+
73
+ ```python
74
+ run = client.invoke_workflow(
75
+ deployment="my-workflow",
76
+ inputs={"customer_id": "42"},
77
+ )
78
+ print(run["status"], run.get("output"))
79
+ ```
80
+
81
+ ## Async workflow
82
+
83
+ ```python
84
+ run = client.invoke_workflow(
85
+ deployment="my-workflow",
86
+ inputs={"customer_id": "42"},
87
+ async_mode=True,
88
+ callback_url="https://example.com/webhook",
89
+ )
90
+ print("Queued:", run["run_id"])
91
+ ```
92
+
93
+ ## Invoke an agent (streaming)
94
+
95
+ ```python
96
+ for chunk in client.invoke_agent_stream(
97
+ agent="my-agent",
98
+ message="What is the weather in London?",
99
+ thread_id="session-abc123", # optional — persist conversation history
100
+ ):
101
+ if chunk.get("type") == "delta":
102
+ print(chunk.get("content", ""), end="", flush=True)
103
+ elif chunk.get("type") == "tool_start":
104
+ print(f"\n[Calling tool: {chunk.get('tool_name')}]")
105
+ ```
106
+
107
+ ## Async streaming
108
+
109
+ ```python
110
+ import asyncio
111
+
112
+ async def main():
113
+ async for chunk in client.ainvoke_agent_stream(
114
+ agent="my-agent",
115
+ message="Hello",
116
+ ):
117
+ if chunk.get("type") == "delta":
118
+ print(chunk.get("content", ""), end="", flush=True)
119
+
120
+ asyncio.run(main())
121
+ ```
122
+
123
+ ## Multimodal (image) inputs
124
+
125
+ ```python
126
+ result = client.invoke_agent(
127
+ agent="vision-agent",
128
+ message="Describe this image",
129
+ image_urls=["https://example.com/photo.jpg"],
130
+ )
131
+ ```
132
+
133
+ ## Authentication
134
+
135
+ Pass the API key to the constructor or set the `FETCH_HIVE_API_KEY` environment variable:
136
+
137
+ ```bash
138
+ export FETCH_HIVE_API_KEY=fhk_...
139
+ ```
140
+
141
+ ## Version
142
+
143
+ 0.1.0
144
+
145
+ ## License
146
+
147
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,6 @@
1
+ fetch_hive_sdk/__init__.py,sha256=nFoBWeMW9G80tblzOglIBi8RSQxCyyyDstn7fmUbE6E,616
2
+ fetch_hive_sdk/client.py,sha256=OZVWuGLNZp-nqBYB2z3dAgyz3SKyk7QOloqxiyEm2_g,8815
3
+ fetch_hive_sdk/streaming.py,sha256=gdaF0pSYxKnD1-hdTNSynNi9o4KAktLgjB6_8ZbCsHs,2599
4
+ fetch_hive_sdk-0.1.0.dist-info/METADATA,sha256=7i7DjvMJg-pvKaYaiJyouFZ8cXYnKFXsJZQ9S5NFAGw,3561
5
+ fetch_hive_sdk-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
6
+ fetch_hive_sdk-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any