Multimodal Input
Multimodal messages use ContentPart lists:
from praval import Agent, ContentPart
agent = Agent("vision", provider="openai", model="gpt-5.4-mini")
response = agent.generate(
[
ContentPart.text_part("Describe this image."),
ContentPart.image_url("https://example.com/image.png"),
]
)
For direct runtime calls:
from praval import ContentPart, ModelMessage, ModelRequest
request = ModelRequest(
provider="gemini",
model="gemini-3.5-flash",
messages=[
ModelMessage(
role="user",
content=[
ContentPart.text_part("What is shown?"),
ContentPart.image_base64("...", "image/png"),
],
)
],
)
Supported part constructors:
Constructor |
Meaning |
|---|---|
|
Text input. |
|
Remote image URL. |
|
Inline base64 image. |
|
Inline file data. |
|
Remote file reference. |
|
Inline base64 audio. |
|
Remote audio reference. |
|
Inline base64 video. |
|
Remote video reference. |
The runtime validates content parts against the resolved capability profile. Unsupported image, file, audio, and unknown parts fail before the adapter is called. This is deliberate: a profile must be truthful or explicitly overridden.
Gemini accepts image, file, audio, and video parts in the 0.8 adapter. A
file_url must already be a URI accepted by the Gemini API, such as a Gemini
Files API URI or a provider-supported remote object URI. Praval does not upload
local files to Gemini in this release. Encode small local inputs as base64 or
upload them separately and pass the resulting reference.
OpenAI generation supports image input in the stable 0.8 profile. File, audio, and video generation inputs remain disabled until the corresponding adapter paths are implemented and tested. Anthropic supports image input; other document or PDF forms are not advertised by the current adapter.
Request-Based Voice
OpenAI agents can transcribe an audio file, use the transcript in a normal agent turn, and synthesize the reply:
from pathlib import Path
from praval import Agent
agent = Agent("voice-assistant", provider="openai", model="gpt-5.4-mini")
transcript = agent.transcribe(
Path("question.wav"),
model="gpt-4o-transcribe",
language="en",
)
reply = agent.chat(transcript)
audio = agent.speak(
reply,
model="tts-1",
voice="alloy",
response_format="mp3",
)
Path("reply.mp3").write_bytes(audio)
Agent.transcribe() accepts bytes, a local path, a binary file object, or an
OpenAI SDK file tuple. It returns transcription text. Agent.speak() returns
audio bytes. These helpers do not add audio operations to conversation history;
only the explicit chat() call adds a turn.
The default audio models are separate from the agent’s chat model:
gpt-4o-transcribe for transcription and tts-1 for speech. Override them per
call, or set transcription_model and speech_model in the agent’s
provider_options configuration.
This is a request-response API. In Praval, a realtime session would keep a
provider connection open for continuous input/output events and potentially
bidirectional audio over WebRTC or WebSocket. Those persistent sessions,
streaming audio conversations, and raw binary Spore transport are deferred
beyond the 0.8 line.
ContentPart.audio_* and ContentPart.video_* are generation inputs for
capable providers such as Gemini; they are distinct from the transcription and
speech helpers.
Multimodal Spores
Spore can carry JSON-safe content_parts, knowledge_references, and
data_references alongside the compatibility knowledge/payload fields:
from datetime import datetime, timezone
from praval import ContentPart, Spore, SporeType
spore = Spore(
id="analysis-1",
spore_type=SporeType.KNOWLEDGE,
from_agent="vision",
to_agent="reviewer",
knowledge={"summary": "Inspect the source image"},
created_at=datetime.now(timezone.utc),
content_parts=[
ContentPart.image_url("https://example.com/source.png")
],
data_references=["s3_main://object/images/source.png"],
)
Rich Spores use a versioned JSON AMQP envelope. Knowledge-only Spores retain the legacy AMQP body for compatibility. Raw bytes are rejected: use base64 content parts for small data and storage references for large data.