Lambda Tool Service Example¶
A complete, production-ready IVCAP lambda service with multiple tools, progress reporting, and artifact handling.
Overview¶
This example demonstrates:
- Multiple tools registered on a single service
- Progress reporting with named steps
- Artifact download and upload
- Async tool function
JobContextinjection- Custom
ToolOptions
Code¶
import asyncio
import io
from pydantic import BaseModel, Field
from ivcap_service import (
Service, ServiceContact, ServiceLicense,
JobContext, getLogger, with_schema,
)
from ivcap_lambda import (
start_lambda_server, ivcap_lambda, ToolOptions, logging_init,
)
from ivcap_lambda.executor import ExecutorOpts
# Initialize logging
logging_init()
logger = getLogger("tool_service")
# Define service metadata
service = Service(
name="IVCAP Tool Service Example",
contact=ServiceContact(name="Dev Team", email="dev@example.com"),
license=ServiceLicense(name="MIT", url="https://opensource.org/license/MIT"),
)
# ── Tool 1: Echo ─────────────────────────────────────────────────────────────
@with_schema("urn:example:schema:echo.request.1")
class EchoRequest(BaseModel):
message: str = Field(..., description="Message to echo back.")
repeat: int = Field(1, description="Number of times to repeat the message.", ge=1, le=10)
@with_schema("urn:example:schema:echo.1")
class EchoResult(BaseModel):
echo: str = Field(..., description="The echoed message.")
length: int = Field(..., description="Total length of the echoed string.")
@ivcap_lambda("/echo", opts=ToolOptions(tags=["Utilities"], service_id="/echo"))
def echo(req: EchoRequest) -> EchoResult:
"""Echo a message back to the caller
Returns the provided message repeated the requested number of times.
Useful for testing connectivity and tool invocation patterns.
"""
result = (req.message + " ") * req.repeat
result = result.strip()
return EchoResult(echo=result, length=len(result))
# ── Tool 2: Word Count (with JobContext) ─────────────────────────────────────
@with_schema("urn:example:schema:word-count.request.1")
class WordCountRequest(BaseModel):
text: str = Field(..., description="Text to analyse.", min_length=1)
@with_schema("urn:example:schema:word-count.1")
class WordCountResult(BaseModel):
words: int = Field(..., description="Number of words.")
chars: int = Field(..., description="Number of characters.")
lines: int = Field(..., description="Number of lines.")
@ivcap_lambda(
"/word-count",
opts=ToolOptions(
tags=["Text Analysis"],
service_id="/word-count",
max_wait_time=10.0,
),
)
def word_count(req: WordCountRequest, jobCtxt: JobContext) -> WordCountResult:
"""Count words, characters, and lines in a piece of text
Analyses the provided text and returns word, character, and line counts.
Use this tool to understand the size and structure of text content.
"""
logger.info(f"word_count job={jobCtxt.job_id}")
with jobCtxt.report.step("analyse", "Analysing text...") as step:
words = len(req.text.split())
chars = len(req.text)
lines = req.text.count("\n") + 1
step.finished(f"Found {words} words, {chars} chars, {lines} lines")
return WordCountResult(words=words, chars=chars, lines=lines)
# ── Tool 3: File Stats (artifact-based) ─────────────────────────────────────
@with_schema("urn:example:schema:file-stats.request.1")
class FileStatsRequest(BaseModel):
artifact_id: str = Field(..., description="IVCAP artifact URN to inspect.")
@with_schema("urn:example:schema:file-stats.1")
class FileStatsResult(BaseModel):
artifact_id: str = Field(..., description="URN of the inspected artifact.")
size_bytes: int = Field(..., description="File size in bytes.")
line_count: int = Field(..., description="Number of lines (for text files).")
@ivcap_lambda(
"/file-stats",
opts=ToolOptions(
tags=["Files"],
service_id="/file-stats",
executor_opts=ExecutorOpts(max_workers=4, job_cache_size=500),
),
)
def file_stats(req: FileStatsRequest, jobCtxt: JobContext) -> FileStatsResult:
"""Get statistics for an IVCAP artifact
Downloads the specified artifact and returns its size and line count.
Works best with text files; binary files will have an approximate line count.
"""
ivcap = jobCtxt.ivcap
with jobCtxt.report.step("download", f"Downloading {req.artifact_id}") as step:
artifact = ivcap.get_artifact(req.artifact_id)
data = b"".join(artifact.as_stream(chunk_size=65536))
step.finished(f"Downloaded {len(data)} bytes")
line_count = data.decode("utf-8", errors="replace").count("\n") + 1
return FileStatsResult(
artifact_id=req.artifact_id,
size_bytes=len(data),
line_count=line_count,
)
# ── Tool 4: Async Reverse ────────────────────────────────────────────────────
@with_schema("urn:example:schema:reverse.request.1")
class ReverseRequest(BaseModel):
text: str = Field(..., description="Text to reverse.")
@with_schema("urn:example:schema:reverse.1")
class ReverseResult(BaseModel):
reversed_text: str = Field(..., description="The reversed text.")
@ivcap_lambda("/reverse", opts=ToolOptions(tags=["Utilities"]))
async def reverse(req: ReverseRequest) -> ReverseResult:
"""Reverse a string asynchronously
Returns the input string with its characters in reversed order.
Demonstrates async tool function support.
"""
await asyncio.sleep(0) # yield to event loop
return ReverseResult(reversed_text=req.text[::-1])
# ── Entry point ───────────────────────────────────────────────────────────────
if __name__ == "__main__":
start_lambda_server(service)
Testing¶
Start the server:
Test each tool:
# Echo
curl -X POST http://localhost:8090/echo \
-H "content-type: application/json" \
-d '{"message": "Hello, IVCAP!", "repeat": 3}'
# Word count
curl -X POST http://localhost:8090/word-count \
-H "content-type: application/json" \
-d '{"text": "The quick brown fox\njumps over the lazy dog"}'
# Async reverse
curl -X POST http://localhost:8090/reverse \
-H "content-type: application/json" \
-d '{"text": "Hello!"}'
Test async (deferred) behaviour:
curl -i -X POST http://localhost:8090/word-count \
-H "Prefer: respond-async" \
-H "content-type: application/json" \
-d '{"text": "some text"}'
# → 204 No Content + Location: /jobs/JOB_ID
curl http://localhost:8090/jobs/JOB_ID
# → 200 + result JSON
Key Features¶
- Multiple tools — four tools registered on a single service instance
- Optional
JobContext— tools accept it when they need it; omit it otherwise - Async support —
reversedemonstrates an async tool - Custom executor options —
file-statsuses a fixed thread pool and smaller cache - Artifact access —
file-statsdownloads an IVCAP artifact viaJobContext.ivcap - Progress steps —
word-countandfile-statsreport progress back to the platform