Skip to content

Commit f97d5f4

Browse files
authored
docs/add examples (#12)
* chore: add microservice architecture scaffolding Add Dockerfile, .dockerignore, .env.example, api/openapi.yaml, deploy/docker/docker-compose.yml, docs/architecture.md, and .github/workflows/docker.yml for GHCR build+push CI. * fix: include VERSION file in Docker build Docker build failed because pyproject.toml requires a VERSION file that was not copied into the build context. * fix: include README.md in Docker build context hatchling requires README.md at build time but it was not being copied into the builder stage. * fix: allow README.md in Docker build context .dockerignore *.md pattern was excluding README.md which pyproject.toml requires as the package readme. * docs: add examples directory with basic usage Add examples/basic/main.py demonstrating how to use the Python SDK to interact with hawk daemon. ---------
1 parent ff686f4 commit f97d5f4

8 files changed

Lines changed: 301 additions & 0 deletions

File tree

.dockerignore

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
.git
2+
.github
3+
.gitignore
4+
*.md
5+
!README.md
6+
.env
7+
.env.*
8+
Dockerfile
9+
.dockerignore
10+
docs/
11+
deploy/
12+
api/
13+
tests/
14+
__pycache__/
15+
*.pyc
16+
.venv/

.env.example

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# hawk-sdk-python environment variables — copy to .env and fill in
2+
# The hawk daemon must be running locally before using this SDK.
3+
HAWK_BASE_URL=http://127.0.0.1:4590
4+
HAWK_API_KEY=

.github/workflows/docker.yml

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
name: Docker
2+
3+
on:
4+
push:
5+
branches: [main]
6+
tags: ["v*"]
7+
pull_request:
8+
branches: [main]
9+
paths:
10+
- "Dockerfile"
11+
- "src/**"
12+
- "pyproject.toml"
13+
14+
permissions:
15+
contents: read
16+
packages: write
17+
18+
env:
19+
REGISTRY: ghcr.io
20+
IMAGE_NAME: graycodeai/hawk-sdk-python
21+
22+
jobs:
23+
build-and-push:
24+
runs-on: ubuntu-latest
25+
steps:
26+
- uses: actions/checkout@v4
27+
28+
- name: Set up Docker Buildx
29+
uses: docker/setup-buildx-action@v3
30+
31+
- name: Log in to GHCR
32+
if: github.event_name != 'pull_request'
33+
uses: docker/login-action@v3
34+
with:
35+
registry: ${{ env.REGISTRY }}
36+
username: ${{ github.actor }}
37+
password: ${{ secrets.GITHUB_TOKEN }}
38+
39+
- name: Docker metadata
40+
id: meta
41+
uses: docker/metadata-action@v5
42+
with:
43+
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
44+
tags: |
45+
type=ref,event=branch
46+
type=semver,pattern={{version}}
47+
type=semver,pattern={{major}}.{{minor}}
48+
type=sha,prefix=sha-
49+
50+
- name: Build and push
51+
uses: docker/build-push-action@v6
52+
with:
53+
context: .
54+
push: ${{ github.event_name != 'pull_request' }}
55+
tags: ${{ steps.meta.outputs.tags }}
56+
labels: ${{ steps.meta.outputs.labels }}
57+
cache-from: type=gha
58+
cache-to: type=gha,mode=max

Dockerfile

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
FROM python:3.12-slim AS builder
2+
3+
WORKDIR /build
4+
COPY pyproject.toml VERSION README.md ./
5+
RUN pip install --no-cache-dir build
6+
7+
COPY src/ src/
8+
RUN python -m build --wheel
9+
10+
FROM python:3.12-slim
11+
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates tini && \
12+
rm -rf /var/lib/apt/lists/* && \
13+
adduser --disabled-password --gecos "" --uid 1000 hawk
14+
15+
COPY --from=builder /build/dist/*.whl /tmp/
16+
RUN pip install --no-cache-dir /tmp/*.whl && rm -rf /tmp/*.whl
17+
18+
USER hawk
19+
WORKDIR /workspace
20+
ENTRYPOINT ["tini", "--"]
21+
CMD ["sleep", "infinity"]

api/openapi.yaml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
openapi: "3.1.0"
2+
info:
3+
title: hawk-sdk-python — Python SDK API Reference
4+
description: |
5+
Python SDK for the hawk daemon HTTP API. Provides sync and async clients,
6+
SSE streaming, tool decorator, and workflow builder.
7+
8+
This is a client SDK — it connects to the hawk daemon at http://localhost:4590.
9+
See hawk's api/openapi.yaml for the server-side contract.
10+
version: "0.1.0"
11+
license:
12+
name: MIT
13+
url: https://github.com/GrayCodeAI/hawk-sdk-python/blob/main/LICENSE
14+
contact:
15+
url: https://github.com/GrayCodeAI/hawk-sdk-python
16+
17+
servers:
18+
- url: http://localhost:4590
19+
description: Hawk daemon (managed by hawk, not this SDK)
20+
21+
x-sdk-api:
22+
package: hawk
23+
classes:
24+
- name: HawkClient
25+
description: Synchronous client
26+
constructor_args:
27+
base_url: string
28+
api_key: string
29+
methods:
30+
- health()
31+
- chat(message, **kwargs)
32+
- chat_stream(message, **kwargs)
33+
- list_sessions(limit, offset)
34+
- get_session(session_id)
35+
- delete_session(session_id)
36+
- get_session_messages(session_id, limit, offset)
37+
- stats()
38+
- name: AsyncHawkClient
39+
description: Asynchronous client (same methods with async/await)
40+
- name: Agent
41+
description: Higher-level conversation agent with session history
42+
- name: Workflow
43+
description: Multi-step workflow builder with retry config
44+
decorators:
45+
- name: tool
46+
description: Decorator to register a function as a hawk tool
47+
usage: "@tool()"

deploy/docker/docker-compose.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
name: hawk-sdk-python
2+
3+
services:
4+
hawk-sdk-python:
5+
build:
6+
context: ../../
7+
dockerfile: Dockerfile
8+
image: ghcr.io/graycodeai/hawk-sdk-python:dev
9+
env_file:
10+
- path: ../../.env.example
11+
required: false

docs/architecture.md

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
<div align="center">
2+
3+
# 🐍 hawk-sdk-python Architecture
4+
5+
**Python SDK for the Hawk Daemon API**
6+
7+
[![Python](https://img.shields.io/badge/Python-3.10+-3776AB?logo=python)](https://python.org/)
8+
[![Type](https://img.shields.io/badge/Type-SDK-blue)]()
9+
10+
</div>
11+
12+
---
13+
14+
## 🎯 Overview
15+
16+
Idiomatic Python client for the hawk daemon HTTP API. Provides both **sync** and **async** clients, SSE streaming, a tool decorator, agent abstraction, and workflow builder. Uses `httpx` for HTTP transport and `Pydantic` for response models.
17+
18+
---
19+
20+
## 🧱 Modules
21+
22+
```
23+
src/hawk/
24+
├── __init__.py 📤 Public exports
25+
├── client.py 🔌 HawkClient (sync) + AsyncHawkClient (async)
26+
├── types.py 📋 Pydantic models (ChatResponse, Session, Stats)
27+
├── errors.py ❌ HawkAPIError base + subclasses, parse_error()
28+
├── retry.py 🔄 RetryConfig, with_retry_sync(), with_retry()
29+
├── streaming.py 📡 StreamReader, AsyncStreamReader, StreamEvent
30+
├── agent.py 🤖 Agent (conversation history, async support)
31+
├── tools.py 🛠️ @tool() decorator, chat_with_tools()
32+
├── workflow.py 🔧 Workflow builder
33+
├── discovery.py 🔍 Auto-discover running hawk daemon on localhost
34+
├── memory_tools.py 🧠 Memory graph operations (yaad integration)
35+
├── evaluate.py 📊 Evaluation helpers
36+
└── tracing.py 📈 OpenTelemetry tracing support
37+
```
38+
39+
---
40+
41+
## 📤 Client Usage
42+
43+
```python
44+
from hawk import HawkClient, AsyncHawkClient
45+
46+
# 🔌 Sync client
47+
with HawkClient(base_url="http://localhost:4590", api_key="sk-...") as client:
48+
health = client.health()
49+
response = client.chat("list files in src/")
50+
print(response.response)
51+
52+
# 📡 Async client
53+
async with AsyncHawkClient() as client:
54+
async for event in client.chat_stream("explain this code"):
55+
print(event.data, end="", flush=True)
56+
57+
# 📋 Sessions
58+
sessions = client.list_sessions(limit=10)
59+
msgs = client.get_session_messages(session_id)
60+
client.delete_session(session_id)
61+
```
62+
63+
---
64+
65+
## 🛠️ Tool Decorator
66+
67+
```python
68+
from hawk import tool, HawkClient
69+
70+
@tool()
71+
def read_file(path: str) -> str:
72+
with open(path) as f:
73+
return f.read()
74+
75+
with HawkClient() as client:
76+
response = client.chat_with_tools("read config.json", tools=[read_file])
77+
```
78+
79+
---
80+
81+
## 🤖 Agent (Higher-Level)
82+
83+
```python
84+
from hawk import Agent
85+
86+
agent = Agent(base_url="http://localhost:4590")
87+
resp1 = agent.chat("refactor this function")
88+
resp2 = agent.chat("now add type hints") # continues same session
89+
```
90+
91+
---
92+
93+
## ❌ Error Handling
94+
95+
```python
96+
from hawk.errors import NotFoundError, RateLimitError
97+
98+
try:
99+
response = client.chat("...")
100+
except RateLimitError as e:
101+
time.sleep(e.retry_after or 1)
102+
except NotFoundError:
103+
...
104+
```
105+
106+
| Error Class | HTTP Status |
107+
|-------------|:-----------:|
108+
| `NotFoundError` | 404 |
109+
| `RateLimitError` | 429 |
110+
| `InternalServerError` | 500 |
111+
112+
---
113+
114+
## 🔄 Retry & Streaming
115+
116+
| Feature | Behavior |
117+
|---------|----------|
118+
| **Auto-retry** | 429, 500, 502, 503, 504 with exponential backoff + jitter |
119+
| **Retry-After: 0** | Valid — don't retry immediately |
120+
| **Dual client** | Every method on both `HawkClient` and `AsyncHawkClient` |

examples/basic/main.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#!/usr/bin/env python3
2+
"""Basic example of using the Hawk SDK."""
3+
4+
from hawk import HawkClient
5+
6+
def main():
7+
with HawkClient() as client:
8+
# Health check
9+
health = client.health()
10+
print(f"Hawk daemon: version={health.version}, sessions={health.sessions}")
11+
12+
# Chat
13+
response = client.chat("Explain what a decorator is in Python")
14+
print(f"Response: {response.response}")
15+
16+
# Streaming
17+
print("\nStreaming response:")
18+
with client.chat_stream("Write a haiku about code") as stream:
19+
for event in stream.events():
20+
print(event.data, end="", flush=True)
21+
print()
22+
23+
if __name__ == "__main__":
24+
main()

0 commit comments

Comments
 (0)