Skip to content

Commit 654bc65

Browse files
committed
feat: initial Python SDK for hawk daemon API
Full-featured Python SDK with sync + async clients (httpx), Pydantic models, typed error hierarchy, auto-retry with exponential backoff, SSE stream readers with collect helpers, @tool decorator and chat_with_tools execution loop, workflow engine with fluent builder, and Agent abstraction with conversation history. Includes 65 passing tests across 6 modules.
0 parents  commit 654bc65

37 files changed

Lines changed: 2969 additions & 0 deletions

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2024 Hawk Contributors
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
# hawk-sdk
2+
3+
Python SDK for the [Hawk](https://github.com/hawk-eco/hawk) daemon API.
4+
5+
## Installation
6+
7+
```bash
8+
pip install hawk-sdk
9+
```
10+
11+
## Quick Start
12+
13+
```python
14+
from hawk import HawkClient
15+
16+
with HawkClient() as client:
17+
# Check health
18+
health = client.health()
19+
print(f"Status: {health.status}, Version: {health.version}")
20+
21+
# Chat
22+
response = client.chat("Explain async/await in Python")
23+
print(response.response)
24+
```
25+
26+
## Async Usage
27+
28+
```python
29+
import asyncio
30+
from hawk import AsyncHawkClient
31+
32+
async def main():
33+
async with AsyncHawkClient() as client:
34+
response = await client.chat("Hello!")
35+
print(response.response)
36+
37+
asyncio.run(main())
38+
```
39+
40+
## Streaming
41+
42+
```python
43+
from hawk import HawkClient
44+
45+
with HawkClient() as client:
46+
with client.chat_stream("Write a haiku") as stream:
47+
for event in stream.events():
48+
print(event.data, end="", flush=True)
49+
print()
50+
```
51+
52+
Or collect the full text:
53+
54+
```python
55+
with HawkClient() as client:
56+
with client.chat_stream("Write a haiku") as stream:
57+
text = stream.collect_text()
58+
print(text)
59+
```
60+
61+
## Tools
62+
63+
```python
64+
from hawk import HawkClient, Tool, tool, chat_with_tools
65+
66+
@tool(
67+
name="get_weather",
68+
description="Get current weather for a location",
69+
parameters={
70+
"type": "object",
71+
"properties": {"location": {"type": "string"}},
72+
"required": ["location"],
73+
},
74+
)
75+
def get_weather(location: str) -> str:
76+
return f"Sunny, 72F in {location}"
77+
78+
with HawkClient() as client:
79+
response = chat_with_tools(
80+
client,
81+
"What's the weather in NYC?",
82+
tools=[get_weather],
83+
)
84+
print(response.response)
85+
```
86+
87+
## Workflow
88+
89+
```python
90+
from hawk import Workflow
91+
from hawk.retry import RetryConfig
92+
93+
def fetch(url: str) -> str:
94+
import httpx
95+
return httpx.get(url).text
96+
97+
def summarize(text: str) -> str:
98+
# Process the text
99+
return text[:100]
100+
101+
wf = (
102+
Workflow("fetch-and-summarize")
103+
.step("fetch", fetch, timeout=10.0)
104+
.step("summarize", summarize, retry=RetryConfig(max_retries=2))
105+
.build()
106+
)
107+
108+
result = wf.run("https://example.com")
109+
```
110+
111+
## Agent
112+
113+
```python
114+
from hawk import HawkClient, Agent, AgentConfig, Tool
115+
116+
weather_tool = Tool(
117+
name="get_weather",
118+
description="Get weather",
119+
parameters={"type": "object", "properties": {"location": {"type": "string"}}},
120+
fn=lambda location: f"72F in {location}",
121+
)
122+
123+
with HawkClient() as client:
124+
agent = Agent(client, AgentConfig(
125+
name="weather-bot",
126+
model="claude-sonnet-4-20250514",
127+
tools=[weather_tool],
128+
))
129+
130+
response = agent.chat("What's the weather in San Francisco?")
131+
print(response.response)
132+
133+
# Conversation history is maintained
134+
response = agent.chat("What about New York?")
135+
print(response.response)
136+
```
137+
138+
## Sessions
139+
140+
```python
141+
from hawk import HawkClient
142+
143+
with HawkClient() as client:
144+
# List sessions
145+
sessions = client.list_sessions(limit=10)
146+
for s in sessions.data:
147+
print(f"{s.id}: {s.turns} turns")
148+
149+
# Get session details
150+
detail = client.get_session("session-id")
151+
print(f"Model: {detail.model}, Messages: {detail.message_count}")
152+
153+
# Get messages
154+
messages = client.list_messages("session-id")
155+
for m in messages.data:
156+
print(f"[{m.role}] {m.content}")
157+
158+
# Delete session
159+
client.delete_session("session-id")
160+
```
161+
162+
## Configuration
163+
164+
```python
165+
from hawk import HawkClient, RetryConfig
166+
167+
client = HawkClient(
168+
base_url="http://localhost:4590", # Daemon URL
169+
api_key="sk-...", # Optional API key
170+
timeout=60.0, # Request timeout in seconds
171+
retry_config=RetryConfig(
172+
max_retries=5,
173+
initial_backoff=1.0,
174+
max_backoff=60.0,
175+
),
176+
)
177+
```
178+
179+
## Error Handling
180+
181+
```python
182+
from hawk import HawkClient, NotFoundError, RateLimitError, HawkAPIError
183+
184+
with HawkClient() as client:
185+
try:
186+
session = client.get_session("nonexistent")
187+
except NotFoundError as e:
188+
print(f"Not found: {e.message}")
189+
except RateLimitError as e:
190+
print(f"Rate limited, retry after {e.retry_after}s")
191+
except HawkAPIError as e:
192+
print(f"API error {e.status_code}: {e.message}")
193+
```
194+
195+
## Development
196+
197+
```bash
198+
pip install -e ".[dev]"
199+
pytest
200+
```
201+
202+
## License
203+
204+
MIT

pyproject.toml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
[build-system]
2+
requires = ["hatchling"]
3+
build-backend = "hatchling.build"
4+
5+
[project]
6+
name = "hawk-sdk"
7+
version = "0.1.0"
8+
description = "Python SDK for the Hawk daemon API"
9+
readme = "README.md"
10+
license = "MIT"
11+
requires-python = ">=3.9"
12+
authors = [
13+
{ name = "Hawk Contributors" },
14+
]
15+
keywords = ["hawk", "sdk", "ai", "agent"]
16+
classifiers = [
17+
"Development Status :: 3 - Alpha",
18+
"Intended Audience :: Developers",
19+
"License :: OSI Approved :: MIT License",
20+
"Programming Language :: Python :: 3",
21+
"Programming Language :: Python :: 3.9",
22+
"Programming Language :: Python :: 3.10",
23+
"Programming Language :: Python :: 3.11",
24+
"Programming Language :: Python :: 3.12",
25+
"Programming Language :: Python :: 3.13",
26+
"Typing :: Typed",
27+
]
28+
dependencies = [
29+
"httpx>=0.25.0",
30+
"pydantic>=2.0.0",
31+
]
32+
33+
[project.optional-dependencies]
34+
dev = [
35+
"pytest>=7.0",
36+
"pytest-asyncio>=0.21",
37+
"respx>=0.21",
38+
]
39+
40+
[tool.hatch.build.targets.wheel]
41+
packages = ["src/hawk"]
42+
43+
[tool.pytest.ini_options]
44+
asyncio_mode = "auto"
45+
testpaths = ["tests"]

src/hawk/__init__.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
"""Hawk SDK — Python client for the Hawk daemon API."""
2+
3+
from ._version import __version__
4+
from .agent import Agent, AgentConfig, AsyncAgent
5+
from .client import AsyncHawkClient, HawkClient
6+
from .errors import (
7+
AuthenticationError,
8+
BadRequestError,
9+
ForbiddenError,
10+
HawkAPIError,
11+
InternalServerError,
12+
NotFoundError,
13+
RateLimitError,
14+
ServiceUnavailableError,
15+
)
16+
from .retry import DEFAULT_RETRY_CONFIG, RetryConfig
17+
from .streaming import AsyncStreamReader, StreamReader
18+
from .tools import Tool, chat_with_tools, chat_with_tools_async, tool
19+
from .types import (
20+
ChatRequest,
21+
ChatResponse,
22+
HealthResponse,
23+
Message,
24+
ModelStat,
25+
PaginatedResponse,
26+
SessionDetail,
27+
SessionSummary,
28+
StatsResponse,
29+
StreamEvent,
30+
StreamEventType,
31+
ToolCall,
32+
Usage,
33+
)
34+
from .workflow import AsyncWorkflow, Workflow
35+
36+
__all__ = [
37+
# Version
38+
"__version__",
39+
# Client
40+
"HawkClient",
41+
"AsyncHawkClient",
42+
# Agent
43+
"Agent",
44+
"AsyncAgent",
45+
"AgentConfig",
46+
# Streaming
47+
"StreamReader",
48+
"AsyncStreamReader",
49+
# Tools
50+
"Tool",
51+
"tool",
52+
"chat_with_tools",
53+
"chat_with_tools_async",
54+
# Workflow
55+
"Workflow",
56+
"AsyncWorkflow",
57+
# Retry
58+
"RetryConfig",
59+
"DEFAULT_RETRY_CONFIG",
60+
# Types
61+
"ChatRequest",
62+
"ChatResponse",
63+
"HealthResponse",
64+
"Message",
65+
"ModelStat",
66+
"PaginatedResponse",
67+
"SessionDetail",
68+
"SessionSummary",
69+
"StatsResponse",
70+
"StreamEvent",
71+
"StreamEventType",
72+
"ToolCall",
73+
"Usage",
74+
# Errors
75+
"HawkAPIError",
76+
"BadRequestError",
77+
"AuthenticationError",
78+
"ForbiddenError",
79+
"NotFoundError",
80+
"RateLimitError",
81+
"InternalServerError",
82+
"ServiceUnavailableError",
83+
]
1.92 KB
Binary file not shown.
274 Bytes
Binary file not shown.
8.25 KB
Binary file not shown.
20.3 KB
Binary file not shown.
7.16 KB
Binary file not shown.
5.26 KB
Binary file not shown.

0 commit comments

Comments
 (0)