Skip to content

Commit e3c8565

Browse files
authored
Python SDK and related updates (#112)
* Scaffold for Orra Python SDK. * Core Python SDK now complete. * A more hardened decorator that includes agent registration. * Added runtime management to the SDK. * Added task handler input / output validation. * Updated the JS SDK to now initService and initAgent directly. * Renamed close to "shutdown". * Minor updates to the JS SDK and related conformance tests. * Python SDK conformance started with related SDK updates. * Can run conformance tests for a specific language. * Python connection resilience confirmation test added. * We now have python conformance tests for healthcheck, reconnection and message queuing. * Clean input output schema before registering a service or agent using the Python SDK. * Added exactly once Python SDK conformance tests. * Added service registration Python SDK conformance tests. * Removed debug logs from connection manager. * Ignore the package.json file as it always updated for every JS conformance test run. * Added large payload Python SDK conformance tests and enhanced the SDK to make it work. * Build and run test harness using docker when running conformance tests. * Ensured Python SDK logging can be configured using ENV VARs similar to the JS SDK. * Removed unnecessary print statement from test large payload conformance test. * Updated the Python conformance test run command to exit after the first encountered failure and generate correct result reporting. * Fix Python SDK shutdown and remove redundant run method and update the SDK Readme to how setup the an Orra Service or Agent. * Added Python SDK documentation. * Added the Python SDK echo example. * Bumped up the patch version number for the JS SDK. * New rapid agent app development doc.
1 parent 43bfba2 commit e3c8565

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+5491
-563
lines changed

README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ docker compose up --build
7979

8080
## Quick Start
8181

82-
Build your first AI-orchestrated application! We'll use our [Echo service](examples/echo) example to show you the
82+
Build your first AI-orchestrated application! We'll use our [Echo service](examples/echo-js) example to show you the
8383
magic of intelligent service orchestration.
8484

8585
While simple, it showcases Orra's capabilities:
@@ -210,7 +210,7 @@ orra verify run "Estimate delivery for customer order" \
210210
### 3. Explore Examples
211211

212212
- 🛒 [E-commerce AI Assistant](examples/ecommerce-agent-app) - E-commerce customer service with a delivery specialised agent
213-
- 📣 [Echo Service](examples/echo) - Simple example showing core concepts
213+
- 📣 [Echo Service](examples/echo-js) - Simple example showing core concepts
214214

215215
## Alpha Features & Limitations
216216

controlplane/validations.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ func validateSpec(spec *Spec) v.Schema {
1616
baseSchema := v.Schema{
1717
v.F("type", spec.Type): v.All(
1818
v.Nonzero[string]().Msg("type is required"),
19-
v.In("object", "string", "number", "boolean").Msg("invalid type"),
19+
v.In("object", "string", "number", "integer", "boolean").Msg("invalid type"),
2020
),
2121
}
2222

docs/advanced.md

+6-6
Original file line numberDiff line numberDiff line change
@@ -99,11 +99,11 @@ Example log sequence:
9999

100100
### Advanced Usage Patterns
101101

102-
#### 1. Dynamic Service Registration
102+
#### 1. Dynamic Service/Agent Registration
103103

104-
Services can update their capabilities at runtime:
104+
Services and Agents can update their capabilities at runtime:
105105
```javascript
106-
await client.registerService('ai-agent', {
106+
await agent.register('ai-agent', {
107107
schema: {
108108
input: {
109109
// New capability added
@@ -124,11 +124,11 @@ await client.registerService('ai-agent', {
124124
Control how service identity persists:
125125

126126
```javascript
127-
const client = createClient({
127+
const service = initService({
128128
persistenceOpts: {
129129
method: 'custom',
130-
customSave: async (serviceId) => {
131-
await redis.set(`service:${serviceId}`, serviceId);
130+
customSave: async (id) => {
131+
await redis.set(`service:${id}`, id);
132132
},
133133
customLoad: async () => {
134134
return await redis.get('service:id');

docs/cli.md

+5-4
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,17 @@ orra api-keys gen production-key
3636
Use the generated API key in your Node.js services:
3737

3838
```javascript
39-
import { createClient } from '@orra.dev/sdk';
39+
import { initService } from '@orra.dev/sdk';
4040

4141
// Initialize with the API key from step 1
42-
const client = createClient({
42+
const svc = initService({
43+
name: 'customer-service',
4344
orraUrl: process.env.ORRA_URL,
4445
orraKey: 'sk-orra-v1-xyz...' // From orra api-keys gen
4546
});
4647

4748
// Register your service
48-
await client.registerService('Customer Service', {
49+
await svc.register({
4950
description: 'Handles customer interactions',
5051
schema: {
5152
input: {
@@ -59,7 +60,7 @@ await client.registerService('Customer Service', {
5960
});
6061

6162
// Start handling tasks
62-
client.startHandler(async (task) => {
63+
svc.start(async (task) => {
6364
// Your service logic here
6465
});
6566
```

docs/rapid-agent-app-devlopment.md

+227
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
# Rapid Multi-Agent App Development with Orra
2+
3+
Stop wrestling with rigid agent frameworks. Orra lets you build production-ready multi-agent systems the way you actually work - starting simple and scaling naturally.
4+
5+
## Why Orra?
6+
7+
As AI engineers, we've all been there:
8+
- Struggling with inflexible agent frameworks that force specific patterns
9+
- Hitting walls when moving from prototype to production
10+
- Writing endless boilerplate for agent communication
11+
- Fighting with fragile orchestration code
12+
- Dealing with frameworks that assume all agents live in one process
13+
14+
Orra is different. It's built for AI engineers who need to:
15+
1. **Prototype Fast**: Start with everything in one file - just like you would naturally
16+
2. **Focus on Logic**: Write agents that do one thing well, Orra handles the rest
17+
3. **Stay Flexible**: No forced patterns, no rigid structures, no mandatory "crew" abstractions
18+
4. **Scale Naturally**: Same code works whether your agents are in one file or distributed globally
19+
5. **Ship Confidently**: Production-ready from day one - just split and deploy when ready
20+
21+
## The Orra Advantage
22+
23+
```python
24+
# This seemingly simple setup is incredibly powerful:
25+
researcher = OrraAgent(
26+
name="research-agent",
27+
description="Researches topics using web search and knowledge base"
28+
)
29+
30+
writer = OrraAgent(
31+
name="writing-agent",
32+
description="Crafts polished content from research"
33+
)
34+
35+
formatter = OrraService(
36+
name="format-service",
37+
description="Formats and validates final content"
38+
)
39+
```
40+
41+
Behind the scenes, Orra:
42+
- Dynamically figures out how agents should interact
43+
- Handles all messaging and retries
44+
- Manages state and failure recovery
45+
- Scales from local to distributed seamlessly
46+
- Requires zero orchestration code from you
47+
48+
Let's see how this works in practice...
49+
50+
## Quick Example
51+
52+
```python
53+
from orra import OrraService, OrraAgent, Task
54+
from pydantic import BaseModel, Field
55+
from typing import List
56+
import asyncio
57+
58+
# --- Models ---
59+
60+
class ResearchInput(BaseModel):
61+
topic: str
62+
depth: str = Field(default="medium", description="Research depth: basic, medium, deep")
63+
64+
class ResearchOutput(BaseModel):
65+
summary: str
66+
key_points: List[str]
67+
sources: List[str]
68+
69+
class WritingInput(BaseModel):
70+
content: str
71+
style: str = Field(default="professional")
72+
73+
class WritingOutput(BaseModel):
74+
text: str
75+
word_count: int
76+
77+
# --- Services & Agents ---
78+
79+
# Initialize with single API key
80+
researcher = OrraAgent(
81+
name="research-agent",
82+
description="Researches topics using web search and knowledge base",
83+
url="https://api.orra.dev",
84+
api_key="sk-orra-..."
85+
)
86+
87+
writer = OrraAgent(
88+
name="writing-agent",
89+
description="Crafts polished content from research",
90+
url="https://api.orra.dev",
91+
api_key="sk-orra-..." # Same key, Orra handles routing
92+
)
93+
94+
formatter = OrraService(
95+
name="format-service",
96+
description="Formats and validates final content",
97+
url="https://api.orra.dev",
98+
api_key="sk-orra-..."
99+
)
100+
101+
# --- Handlers ---
102+
103+
@researcher.handler()
104+
async def research(task: Task[ResearchInput]) -> ResearchOutput:
105+
"""Research handler using your preferred tools (Tavily, web search, etc)"""
106+
# Your research implementation
107+
research_result = await your_research_function(task.input.topic, task.input.depth)
108+
return ResearchOutput(
109+
summary=research_result.summary,
110+
key_points=research_result.points,
111+
sources=research_result.sources
112+
)
113+
114+
@writer.handler()
115+
async def write(task: Task[WritingInput]) -> WritingOutput:
116+
"""Writing handler using your LLM of choice"""
117+
# Your writing implementation
118+
written_content = await your_llm_function(task.input.content, task.input.style)
119+
return WritingOutput(
120+
text=written_content,
121+
word_count=len(written_content.split())
122+
)
123+
124+
@formatter.handler()
125+
async def format(task: Task[WritingInput]) -> WritingOutput:
126+
"""Formatting service - could be stateless language processing"""
127+
# Your formatting implementation
128+
formatted = await your_formatter(task.input.text)
129+
return WritingOutput(
130+
text=formatted,
131+
word_count=len(formatted.split())
132+
)
133+
134+
# --- Run Everything ---
135+
136+
async def main():
137+
# Start all services concurrently
138+
await asyncio.gather(
139+
researcher.start(),
140+
writer.start(),
141+
formatter.start()
142+
)
143+
144+
if __name__ == "__main__":
145+
asyncio.run(main())
146+
```
147+
148+
## Usage
149+
150+
1. Run your agents:
151+
```bash
152+
python agents.py
153+
```
154+
155+
2. Orchestrate with CLI:
156+
```bash
157+
# Research and write about AI
158+
orra verify run 'Research and write an article about AI trends' \
159+
--data topic:'AI in 2024' \
160+
--data style:'technical'
161+
```
162+
163+
Orra automatically:
164+
- Determines the optimal execution flow
165+
- Handles all agent communication
166+
- Manages retries and failures
167+
- Scales execution based on dependencies
168+
169+
## Best Practices
170+
171+
1. **Model Design**
172+
- Use Pydantic for validation
173+
- Clear field descriptions
174+
- Sensible defaults
175+
176+
2. **Error Handling**
177+
- Let agents handle retries
178+
- Throw on permanent failures
179+
- Orra manages recovery
180+
181+
3. **Development Flow**
182+
```
183+
Prototype (single file)
184+
→ Test & Refine
185+
→ Split Services
186+
→ Deploy
187+
```
188+
189+
## Going to Production
190+
191+
When ready, split into separate services:
192+
```plaintext
193+
services/
194+
├── researcher/
195+
│ └── main.py # Just researcher code
196+
├── writer/
197+
│ └── main.py # Just writer code
198+
└── formatter/
199+
└── main.py # Just formatter code
200+
```
201+
202+
Orra orchestrates them the same way!
203+
204+
## Tips
205+
206+
1. **Development**
207+
- Use shared utilities
208+
- Test locally first
209+
- Monitor execution logs
210+
211+
2. **Debugging**
212+
```bash
213+
# Watch execution
214+
orra ps
215+
216+
# Inspect flow
217+
orra inspect -d <orchestration-id>
218+
219+
```
220+
221+
3. **Advanced Features**
222+
- Custom retries per agent
223+
- Stateful agents
224+
- Parallel execution
225+
- Webhook notifications
226+
227+
Need help? Check out our [examples](../examples) or join our Discord!

0 commit comments

Comments
 (0)