memra 0.2.3__py3-none-any.whl → 0.2.5__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.
Files changed (70) hide show
  1. memra/__init__.py +16 -2
  2. memra/cli.py +286 -0
  3. memra/execution.py +222 -41
  4. memra/models.py +1 -0
  5. memra/tool_registry.py +8 -0
  6. memra/tool_registry_client.py +2 -2
  7. memra-0.2.5.dist-info/METADATA +319 -0
  8. memra-0.2.5.dist-info/RECORD +68 -0
  9. memra-0.2.5.dist-info/entry_points.txt +2 -0
  10. memra-0.2.5.dist-info/licenses/LICENSE +21 -0
  11. memra-0.2.5.dist-info/top_level.txt +4 -0
  12. memra-ops/app.py +808 -0
  13. memra-ops/config/config.py +25 -0
  14. memra-ops/config.py +34 -0
  15. memra-ops/logic/__init__.py +1 -0
  16. memra-ops/logic/file_tools.py +43 -0
  17. memra-ops/logic/invoice_tools.py +668 -0
  18. memra-ops/logic/invoice_tools_fix.py +66 -0
  19. memra-ops/mcp_bridge_server.py +1178 -0
  20. memra-ops/scripts/check_database.py +37 -0
  21. memra-ops/scripts/clear_database.py +48 -0
  22. memra-ops/scripts/monitor_database.py +67 -0
  23. memra-ops/scripts/release.py +133 -0
  24. memra-ops/scripts/reset_database.py +65 -0
  25. memra-ops/scripts/start_memra.py +334 -0
  26. memra-ops/scripts/stop_memra.py +132 -0
  27. memra-ops/server_tool_registry.py +190 -0
  28. memra-ops/tests/test_llm_text_to_sql.py +115 -0
  29. memra-ops/tests/test_llm_vs_pattern.py +130 -0
  30. memra-ops/tests/test_mcp_schema_aware.py +124 -0
  31. memra-ops/tests/test_schema_aware_sql.py +139 -0
  32. memra-ops/tests/test_schema_aware_sql_simple.py +66 -0
  33. memra-ops/tests/test_text_to_sql_demo.py +140 -0
  34. memra-ops/tools/mcp_bridge_server.py +851 -0
  35. memra-sdk/examples/accounts_payable.py +215 -0
  36. memra-sdk/examples/accounts_payable_client.py +217 -0
  37. memra-sdk/examples/accounts_payable_mcp.py +200 -0
  38. memra-sdk/examples/ask_questions.py +123 -0
  39. memra-sdk/examples/invoice_processing.py +116 -0
  40. memra-sdk/examples/propane_delivery.py +87 -0
  41. memra-sdk/examples/simple_text_to_sql.py +158 -0
  42. memra-sdk/memra/__init__.py +31 -0
  43. memra-sdk/memra/discovery.py +15 -0
  44. memra-sdk/memra/discovery_client.py +49 -0
  45. memra-sdk/memra/execution.py +481 -0
  46. memra-sdk/memra/models.py +99 -0
  47. memra-sdk/memra/tool_registry.py +343 -0
  48. memra-sdk/memra/tool_registry_client.py +106 -0
  49. memra-sdk/scripts/release.py +133 -0
  50. memra-sdk/setup.py +52 -0
  51. memra-workflows/accounts_payable/accounts_payable.py +215 -0
  52. memra-workflows/accounts_payable/accounts_payable_client.py +216 -0
  53. memra-workflows/accounts_payable/accounts_payable_mcp.py +200 -0
  54. memra-workflows/accounts_payable/accounts_payable_smart.py +221 -0
  55. memra-workflows/invoice_processing/invoice_processing.py +116 -0
  56. memra-workflows/invoice_processing/smart_invoice_processor.py +220 -0
  57. memra-workflows/logic/__init__.py +1 -0
  58. memra-workflows/logic/file_tools.py +50 -0
  59. memra-workflows/logic/invoice_tools.py +501 -0
  60. memra-workflows/logic/propane_agents.py +52 -0
  61. memra-workflows/mcp_bridge_server.py +230 -0
  62. memra-workflows/propane_delivery/propane_delivery.py +87 -0
  63. memra-workflows/text_to_sql/complete_invoice_workflow_with_queries.py +208 -0
  64. memra-workflows/text_to_sql/complete_text_to_sql_system.py +266 -0
  65. memra-workflows/text_to_sql/file_discovery_demo.py +156 -0
  66. memra-0.2.3.dist-info/METADATA +0 -101
  67. memra-0.2.3.dist-info/RECORD +0 -12
  68. memra-0.2.3.dist-info/entry_points.txt +0 -2
  69. memra-0.2.3.dist-info/top_level.txt +0 -1
  70. {memra-0.2.3.dist-info → memra-0.2.5.dist-info}/WHEEL +0 -0
memra-ops/app.py ADDED
@@ -0,0 +1,808 @@
1
+ from fastapi import FastAPI, HTTPException, Depends, Header
2
+ from fastapi.responses import HTMLResponse
3
+ from pydantic import BaseModel
4
+ from typing import Dict, Any, Optional, List
5
+ import importlib
6
+ import logging
7
+ import os
8
+ import sys
9
+ from pathlib import Path
10
+ import json
11
+ import asyncio
12
+ import hashlib
13
+ import hmac
14
+ import aiohttp
15
+ import base64
16
+ import uuid
17
+ from datetime import datetime, timedelta
18
+ from server_tool_registry import ServerToolRegistry
19
+
20
+ # Add current directory to path for imports
21
+ sys.path.append(str(Path(__file__).parent))
22
+
23
+ # Configure logging
24
+ logging.basicConfig(level=logging.INFO)
25
+ logger = logging.getLogger(__name__)
26
+
27
+ app = FastAPI(
28
+ title="Memra Tool Execution API",
29
+ description="API for executing Memra workflow tools",
30
+ version="1.0.0"
31
+ )
32
+
33
+ # Initialize server-side tool registry
34
+ tool_registry = ServerToolRegistry()
35
+
36
+ # Request/Response models
37
+ class ToolExecutionRequest(BaseModel):
38
+ tool_name: str
39
+ hosted_by: str
40
+ input_data: Dict[str, Any]
41
+ config: Optional[Dict[str, Any]] = None
42
+
43
+ class ToolExecutionResponse(BaseModel):
44
+ success: bool
45
+ data: Optional[Dict[str, Any]] = None
46
+ error: Optional[str] = None
47
+
48
+ class ToolDiscoveryResponse(BaseModel):
49
+ tools: List[Dict[str, str]]
50
+
51
+ class FileUploadRequest(BaseModel):
52
+ filename: str
53
+ content: str # base64 encoded
54
+ content_type: str
55
+
56
+ class FileUploadResponse(BaseModel):
57
+ success: bool
58
+ data: Optional[dict] = None
59
+ error: Optional[str] = None
60
+
61
+ # Authentication
62
+ def verify_api_key(api_key: str) -> bool:
63
+ """Verify if the provided API key is valid"""
64
+ # Get valid keys from environment variable only - no defaults
65
+ valid_keys_str = os.getenv("MEMRA_API_KEYS")
66
+ if not valid_keys_str:
67
+ # If no keys are set, deny all access
68
+ return False
69
+
70
+ valid_keys = valid_keys_str.split(",")
71
+ return api_key.strip() in [key.strip() for key in valid_keys]
72
+
73
+ # FastAPI dependency for API key verification
74
+ async def get_api_key(x_api_key: Optional[str] = Header(None)):
75
+ """FastAPI dependency to verify API key from header"""
76
+ if not x_api_key:
77
+ raise HTTPException(
78
+ status_code=401,
79
+ detail="Missing API key. Please provide X-API-Key header."
80
+ )
81
+
82
+ if not verify_api_key(x_api_key):
83
+ raise HTTPException(
84
+ status_code=401,
85
+ detail="Invalid API key. Please contact info@memra.co for access."
86
+ )
87
+
88
+ logger.info(f"Valid API key used: {x_api_key}")
89
+ return x_api_key
90
+
91
+ # File upload configuration
92
+ UPLOAD_DIR = "/tmp/uploads"
93
+ FILE_EXPIRY_HOURS = 24
94
+
95
+ # Ensure upload directory exists
96
+ os.makedirs(UPLOAD_DIR, exist_ok=True)
97
+
98
+ @app.post("/upload", response_model=FileUploadResponse)
99
+ async def upload_file(
100
+ request: FileUploadRequest,
101
+ api_key: Optional[str] = Depends(get_api_key)
102
+ ):
103
+ """Upload a file to the server for processing"""
104
+ try:
105
+ # Validate file type
106
+ if not request.content_type.startswith("application/pdf"):
107
+ raise HTTPException(status_code=400, detail="Only PDF files are supported")
108
+
109
+ # Validate file size (50MB limit)
110
+ try:
111
+ file_content = base64.b64decode(request.content)
112
+ if len(file_content) > 50 * 1024 * 1024: # 50MB
113
+ raise HTTPException(status_code=400, detail="File too large (max 50MB)")
114
+ except Exception as e:
115
+ raise HTTPException(status_code=400, detail="Invalid base64 content")
116
+
117
+ # Generate unique filename
118
+ file_id = str(uuid.uuid4())
119
+ file_extension = os.path.splitext(request.filename)[1]
120
+ remote_filename = f"{file_id}{file_extension}"
121
+ remote_path = os.path.join(UPLOAD_DIR, remote_filename)
122
+
123
+ # Save file
124
+ with open(remote_path, 'wb') as f:
125
+ f.write(file_content)
126
+
127
+ # Calculate expiry time
128
+ expires_at = datetime.utcnow() + timedelta(hours=FILE_EXPIRY_HOURS)
129
+
130
+ logger.info(f"File uploaded: {request.filename} -> {remote_filename}")
131
+
132
+ return FileUploadResponse(
133
+ success=True,
134
+ data={
135
+ "remote_path": f"/uploads/{remote_filename}",
136
+ "file_id": file_id,
137
+ "expires_at": expires_at.isoformat(),
138
+ "original_filename": request.filename
139
+ }
140
+ )
141
+
142
+ except HTTPException:
143
+ raise
144
+ except Exception as e:
145
+ logger.error(f"Upload failed: {str(e)}")
146
+ return FileUploadResponse(
147
+ success=False,
148
+ error=f"Upload failed: {str(e)}"
149
+ )
150
+
151
+ async def cleanup_expired_files():
152
+ """Remove files older than FILE_EXPIRY_HOURS"""
153
+ while True:
154
+ try:
155
+ current_time = datetime.utcnow()
156
+ for filename in os.listdir(UPLOAD_DIR):
157
+ file_path = os.path.join(UPLOAD_DIR, filename)
158
+ file_time = datetime.fromtimestamp(os.path.getctime(file_path))
159
+
160
+ if current_time - file_time > timedelta(hours=FILE_EXPIRY_HOURS):
161
+ try:
162
+ os.remove(file_path)
163
+ logger.info(f"Cleaned up expired file: {filename}")
164
+ except Exception as e:
165
+ logger.error(f"Failed to clean up {filename}: {e}")
166
+
167
+ except Exception as e:
168
+ logger.error(f"File cleanup error: {e}")
169
+
170
+ await asyncio.sleep(3600) # Run every hour
171
+
172
+ @app.on_event("startup")
173
+ async def start_cleanup():
174
+ asyncio.create_task(cleanup_expired_files())
175
+
176
+ @app.get("/", response_class=HTMLResponse)
177
+ async def landing_page():
178
+ """API documentation landing page"""
179
+ return """
180
+ <!DOCTYPE html>
181
+ <html lang="en">
182
+ <head>
183
+ <meta charset="UTF-8">
184
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
185
+ <title>memra API - Declarative AI Workflows</title>
186
+ <style>
187
+ @import url('https://fonts.cdnfonts.com/css/effra');
188
+
189
+ * {
190
+ margin: 0;
191
+ padding: 0;
192
+ box-sizing: border-box;
193
+ }
194
+
195
+ body {
196
+ font-family: 'Effra', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
197
+ line-height: 1.6;
198
+ color: #ffffff;
199
+ background: #0f0f23;
200
+ min-height: 100vh;
201
+ overflow-x: hidden;
202
+ }
203
+
204
+ /* Animated gradient background */
205
+ .bg-gradient {
206
+ position: fixed;
207
+ top: 0;
208
+ left: 0;
209
+ right: 0;
210
+ bottom: 0;
211
+ background: radial-gradient(ellipse at top left, rgba(88, 28, 135, 0.8) 0%, transparent 40%),
212
+ radial-gradient(ellipse at bottom right, rgba(59, 130, 246, 0.6) 0%, transparent 40%),
213
+ radial-gradient(ellipse at center, rgba(147, 51, 234, 0.4) 0%, transparent 60%),
214
+ #1a1a2e;
215
+ z-index: -2;
216
+ }
217
+
218
+ /* Navigation */
219
+ nav {
220
+ position: fixed;
221
+ top: 0;
222
+ left: 0;
223
+ right: 0;
224
+ background: rgba(15, 15, 35, 0.7);
225
+ backdrop-filter: blur(20px);
226
+ border-bottom: 1px solid rgba(255, 255, 255, 0.08);
227
+ z-index: 1000;
228
+ padding: 1.5rem 2rem;
229
+ }
230
+
231
+ .nav-content {
232
+ max-width: 1400px;
233
+ margin: 0 auto;
234
+ display: flex;
235
+ justify-content: space-between;
236
+ align-items: center;
237
+ }
238
+
239
+ .logo {
240
+ font-size: 1.75rem;
241
+ font-weight: 300;
242
+ color: #ffffff;
243
+ letter-spacing: -0.02em;
244
+ }
245
+
246
+ .status-badge {
247
+ background: rgba(34, 197, 94, 0.15);
248
+ border: 1px solid rgba(34, 197, 94, 0.3);
249
+ color: #22c55e;
250
+ padding: 0.4rem 1rem;
251
+ border-radius: 999px;
252
+ font-size: 0.875rem;
253
+ font-weight: 400;
254
+ animation: pulse 3s ease-in-out infinite;
255
+ }
256
+
257
+ @keyframes pulse {
258
+ 0%, 100% { opacity: 1; }
259
+ 50% { opacity: 0.7; }
260
+ }
261
+
262
+ /* Main content */
263
+ .container {
264
+ max-width: 1200px;
265
+ margin: 0 auto;
266
+ padding: 10rem 2rem 4rem;
267
+ }
268
+
269
+ /* Hero section */
270
+ .hero {
271
+ text-align: center;
272
+ margin-bottom: 8rem;
273
+ }
274
+
275
+ .hero h1 {
276
+ font-size: clamp(2.5rem, 6vw, 4rem);
277
+ font-weight: 300;
278
+ margin-bottom: 1.5rem;
279
+ color: #ffffff;
280
+ letter-spacing: -0.02em;
281
+ }
282
+
283
+ .hero .tagline {
284
+ font-size: 1.25rem;
285
+ color: rgba(255, 255, 255, 0.6);
286
+ margin-bottom: 4rem;
287
+ font-weight: 300;
288
+ max-width: 600px;
289
+ margin-left: auto;
290
+ margin-right: auto;
291
+ }
292
+
293
+ /* Glass card effect */
294
+ .glass-card {
295
+ background: rgba(255, 255, 255, 0.03);
296
+ backdrop-filter: blur(20px);
297
+ border: 1px solid rgba(255, 255, 255, 0.08);
298
+ border-radius: 20px;
299
+ padding: 2.5rem;
300
+ margin-bottom: 2rem;
301
+ transition: all 0.3s ease;
302
+ }
303
+
304
+ .glass-card:hover {
305
+ background: rgba(255, 255, 255, 0.05);
306
+ border-color: rgba(255, 255, 255, 0.12);
307
+ }
308
+
309
+ /* Code blocks */
310
+ .code-block {
311
+ background: rgba(0, 0, 0, 0.5);
312
+ border: 1px solid rgba(255, 255, 255, 0.1);
313
+ border-radius: 16px;
314
+ padding: 1.5rem;
315
+ font-family: 'Monaco', 'Menlo', 'Courier New', monospace;
316
+ font-size: 0.9rem;
317
+ color: rgba(255, 255, 255, 0.9);
318
+ overflow-x: auto;
319
+ margin: 1.5rem 0;
320
+ }
321
+
322
+ /* Use cases grid */
323
+ .use-cases-grid {
324
+ display: grid;
325
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
326
+ gap: 1.5rem;
327
+ margin: 2rem 0 4rem 0;
328
+ }
329
+
330
+ .use-case-card {
331
+ background: rgba(147, 51, 234, 0.05);
332
+ border: 1px solid rgba(147, 51, 234, 0.2);
333
+ border-radius: 16px;
334
+ padding: 2rem;
335
+ transition: all 0.3s ease;
336
+ }
337
+
338
+ .use-case-card:hover {
339
+ background: rgba(147, 51, 234, 0.08);
340
+ border-color: rgba(147, 51, 234, 0.4);
341
+ transform: translateY(-2px);
342
+ }
343
+
344
+ .use-case-header {
345
+ display: flex;
346
+ align-items: center;
347
+ gap: 1rem;
348
+ margin-bottom: 1rem;
349
+ }
350
+
351
+ .use-case-icon {
352
+ font-size: 2rem;
353
+ }
354
+
355
+ .use-case-card h3 {
356
+ font-size: 1.25rem;
357
+ font-weight: 400;
358
+ color: #ffffff;
359
+ }
360
+
361
+ .use-case-card p {
362
+ color: rgba(255, 255, 255, 0.7);
363
+ margin-bottom: 1rem;
364
+ line-height: 1.6;
365
+ }
366
+
367
+ .code-snippet {
368
+ background: rgba(0, 0, 0, 0.3);
369
+ border-radius: 8px;
370
+ padding: 0.75rem;
371
+ font-size: 0.8rem;
372
+ overflow-x: auto;
373
+ }
374
+
375
+ .code-snippet code {
376
+ color: #a78bfa;
377
+ font-family: 'Monaco', 'Menlo', monospace;
378
+ }
379
+
380
+ /* Features grid */
381
+ .features-grid {
382
+ display: grid;
383
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
384
+ gap: 2rem;
385
+ margin: 3rem 0;
386
+ }
387
+
388
+ .feature-card {
389
+ background: rgba(255, 255, 255, 0.02);
390
+ border: 1px solid rgba(255, 255, 255, 0.06);
391
+ border-radius: 16px;
392
+ padding: 2.5rem;
393
+ text-align: center;
394
+ transition: all 0.3s ease;
395
+ }
396
+
397
+ .feature-card:hover {
398
+ background: rgba(255, 255, 255, 0.04);
399
+ border-color: rgba(147, 51, 234, 0.3);
400
+ transform: translateY(-4px);
401
+ }
402
+
403
+ .feature-icon {
404
+ font-size: 2.5rem;
405
+ margin-bottom: 1rem;
406
+ opacity: 0.8;
407
+ }
408
+
409
+ .feature-title {
410
+ font-size: 1.25rem;
411
+ font-weight: 400;
412
+ margin-bottom: 0.5rem;
413
+ color: #ffffff;
414
+ }
415
+
416
+ .feature-desc {
417
+ color: rgba(255, 255, 255, 0.6);
418
+ font-size: 0.95rem;
419
+ line-height: 1.5;
420
+ }
421
+
422
+ /* Endpoints section */
423
+ .endpoint {
424
+ background: rgba(0, 0, 0, 0.2);
425
+ border-left: 3px solid rgba(147, 51, 234, 0.5);
426
+ padding: 1.5rem;
427
+ margin: 1rem 0;
428
+ border-radius: 0 12px 12px 0;
429
+ transition: all 0.3s ease;
430
+ }
431
+
432
+ .endpoint:hover {
433
+ background: rgba(0, 0, 0, 0.3);
434
+ border-left-color: rgba(147, 51, 234, 0.8);
435
+ }
436
+
437
+ .method {
438
+ display: inline-block;
439
+ background: rgba(147, 51, 234, 0.15);
440
+ color: #a78bfa;
441
+ padding: 0.3rem 0.8rem;
442
+ border-radius: 8px;
443
+ font-weight: 500;
444
+ font-size: 0.875rem;
445
+ margin-right: 1rem;
446
+ }
447
+
448
+ /* CTA section */
449
+ .cta-section {
450
+ background: rgba(255, 255, 255, 0.02);
451
+ border: 1px solid rgba(255, 255, 255, 0.08);
452
+ border-radius: 24px;
453
+ padding: 4rem;
454
+ text-align: center;
455
+ margin: 4rem 0;
456
+ }
457
+
458
+ .cta-button {
459
+ display: inline-block;
460
+ background: #e879f9;
461
+ color: #0f0f23;
462
+ padding: 1rem 2.5rem;
463
+ border-radius: 12px;
464
+ text-decoration: none;
465
+ font-weight: 500;
466
+ transition: all 0.3s ease;
467
+ font-size: 1rem;
468
+ }
469
+
470
+ .cta-button:hover {
471
+ transform: translateY(-2px);
472
+ box-shadow: 0 10px 30px rgba(232, 121, 249, 0.3);
473
+ background: #f0abfc;
474
+ }
475
+
476
+ /* Section headers */
477
+ h2 {
478
+ font-size: 2rem;
479
+ font-weight: 300;
480
+ margin-bottom: 2rem;
481
+ color: #ffffff;
482
+ letter-spacing: -0.02em;
483
+ }
484
+
485
+ /* Accent text */
486
+ .accent {
487
+ color: #e879f9;
488
+ }
489
+
490
+ /* Warning box */
491
+ .warning-box {
492
+ background: rgba(251, 191, 36, 0.1);
493
+ border: 1px solid rgba(251, 191, 36, 0.3);
494
+ border-radius: 12px;
495
+ padding: 1rem 1.5rem;
496
+ margin: 1.5rem 0;
497
+ color: #fbbf24;
498
+ }
499
+
500
+ .warning-box strong {
501
+ color: #f59e0b;
502
+ }
503
+
504
+ /* Links */
505
+ a {
506
+ color: #a78bfa;
507
+ text-decoration: none;
508
+ transition: color 0.3s ease;
509
+ }
510
+
511
+ a:hover {
512
+ color: #e879f9;
513
+ }
514
+
515
+ /* Footer links */
516
+ .footer-links {
517
+ text-align: center;
518
+ margin-top: 6rem;
519
+ padding-top: 3rem;
520
+ border-top: 1px solid rgba(255, 255, 255, 0.08);
521
+ }
522
+
523
+ .footer-links a {
524
+ margin: 0 1.5rem;
525
+ color: rgba(255, 255, 255, 0.5);
526
+ font-size: 0.95rem;
527
+ }
528
+
529
+ .footer-links a:hover {
530
+ color: rgba(255, 255, 255, 0.8);
531
+ }
532
+
533
+ /* Responsive */
534
+ @media (max-width: 768px) {
535
+ .hero h1 {
536
+ font-size: 2.5rem;
537
+ }
538
+
539
+ .hero .tagline {
540
+ font-size: 1.1rem;
541
+ }
542
+
543
+ .container {
544
+ padding: 7rem 1.5rem 2rem;
545
+ }
546
+
547
+ .glass-card {
548
+ padding: 2rem;
549
+ }
550
+
551
+ .cta-section {
552
+ padding: 3rem 2rem;
553
+ }
554
+ }
555
+ </style>
556
+ </head>
557
+ <body>
558
+ <div class="bg-gradient"></div>
559
+
560
+ <nav>
561
+ <div class="nav-content">
562
+ <div class="logo">
563
+ <span>memra API</span>
564
+ </div>
565
+ <div class="status-badge">● API Running</div>
566
+ </div>
567
+ </nav>
568
+
569
+ <div class="container">
570
+ <section class="hero">
571
+ <h1>Build AI Agents That Actually Do Work</h1>
572
+ <p class="tagline">Stop writing boilerplate. Define what you want done, not how to do it. memra handles the AI orchestration so you can ship faster.</p>
573
+
574
+ <div class="glass-card">
575
+ <h2>🚀 Quick Start</h2>
576
+ <div class="code-block">pip install memra</div>
577
+ <div class="warning-box">
578
+ <strong>🔑 API Access Required:</strong>
579
+ Contact <a href="mailto:info@memra.co">info@memra.co</a> for early access to the memra API
580
+ </div>
581
+ <div style="margin-top: 2rem; padding-top: 2rem; border-top: 1px solid rgba(255, 255, 255, 0.1);">
582
+ <h3 style="font-size: 1.25rem; margin-bottom: 1rem; color: #e879f9;">Next Steps:</h3>
583
+ <ol style="color: rgba(255, 255, 255, 0.8); line-height: 2;">
584
+ <li>Install the SDK: <code style="background: rgba(0,0,0,0.3); padding: 0.2rem 0.5rem; border-radius: 4px;">pip install memra</code></li>
585
+ <li>Get your API key from <a href="mailto:info@memra.co">info@memra.co</a></li>
586
+ <li>Check out the examples below to see what you can build</li>
587
+ <li>Start with a simple agent and expand from there</li>
588
+ </ol>
589
+ </div>
590
+ </div>
591
+ </section>
592
+
593
+ <section>
594
+ <h2>🛠️ What Can You Build?</h2>
595
+ <div class="use-cases-grid">
596
+ <div class="use-case-card">
597
+ <div class="use-case-header">
598
+ <span class="use-case-icon">📄</span>
599
+ <h3>Document Processing Pipeline</h3>
600
+ </div>
601
+ <p>Auto-extract data from PDFs, invoices, contracts. Parse, validate, and push to your database.</p>
602
+ <div class="code-snippet">
603
+ <code>Agent(role="Invoice Parser", tools=["PDFProcessor", "DatabaseWriter"])</code>
604
+ </div>
605
+ </div>
606
+ <div class="use-case-card">
607
+ <div class="use-case-header">
608
+ <span class="use-case-icon">📧</span>
609
+ <h3>Customer Support Automation</h3>
610
+ </div>
611
+ <p>Handle support tickets, categorize issues, draft responses, and escalate complex cases.</p>
612
+ <div class="code-snippet">
613
+ <code>Agent(role="Support Analyst", tools=["EmailReader", "TicketClassifier"])</code>
614
+ </div>
615
+ </div>
616
+ <div class="use-case-card">
617
+ <div class="use-case-header">
618
+ <span class="use-case-icon">📊</span>
619
+ <h3>Data Analysis Workflows</h3>
620
+ </div>
621
+ <p>Connect to databases, run analysis, generate reports, and send insights to Slack.</p>
622
+ <div class="code-snippet">
623
+ <code>Agent(role="Data Analyst", tools=["SQLQuery", "ChartGenerator", "SlackNotifier"])</code>
624
+ </div>
625
+ </div>
626
+ <div class="use-case-card">
627
+ <div class="use-case-header">
628
+ <span class="use-case-icon">🔄</span>
629
+ <h3>API Integration Chains</h3>
630
+ </div>
631
+ <p>Chain multiple APIs together with AI decision-making between steps.</p>
632
+ <div class="code-snippet">
633
+ <code>Agent(role="Integration Expert", tools=["HTTPClient", "JSONTransformer"])</code>
634
+ </div>
635
+ </div>
636
+ <div class="use-case-card">
637
+ <div class="use-case-header">
638
+ <span class="use-case-icon">🔍</span>
639
+ <h3>Content Moderation Pipeline</h3>
640
+ </div>
641
+ <p>Review user content, flag issues, apply policies, and maintain compliance automatically.</p>
642
+ <div class="code-snippet">
643
+ <code>Agent(role="Content Reviewer", tools=["TextAnalyzer", "PolicyEngine", "FlagSystem"])</code>
644
+ </div>
645
+ </div>
646
+ <div class="use-case-card">
647
+ <div class="use-case-header">
648
+ <span class="use-case-icon">🚀</span>
649
+ <h3>Lead Qualification System</h3>
650
+ </div>
651
+ <p>Score leads, enrich data from multiple sources, and route to the right sales team.</p>
652
+ <div class="code-snippet">
653
+ <code>Agent(role="Lead Scorer", tools=["CRMConnector", "DataEnricher", "RouterAgent"])</code>
654
+ </div>
655
+ </div>
656
+ </div>
657
+ </section>
658
+
659
+ <section>
660
+ <h2>✨ Why <span class="accent">memra</span>?</h2>
661
+ <div class="features-grid">
662
+ <div class="feature-card">
663
+ <div class="feature-icon">📋</div>
664
+ <div class="feature-title">Declarative Design</div>
665
+ <div class="feature-desc">Define workflows like Kubernetes YAML. Version control your AI business logic.</div>
666
+ </div>
667
+ <div class="feature-card">
668
+ <div class="feature-icon">🤖</div>
669
+ <div class="feature-title">Multi-LLM Support</div>
670
+ <div class="feature-desc">Seamlessly integrate OpenAI, Anthropic, and other providers with zero config changes.</div>
671
+ </div>
672
+ <div class="feature-card">
673
+ <div class="feature-icon">💬</div>
674
+ <div class="feature-title">Self-Documenting</div>
675
+ <div class="feature-desc">Agents explain their decisions in natural language for full transparency.</div>
676
+ </div>
677
+ <div class="feature-card">
678
+ <div class="feature-icon">🏗️</div>
679
+ <div class="feature-title">Enterprise Ready</div>
680
+ <div class="feature-desc">Battle-tested on real databases, files, and complex business workflows.</div>
681
+ </div>
682
+ </div>
683
+ </section>
684
+
685
+ <section>
686
+ <h2>📡 API Endpoints</h2>
687
+ <div class="glass-card">
688
+ <div class="endpoint">
689
+ <span class="method">GET</span>/health
690
+ <div>Health check and API status verification</div>
691
+ </div>
692
+ <div class="endpoint">
693
+ <span class="method">GET</span>/tools/discover
694
+ <div>Discover available workflow tools and their capabilities</div>
695
+ </div>
696
+ <div class="endpoint">
697
+ <span class="method">POST</span>/tools/execute
698
+ <div>Execute workflow tools with structured input data</div>
699
+ </div>
700
+ </div>
701
+ </section>
702
+
703
+ <section>
704
+ <h2>📚 Full Example: Invoice Processing</h2>
705
+ <div class="glass-card">
706
+ <p style="color: rgba(255, 255, 255, 0.7); margin-bottom: 1.5rem;">
707
+ Here's how you'd build an invoice processing system that extracts data and updates your database:
708
+ </p>
709
+ <div class="code-block">from memra import Agent, Department
710
+ from memra.execution import ExecutionEngine
711
+
712
+ # 1. Define what you want done (not how)
713
+ invoice_processor = Agent(
714
+ role="Invoice Processor",
715
+ job="Extract vendor, amount, and line items from PDF invoices",
716
+ tools=[
717
+ {"name": "PDFProcessor", "hosted_by": "memra"},
718
+ {"name": "DataValidator", "hosted_by": "memra"},
719
+ {"name": "DatabaseWriter", "hosted_by": "memra"}
720
+ ]
721
+ )
722
+
723
+ # 2. Create a department (group related agents)
724
+ finance_dept = Department(
725
+ name="Accounts Payable",
726
+ agents=[invoice_processor]
727
+ )
728
+
729
+ # 3. Execute - memra handles the AI orchestration
730
+ engine = ExecutionEngine()
731
+ result = engine.execute_department(
732
+ finance_dept,
733
+ {"file": "invoice.pdf", "database": "postgresql://..."}
734
+ )
735
+
736
+ # Result: Structured data extracted and saved to your DB
737
+ print(result.summary) # "Extracted invoice #INV-001 for $1,234.56 from Acme Corp"</div>
738
+ </div>
739
+ </section>
740
+
741
+ <div class="cta-section">
742
+ <h2>Ready to Build Your AI Workforce?</h2>
743
+ <p style="color: rgba(255, 255, 255, 0.6); margin-bottom: 2rem;">Join innovative teams automating their workflows with memra</p>
744
+ <a href="mailto:info@memra.co" class="cta-button">Get Started Now</a>
745
+ </div>
746
+
747
+ <div class="footer-links">
748
+ <a href="https://pypi.org/project/memra/">PyPI Package</a>
749
+ <a href="https://github.com/memra-platform/memra-sdk">GitHub</a>
750
+ <a href="https://memra.co">Website</a>
751
+ <a href="mailto:info@memra.co">Contact</a>
752
+ </div>
753
+ </div>
754
+ </body>
755
+ </html>
756
+ """
757
+
758
+ @app.get("/health")
759
+ async def health_check():
760
+ """Health check for Fly.io"""
761
+ return {"status": "healthy"}
762
+
763
+ @app.post("/tools/execute", response_model=ToolExecutionResponse)
764
+ async def execute_tool(
765
+ request: ToolExecutionRequest,
766
+ api_key: Optional[str] = Depends(get_api_key)
767
+ ):
768
+ """Execute a tool with the given input data"""
769
+ try:
770
+ logger.info(f"Executing tool: {request.tool_name}")
771
+
772
+ # Create registry and execute tool
773
+ result = tool_registry.execute_tool(
774
+ request.tool_name,
775
+ request.hosted_by,
776
+ request.input_data,
777
+ request.config
778
+ )
779
+
780
+ return ToolExecutionResponse(
781
+ success=result.get("success", False),
782
+ data=result.get("data"),
783
+ error=result.get("error")
784
+ )
785
+
786
+ except Exception as e:
787
+ logger.error(f"Tool execution failed: {str(e)}")
788
+ return ToolExecutionResponse(
789
+ success=False,
790
+ error=str(e)
791
+ )
792
+
793
+ @app.get("/tools/discover", response_model=ToolDiscoveryResponse)
794
+ async def discover_tools(api_key: Optional[str] = Depends(get_api_key)):
795
+ """Discover available tools"""
796
+ try:
797
+ tools = tool_registry.discover_tools()
798
+
799
+ return ToolDiscoveryResponse(tools=tools)
800
+
801
+ except Exception as e:
802
+ logger.error(f"Tool discovery failed: {str(e)}")
803
+ raise HTTPException(status_code=500, detail=str(e))
804
+
805
+ if __name__ == "__main__":
806
+ import uvicorn
807
+ port = int(os.getenv("PORT", 8080))
808
+ uvicorn.run(app, host="0.0.0.0", port=port)