ivcap-lambda 0.7.22__tar.gz → 0.7.25__tar.gz
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.
- ivcap_lambda-0.7.25/PKG-INFO +507 -0
- ivcap_lambda-0.7.25/README.md +484 -0
- ivcap_lambda-0.7.25/ivcap_lambda/__init__.py +49 -0
- {ivcap_lambda-0.7.22 → ivcap_lambda-0.7.25}/ivcap_lambda/builder.py +150 -62
- {ivcap_lambda-0.7.22 → ivcap_lambda-0.7.25}/ivcap_lambda/decorators.py +3 -4
- {ivcap_lambda-0.7.22 → ivcap_lambda-0.7.25}/ivcap_lambda/executor.py +36 -27
- ivcap_lambda-0.7.25/ivcap_lambda/health_handler.py +45 -0
- {ivcap_lambda-0.7.22 → ivcap_lambda-0.7.25}/ivcap_lambda/logger.py +11 -4
- {ivcap_lambda-0.7.22 → ivcap_lambda-0.7.25}/ivcap_lambda/mcp.py +62 -60
- {ivcap_lambda-0.7.22 → ivcap_lambda-0.7.25}/ivcap_lambda/server.py +45 -28
- {ivcap_lambda-0.7.22 → ivcap_lambda-0.7.25}/ivcap_lambda/service_definition.py +15 -7
- {ivcap_lambda-0.7.22 → ivcap_lambda-0.7.25}/ivcap_lambda/utils.py +12 -10
- {ivcap_lambda-0.7.22 → ivcap_lambda-0.7.25}/pyproject.toml +25 -1
- ivcap_lambda-0.7.22/PKG-INFO +0 -134
- ivcap_lambda-0.7.22/README.md +0 -111
- ivcap_lambda-0.7.22/ivcap_lambda/__init__.py +0 -17
- {ivcap_lambda-0.7.22 → ivcap_lambda-0.7.25}/AUTHORS.md +0 -0
- {ivcap_lambda-0.7.22 → ivcap_lambda-0.7.25}/LICENSE +0 -0
- {ivcap_lambda-0.7.22 → ivcap_lambda-0.7.25}/ivcap_lambda/logging.json +0 -0
- {ivcap_lambda-0.7.22 → ivcap_lambda-0.7.25}/ivcap_lambda/py.typed +0 -0
- {ivcap_lambda-0.7.22 → ivcap_lambda-0.7.25}/ivcap_lambda/secret.py +0 -0
- {ivcap_lambda-0.7.22 → ivcap_lambda-0.7.25}/ivcap_lambda/version.py +0 -0
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ivcap-lambda
|
|
3
|
+
Version: 0.7.25
|
|
4
|
+
Summary: Helper functions for building lambda-style services on the IVCAP platform
|
|
5
|
+
License-File: AUTHORS.md
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Author: Max Ott
|
|
8
|
+
Author-email: max.ott@csiro.au
|
|
9
|
+
Requires-Python: >=3.11,<4.0
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
|
+
Requires-Dist: cachetools (>=5.5.2,<6.0.0)
|
|
16
|
+
Requires-Dist: fastapi (>=0.121.2,<0.122.0)
|
|
17
|
+
Requires-Dist: ivcap-service (>=0.6.21,<0.7.0)
|
|
18
|
+
Requires-Dist: opentelemetry-instrumentation-fastapi (>=0.57b0)
|
|
19
|
+
Requires-Dist: uuid6 (==2024.7.10)
|
|
20
|
+
Requires-Dist: uvicorn (>=0.38.0,<0.39.0)
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# ivcap-lambda: Python SDK for Lambda-Style IVCAP Services
|
|
24
|
+
|
|
25
|
+
> **Package renamed:** `ivcap-ai-tool` has been renamed to `ivcap-lambda` to reflect that
|
|
26
|
+
> the library is useful for any lambda-style IVCAP service, not just AI agent tools.
|
|
27
|
+
> A [compatibility shim](./compat/) is published under the old name — existing apps
|
|
28
|
+
> will continue to work but will see a `DeprecationWarning` prompting migration.
|
|
29
|
+
|
|
30
|
+
<a href="https://scan.coverity.com/projects/ivcap-works-ivcap-ai-tool-sdk-python">
|
|
31
|
+
<img alt="Coverity Scan Build Status"
|
|
32
|
+
src="https://img.shields.io/coverity/scan/31491.svg"/>
|
|
33
|
+
</a>
|
|
34
|
+
|
|
35
|
+
`ivcap-lambda` is a Python library that provides the scaffolding for building **lambda-style services** on the [IVCAP platform](https://github.com/ivcap-works). It sits on top of [`ivcap-service`](https://pypi.org/project/ivcap-service/) and [FastAPI](https://fastapi.tiangolo.com/) and handles:
|
|
36
|
+
|
|
37
|
+
- Registering tool functions as HTTP endpoints (with async "try-later" semantics)
|
|
38
|
+
- Job execution in threads, result caching, and graceful shutdown
|
|
39
|
+
- Event/progress reporting back to the IVCAP platform
|
|
40
|
+
- Automatic tool-description endpoints (for AI agents and the MCP protocol)
|
|
41
|
+
- Optional OpenTelemetry tracing and an MCP endpoint
|
|
42
|
+
|
|
43
|
+
> **Template repository:** A ready-to-clone project template is available at
|
|
44
|
+
> [ivcap-works/ivcap-python-ai-tool-template](https://github.com/ivcap-works/ivcap-python-ai-tool-template).
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Contents
|
|
49
|
+
|
|
50
|
+
- [Installation](#installation)
|
|
51
|
+
- [Quick Start](#quick-start)
|
|
52
|
+
- [Defining a Tool](#defining-a-tool)
|
|
53
|
+
- [Request & Result models](#request--result-models)
|
|
54
|
+
- [The `@ivcap_lambda` decorator](#the-ivcap_lambda-decorator)
|
|
55
|
+
- [Accessing the Job Context](#accessing-the-job-context)
|
|
56
|
+
- [Async tools](#async-tools)
|
|
57
|
+
- [Starting the Server](#starting-the-server)
|
|
58
|
+
- [Endpoints Created per Tool](#endpoints-created-per-tool)
|
|
59
|
+
- [Reporting Progress Events](#reporting-progress-events)
|
|
60
|
+
- [Accessing IVCAP Artifacts](#accessing-ivcap-artifacts)
|
|
61
|
+
- [Project Layout & Configuration](#project-layout--configuration)
|
|
62
|
+
- [Running Locally](#running-locally)
|
|
63
|
+
- [Building & Deploying a Docker Image](#building--deploying-a-docker-image)
|
|
64
|
+
- [Migration from `ivcap-ai-tool`](#migration-from-ivcap-ai-tool)
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Installation
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
pip install ivcap-lambda
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Or with Poetry:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
poetry add ivcap-lambda
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Quick Start
|
|
83
|
+
|
|
84
|
+
The simplest possible lambda service:
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from pydantic import BaseModel, Field
|
|
88
|
+
from ivcap_service import Service, getLogger, with_schema
|
|
89
|
+
from ivcap_lambda import start_lambda_server, ivcap_lambda, ToolOptions, logging_init
|
|
90
|
+
|
|
91
|
+
logging_init()
|
|
92
|
+
logger = getLogger("my-service")
|
|
93
|
+
|
|
94
|
+
service = Service(
|
|
95
|
+
name="My IVCAP Service",
|
|
96
|
+
description="A minimal example service.",
|
|
97
|
+
contact={"name": "Alice", "email": "alice@example.com"},
|
|
98
|
+
license={"name": "MIT", "url": "https://opensource.org/licenses/MIT"},
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@with_schema("urn:example:schema:echo.request.1")
|
|
103
|
+
class EchoRequest(BaseModel):
|
|
104
|
+
message: str = Field(..., description="The message to echo back.")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@with_schema("urn:example:schema:echo.1")
|
|
108
|
+
class EchoResult(BaseModel):
|
|
109
|
+
echo: str = Field(..., description="The echoed message.")
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@ivcap_lambda("/", opts=ToolOptions(tags=["Echo"]))
|
|
113
|
+
def echo(req: EchoRequest) -> EchoResult:
|
|
114
|
+
"""Echo a message
|
|
115
|
+
|
|
116
|
+
Returns the message passed in the request unchanged.
|
|
117
|
+
"""
|
|
118
|
+
return EchoResult(echo=req.message)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
if __name__ == "__main__":
|
|
122
|
+
start_lambda_server(service)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Run it:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
python my_service.py --port 8090
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Test it:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
curl -X POST http://localhost:8090/ \
|
|
135
|
+
-H "content-type: application/json" \
|
|
136
|
+
-d '{"message": "Hello, IVCAP!"}'
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Defining a Tool
|
|
142
|
+
|
|
143
|
+
### Request & Result models
|
|
144
|
+
|
|
145
|
+
Tool inputs and outputs are [Pydantic](https://docs.pydantic.dev/) `BaseModel` classes. Use the `@with_schema` decorator (from `ivcap_service`) to annotate them with an IVCAP schema URI — this adds a `$schema` field that the platform uses to identify payloads.
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
from pydantic import BaseModel, Field
|
|
149
|
+
from ivcap_service import with_schema
|
|
150
|
+
|
|
151
|
+
@with_schema("urn:example:schema:my-tool.request.1")
|
|
152
|
+
class MyRequest(BaseModel):
|
|
153
|
+
name: str = Field(..., description="Name to greet.")
|
|
154
|
+
count: int = Field(1, description="Number of times to repeat the greeting.", ge=1)
|
|
155
|
+
|
|
156
|
+
@with_schema("urn:example:schema:my-tool.1")
|
|
157
|
+
class MyResult(BaseModel):
|
|
158
|
+
greeting: str = Field(..., description="The generated greeting.")
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### The `@ivcap_lambda` decorator
|
|
162
|
+
|
|
163
|
+
Use `@ivcap_lambda` to register a function as a tool endpoint:
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
from ivcap_lambda import ivcap_lambda, ToolOptions
|
|
167
|
+
|
|
168
|
+
@ivcap_lambda("/greet", opts=ToolOptions(tags=["Greeter"], service_id="/greet"))
|
|
169
|
+
def greet(req: MyRequest) -> MyResult:
|
|
170
|
+
"""Greet a person
|
|
171
|
+
|
|
172
|
+
Generates a personalised greeting the requested number of times.
|
|
173
|
+
Describe your tool here — this text is surfaced to AI agents to
|
|
174
|
+
help them decide whether to use it.
|
|
175
|
+
"""
|
|
176
|
+
return MyResult(greeting=(f"Hello, {req.name}! " * req.count).strip())
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**`ToolOptions` fields:**
|
|
180
|
+
|
|
181
|
+
| Field | Default | Description |
|
|
182
|
+
|---|---|---|
|
|
183
|
+
| `name` | (inferred from path) | Human-readable name for the tool endpoint |
|
|
184
|
+
| `tags` | (inferred from path) | OpenAPI tags for grouping endpoints |
|
|
185
|
+
| `max_wait_time` | `5.0` | Seconds the `POST` waits before returning `204 Try-Later` |
|
|
186
|
+
| `refresh_interval` | `3` | `Retry-Later` header value (seconds) returned with `204` |
|
|
187
|
+
| `service_id` | `None` | Overrides the service ID reported in the tool description |
|
|
188
|
+
| `post_route_opts` | `{}` | Additional kwargs forwarded to the FastAPI route constructor |
|
|
189
|
+
| `executor_opts` | `None` | `ExecutorOpts` (job cache size/TTL, thread pool size) |
|
|
190
|
+
|
|
191
|
+
**`service_id`:** If set to a path (e.g. `"/"` or `"/greet"`), the server prepends the public URL prefix automatically, so agents receive a fully-qualified service ID.
|
|
192
|
+
|
|
193
|
+
### Accessing the Job Context
|
|
194
|
+
|
|
195
|
+
Your tool function can optionally accept a `JobContext` (from `ivcap_service`) as a keyword argument. The framework detects it by type annotation and injects it automatically. Through `JobContext` you can:
|
|
196
|
+
|
|
197
|
+
- Report progress events back to the platform
|
|
198
|
+
- Access IVCAP artifacts and other platform resources
|
|
199
|
+
|
|
200
|
+
```python
|
|
201
|
+
from ivcap_service import JobContext
|
|
202
|
+
from fastapi import Request as FRequest
|
|
203
|
+
|
|
204
|
+
@ivcap_lambda("/process", opts=ToolOptions(tags=["Processor"]))
|
|
205
|
+
def process(req: MyRequest, freq: FRequest, jobCtxt: JobContext) -> MyResult:
|
|
206
|
+
"""Process a request
|
|
207
|
+
|
|
208
|
+
Detailed description for agents.
|
|
209
|
+
"""
|
|
210
|
+
logger.info(f"job_id={jobCtxt.job_id}")
|
|
211
|
+
with jobCtxt.report.step("work", "Starting work...") as step:
|
|
212
|
+
result = do_work(req)
|
|
213
|
+
step.finished(f"Finished in {len(result)} steps")
|
|
214
|
+
return MyResult(...)
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
`JobContext` fields:
|
|
218
|
+
|
|
219
|
+
| Field | Type | Description |
|
|
220
|
+
|---|---|---|
|
|
221
|
+
| `job_id` | `str` | The unique job identifier (URN) |
|
|
222
|
+
| `report` | `EventReporter` | For emitting progress events to the platform |
|
|
223
|
+
| `job_authorization` | `str \| None` | Bearer token for authenticated calls |
|
|
224
|
+
| `ivcap` | `IVCAP` | IVCAP client for artifacts, services, etc. |
|
|
225
|
+
|
|
226
|
+
The `fastapi.Request` (`freq`) parameter is also optional and, like `JobContext`, is injected by type.
|
|
227
|
+
|
|
228
|
+
### Async tools
|
|
229
|
+
|
|
230
|
+
Async functions are fully supported:
|
|
231
|
+
|
|
232
|
+
```python
|
|
233
|
+
@ivcap_lambda("/async-greet", opts=ToolOptions(tags=["Greeter"]))
|
|
234
|
+
async def async_greet(req: MyRequest) -> MyResult:
|
|
235
|
+
"""Greet asynchronously
|
|
236
|
+
|
|
237
|
+
Same as greet but runs in an async context.
|
|
238
|
+
"""
|
|
239
|
+
await asyncio.sleep(0) # yield once
|
|
240
|
+
return MyResult(greeting=f"Hello, {req.name}!")
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## Starting the Server
|
|
246
|
+
|
|
247
|
+
```python
|
|
248
|
+
if __name__ == "__main__":
|
|
249
|
+
start_lambda_server(service)
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
`start_lambda_server` accepts:
|
|
253
|
+
|
|
254
|
+
| Argument | Description |
|
|
255
|
+
|---|---|
|
|
256
|
+
| `service` | A `Service` instance (name, contact, license) |
|
|
257
|
+
| `custom_args` | `Callable[[ArgumentParser], Namespace]` — add your own CLI flags |
|
|
258
|
+
| `run_opts` | Extra kwargs forwarded to `uvicorn.Config` |
|
|
259
|
+
| `with_telemetry` | `True` / `False` to force-enable or force-disable OpenTelemetry |
|
|
260
|
+
|
|
261
|
+
**Built-in CLI flags** (available to every service):
|
|
262
|
+
|
|
263
|
+
```
|
|
264
|
+
--host HOST Bind address (default: 0.0.0.0 / $HOST)
|
|
265
|
+
--port PORT Port to listen on (default: 8090 / $PORT)
|
|
266
|
+
--with-telemetry Initialise OpenTelemetry tracing
|
|
267
|
+
--with-mcp Expose an MCP endpoint at /mcp
|
|
268
|
+
--print-tool-description Print the tool description JSON and exit
|
|
269
|
+
--print-service-description Print the full service description JSON and exit
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
**Custom CLI flags example:**
|
|
273
|
+
|
|
274
|
+
```python
|
|
275
|
+
def custom_args(parser: argparse.ArgumentParser) -> argparse.Namespace:
|
|
276
|
+
parser.add_argument("--my-flag", type=str, help="My custom flag")
|
|
277
|
+
args = parser.parse_args()
|
|
278
|
+
if args.my_flag:
|
|
279
|
+
os.environ["MY_FLAG"] = args.my_flag
|
|
280
|
+
return args
|
|
281
|
+
|
|
282
|
+
if __name__ == "__main__":
|
|
283
|
+
start_lambda_server(service, custom_args=custom_args)
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## Endpoints Created per Tool
|
|
289
|
+
|
|
290
|
+
For each `@ivcap_lambda`-decorated function at path `{prefix}`, three routes are registered:
|
|
291
|
+
|
|
292
|
+
| Method | Path | Purpose |
|
|
293
|
+
|---|---|---|
|
|
294
|
+
| `POST` | `{prefix}` | Submit a job (execute the tool) |
|
|
295
|
+
| `GET` | `{prefix}` | Return a tool description (for agents / MCP) |
|
|
296
|
+
| `GET` | `/jobs/{job_id}` | Poll for the result of a deferred job |
|
|
297
|
+
|
|
298
|
+
Additionally, the framework registers:
|
|
299
|
+
|
|
300
|
+
- `GET /_healtz` — health check (returns `{"version": "..."}`)
|
|
301
|
+
- `GET /api` — Swagger/OpenAPI UI
|
|
302
|
+
- `GET /mcp` — MCP endpoint (only if `--with-mcp` is passed)
|
|
303
|
+
|
|
304
|
+
### Asynchronous ("try-later") semantics
|
|
305
|
+
|
|
306
|
+
When a job takes longer than `ToolOptions.max_wait_time` (default 5 s), the `POST` returns **`204 No Content`** with:
|
|
307
|
+
|
|
308
|
+
```
|
|
309
|
+
Location: /jobs/{job_id}
|
|
310
|
+
Retry-Later: 3
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
The caller can then `GET /jobs/{job_id}` after the indicated delay to collect the result.
|
|
314
|
+
|
|
315
|
+
You can force asynchronous behaviour by sending `Prefer: respond-async` or control the timeout per request with a `Timeout: <seconds>` header.
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
## Reporting Progress Events
|
|
320
|
+
|
|
321
|
+
`JobContext.report` is an `EventReporter`. Use it to stream structured progress information to the IVCAP platform.
|
|
322
|
+
|
|
323
|
+
```python
|
|
324
|
+
from ivcap_service.events import GenericEvent
|
|
325
|
+
|
|
326
|
+
# Structured step (emits a start event, then a finish event automatically)
|
|
327
|
+
with jobCtxt.report.step("download", "Downloading data...") as step:
|
|
328
|
+
for i, chunk in enumerate(data_stream):
|
|
329
|
+
process(chunk)
|
|
330
|
+
step.info(GenericEvent(name="progress", options={"chunk": i}))
|
|
331
|
+
step.finished(f"Downloaded {i+1} chunks")
|
|
332
|
+
|
|
333
|
+
# Emit a one-off event
|
|
334
|
+
jobCtxt.report.emit(GenericEvent(name="done", options={"count": 42}))
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
**Available event types** (from `ivcap_service.events`):
|
|
338
|
+
|
|
339
|
+
| Class | Schema URI | Use |
|
|
340
|
+
|---|---|---|
|
|
341
|
+
| `GenericEvent(name, options)` | `urn:ivcap:schema:service.event.generic.1` | General-purpose named event |
|
|
342
|
+
| `GenericErrorEvent(error, context, stacktrace)` | `urn:ivcap:schema:service.event.error.1` | Error/exception reporting |
|
|
343
|
+
|
|
344
|
+
Custom events can be created by subclassing `BaseEvent` and defining a `SCHEMA` class variable.
|
|
345
|
+
|
|
346
|
+
---
|
|
347
|
+
|
|
348
|
+
## Accessing IVCAP Artifacts
|
|
349
|
+
|
|
350
|
+
`JobContext.ivcap` is an `IVCAP` client instance (from [`ivcap-client`](https://pypi.org/project/ivcap-client/)). Use it to interact with platform resources such as artifacts.
|
|
351
|
+
|
|
352
|
+
```python
|
|
353
|
+
def my_tool(req: MyRequest, jobCtxt: JobContext) -> MyResult:
|
|
354
|
+
artifact = jobCtxt.ivcap.get_artifact(req.artifact_id)
|
|
355
|
+
|
|
356
|
+
with jobCtxt.report.step("download", f"Streaming {artifact.id}") as step:
|
|
357
|
+
bytes_received = 0
|
|
358
|
+
for chunk in artifact.as_stream(chunk_size=8192):
|
|
359
|
+
bytes_received += len(chunk)
|
|
360
|
+
step.info(GenericEvent(name="chunk", options={"bytes": bytes_received}))
|
|
361
|
+
step.finished(f"Downloaded {bytes_received} bytes")
|
|
362
|
+
|
|
363
|
+
return MyResult(size=bytes_received)
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
## Project Layout & Configuration
|
|
369
|
+
|
|
370
|
+
A typical project looks like this:
|
|
371
|
+
|
|
372
|
+
```
|
|
373
|
+
my-service/
|
|
374
|
+
├── pyproject.toml
|
|
375
|
+
├── my_service.py # tool implementation (entry point)
|
|
376
|
+
├── Dockerfile
|
|
377
|
+
└── tests/
|
|
378
|
+
└── echo.json
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
**`pyproject.toml`** — include the `ivcap` plugin section to integrate with the `poetry-plugin-ivcap` tooling:
|
|
382
|
+
|
|
383
|
+
```toml
|
|
384
|
+
[project]
|
|
385
|
+
name = "my-service"
|
|
386
|
+
version = "0.1.0"
|
|
387
|
+
requires-python = ">=3.11"
|
|
388
|
+
dependencies = ["ivcap-lambda"]
|
|
389
|
+
|
|
390
|
+
[tool.poetry-plugin-ivcap]
|
|
391
|
+
service-file = "my_service.py" # entry-point script
|
|
392
|
+
service-id = "urn:ivcap:service:<uuid>" # stable service URN
|
|
393
|
+
service-type = "lambda"
|
|
394
|
+
port = 8095
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
399
|
+
## Running Locally
|
|
400
|
+
|
|
401
|
+
```bash
|
|
402
|
+
# Install dependencies
|
|
403
|
+
poetry install
|
|
404
|
+
|
|
405
|
+
# Run the service (uses port from pyproject.toml [tool.poetry-plugin-ivcap])
|
|
406
|
+
poetry ivcap run
|
|
407
|
+
|
|
408
|
+
# Or run directly
|
|
409
|
+
python my_service.py --port 8095
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
**Quick test with `curl`:**
|
|
413
|
+
|
|
414
|
+
```bash
|
|
415
|
+
# Synchronous call (waits up to max_wait_time)
|
|
416
|
+
curl -X POST http://localhost:8095/ \
|
|
417
|
+
-H "content-type: application/json" \
|
|
418
|
+
-d '{"message": "Hello!"}'
|
|
419
|
+
|
|
420
|
+
# Async call — get a 204 immediately with a Location header
|
|
421
|
+
curl -i -X POST http://localhost:8095/ \
|
|
422
|
+
-H "content-type: application/json" \
|
|
423
|
+
-H "Prefer: respond-async" \
|
|
424
|
+
-d '{"message": "Hello!"}'
|
|
425
|
+
|
|
426
|
+
# Collect the result (replace JOB_ID)
|
|
427
|
+
curl http://localhost:8095/jobs/JOB_ID
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
**With IVCAP authentication:**
|
|
431
|
+
|
|
432
|
+
```bash
|
|
433
|
+
curl -X POST http://localhost:8095/ \
|
|
434
|
+
-H "content-type: application/json" \
|
|
435
|
+
-H "job-id: urn:ivcap:job:<uuid>" \
|
|
436
|
+
-H "Authorization: Bearer $(ivcap context get access-token --refresh-token)" \
|
|
437
|
+
-d '{"message": "Hello!"}'
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
**Print service/tool description (useful for IVCAP deployment):**
|
|
441
|
+
|
|
442
|
+
```bash
|
|
443
|
+
python my_service.py --print-service-description
|
|
444
|
+
python my_service.py --print-tool-description
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
---
|
|
448
|
+
|
|
449
|
+
## Building & Deploying a Docker Image
|
|
450
|
+
|
|
451
|
+
A minimal `Dockerfile`:
|
|
452
|
+
|
|
453
|
+
```dockerfile
|
|
454
|
+
FROM python:3.11-slim-bookworm
|
|
455
|
+
WORKDIR /app
|
|
456
|
+
COPY pyproject.toml ./
|
|
457
|
+
RUN pip install poetry \
|
|
458
|
+
&& poetry config virtualenvs.create false \
|
|
459
|
+
&& poetry install --no-root \
|
|
460
|
+
&& pip uninstall -y poetry
|
|
461
|
+
|
|
462
|
+
COPY my_service.py ./
|
|
463
|
+
|
|
464
|
+
ARG VERSION=???
|
|
465
|
+
ENV VERSION=$VERSION
|
|
466
|
+
ENV PORT=80
|
|
467
|
+
|
|
468
|
+
ENTRYPOINT ["python", "/app/my_service.py"]
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
Build and run:
|
|
472
|
+
|
|
473
|
+
```bash
|
|
474
|
+
docker build -t my-service .
|
|
475
|
+
docker run -p 8095:80 my-service
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
---
|
|
479
|
+
|
|
480
|
+
## Migration from `ivcap-ai-tool`
|
|
481
|
+
|
|
482
|
+
| Old (deprecated) | New |
|
|
483
|
+
|---|---|
|
|
484
|
+
| `pip install ivcap-ai-tool` | `pip install ivcap-lambda` |
|
|
485
|
+
| `from ivcap_ai_tool import ...` | `from ivcap_lambda import ...` |
|
|
486
|
+
| `@ivcap_ai_tool(...)` | `@ivcap_lambda(...)` |
|
|
487
|
+
|
|
488
|
+
The old `ivcap-ai-tool` package is a compatibility shim that re-exports everything from `ivcap-lambda`. It will emit a `DeprecationWarning` at import time. No code changes beyond the import are required.
|
|
489
|
+
|
|
490
|
+
---
|
|
491
|
+
|
|
492
|
+
## Key Symbols
|
|
493
|
+
|
|
494
|
+
| Symbol | Package | Description |
|
|
495
|
+
|---|---|---|
|
|
496
|
+
| `ivcap_lambda` | `ivcap_lambda` | Decorator to register a tool function |
|
|
497
|
+
| `start_lambda_server` | `ivcap_lambda` | Start the FastAPI/uvicorn server |
|
|
498
|
+
| `start_tool_server` | `ivcap_lambda` | Deprecated alias for `start_lambda_server` |
|
|
499
|
+
| `ToolOptions` | `ivcap_lambda` | Options for `@ivcap_lambda` |
|
|
500
|
+
| `logging_init` | `ivcap_lambda` | Initialise structured logging |
|
|
501
|
+
| `Service` | `ivcap_service` | Service metadata (name, contact, license) |
|
|
502
|
+
| `JobContext` | `ivcap_service` | Per-job context (ID, reporter, IVCAP client) |
|
|
503
|
+
| `with_schema` | `ivcap_service` | Decorator to add a `$schema` URI to a model |
|
|
504
|
+
| `getLogger` | `ivcap_service` | Get a structured logger |
|
|
505
|
+
| `GenericEvent` | `ivcap_service.events` | Emit a named event |
|
|
506
|
+
| `GenericErrorEvent` | `ivcap_service.events` | Emit an error event |
|
|
507
|
+
|