Agent
The Agent runs an iterative reasoning loop over indexed files and conversation history to produce an answer. Compared with Question Answering (one answer per question) or Extraction (one value per field), the agent decides its own steps and can optionally return a structured answer constrained by a JSON Schema.
How It Works
An agent run is an asynchronous job, like Question Answering and Extraction. You submit a message (or a list of messages), optionally attach files or collections, then poll the job until it reaches a terminal state and read its answer.
It supports two modes:
- Stateful: send a single new
message, optionally with aconversationIdto continue a server-side conversation. Prior turns are loaded automatically. - Stateless: send the full list of
messages(role + content) for the turn; nothing is stored server-side.
Creating an Agent Job
An agent run is an async job in one of two modes — pick based on whether the server should track conversation state. Both accept the same options (files, config, output_model_schema, completion_config, max_iterations) and are submitted to the same endpoint.
The optional config field (JobLaunchConfig) controls how unindexed input files are handled. By default, the request fails if any file is not indexed. Set skip_unindexed_files: true to exclude them instead. See Common Patterns: Job Launch Config.
Stateful
Send a single new message. Omit conversation_id to start a new conversation, or pass it to continue an existing one — earlier turns are loaded automatically.
- cURL
- Python (httpx)
- R
- SDK
curl -X POST "https://<api-domain>/api/core/agent/jobs" \
-H "Authorization: Bearer <your-pat>" \
-H "Content-Type: application/json" \
-d '{
"message": "Summarize the attached report and list the main risks.",
"files": { "fileIds": ["a1b2c3d4-e5f6-7890-abcd-ef1234567890"] },
"output_model_schema": {
"type": "object",
"properties": { "verdict": { "type": "string" } },
"required": ["verdict"],
"additionalProperties": false
},
"max_iterations": 10
}'
response = client.post(
"/agent/jobs",
json={
"message": "Summarize the attached report and list the main risks.",
"files": {"fileIds": ["a1b2c3d4-e5f6-7890-abcd-ef1234567890"]},
"max_iterations": 10,
},
)
response.raise_for_status()
job = response.json()
print(f"Job created: {job['jobId']} ({job['status']})")
resp <- base_req |>
req_url_path_append("agent", "jobs") |>
req_body_json(list(
message = "Summarize the attached report and list the main risks.",
files = list(fileIds = list("a1b2c3d4-e5f6-7890-abcd-ef1234567890")),
max_iterations = 10
)) |>
req_perform()
job <- resp_body_json(resp)
cat(sprintf("Job created: %s (status: %s)\n", job$jobId, job$status))
import asyncio
from biolevate import BiolevateClient
async def main():
async with BiolevateClient(
base_url="https://<api-domain>",
token="<your-pat>",
) as client:
job = await client.agent.create_job(
message="Summarize the attached report and list the main risks.",
file_ids=["a1b2c3d4-e5f6-7890-abcd-ef1234567890"],
output_model_schema={
"type": "object",
"properties": {"verdict": {"type": "string"}},
"required": ["verdict"],
"additionalProperties": False,
},
max_iterations=10,
)
print(f"Job created: {job.job_id} (status: {job.status})")
asyncio.run(main())
To continue an existing conversation, include its conversation_id:
curl -X POST "https://<api-domain>/api/core/agent/jobs" \
-H "Authorization: Bearer <your-pat>" \
-H "Content-Type: application/json" \
-d '{
"message": "And what about the secondary endpoints?",
"conversation_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}'
Stateless
Send the entire turn history as messages. Nothing is stored server-side, so each request carries the full context — you manage the conversation yourself.
- cURL
- SDK
curl -X POST "https://<api-domain>/api/core/agent/jobs" \
-H "Authorization: Bearer <your-pat>" \
-H "Content-Type: application/json" \
-d '{
"messages": [
{ "role": "USER", "content": "What are the main risks in this study?" },
{ "role": "ASSISTANT", "content": "The main risks are dropout rate, dosing variance, and small sample size." },
{ "role": "USER", "content": "Which of those is most severe?" }
],
"files": { "fileIds": ["a1b2c3d4-e5f6-7890-abcd-ef1234567890"] }
}'
from biolevate import AgentMessage
job = await client.agent.create_job(
messages=[
AgentMessage(role="USER", content="What are the main risks in this study?"),
AgentMessage(role="ASSISTANT", content="The main risks are dropout rate, dosing variance, and small sample size."),
AgentMessage(role="USER", content="Which of those is most severe?"),
],
file_ids=["a1b2c3d4-e5f6-7890-abcd-ef1234567890"],
)
print(f"Job created: {job.job_id} (status: {job.status})")
Request Fields
| Field | Description |
|---|---|
message | Stateful mode: the new user message for this turn. Mutually exclusive with messages. |
messages | Stateless mode: full list of { role, content } items (role is USER/ASSISTANT/SYSTEM). |
files | Files the agent can read from: { fileIds, collectionIds }. |
output_model_schema | Optional strict JSON Schema constraining the final answer to a structured object. Every object must set "additionalProperties": false and list all of its keys in required. |
completion_config | Per-completion LLM knobs: model (FAST/BALANCED), temperature, max_completion_tokens. |
max_iterations | Hard cap on the number of agent-loop iterations. |
conversation_id | Stateful only: continue an existing conversation. Omit to start a new one. |
Structured Output Schemas
When you pass output_model_schema, the answer is produced in strict structured-output mode. Two rules apply to every object in the schema:
"additionalProperties": false— the answer contains no keys beyond those you declare.- Every property must be listed in
required— the set of returned keys is fixed and known in advance.
Strict mode has no optional keys: you cannot make a field optional by leaving it out of required (the schema would be rejected). Instead, keep the key in required and make its value nullable by adding "null" to its type. The key is always returned, but its value may be null:
{
"type": "object",
"properties": {
"verdict": { "type": "string" },
"confidence": { "type": ["number", "null"] }
},
"required": ["verdict", "confidence"],
"additionalProperties": false
}
Here confidence is effectively optional: the agent always returns the key, but sets it to null when it has no value. Handle it as confidence is None rather than checking whether the key exists.
Polling and Reading Results
curl -s "https://<api-domain>/api/core/agent/jobs/${JOB_ID}" \
-H "Authorization: Bearer <your-pat>"
curl -s "https://<api-domain>/api/core/agent/jobs/${JOB_ID}/results" \
-H "Authorization: Bearer <your-pat>"
The results endpoint returns:
| Field | Description |
|---|---|
answer | The agent's answer — a structured object (if a schema was provided) or plain text |
explanation | A short explanation of how the answer was produced |
referenceIds | Annotation IDs linking the answer to source passages |
Listing Jobs
# All agent jobs
curl -s "https://<api-domain>/api/core/agent/jobs?page=0&pageSize=20" \
-H "Authorization: Bearer <your-pat>"
# Only jobs from one conversation
curl -s "https://<api-domain>/api/core/agent/jobs?page=0&pageSize=20&conversationId=${CONVERSATION_ID}" \
-H "Authorization: Bearer <your-pat>"
SDK Polling Example
import asyncio
from biolevate import BiolevateClient
TERMINAL_STATUSES = {"SUCCESS", "FAILED", "ABORTED"}
async def main():
async with BiolevateClient(
base_url="https://<api-domain>",
token="<your-pat>",
) as client:
job = await client.agent.create_job(
message="Summarize the attached report and list the main risks.",
file_ids=["a1b2c3d4-e5f6-7890-abcd-ef1234567890"],
)
while True:
job = await client.agent.get_job(job.job_id)
if job.status in TERMINAL_STATUSES:
break
await asyncio.sleep(3)
if job.status == "SUCCESS":
outputs = await client.agent.get_job_outputs(job.job_id)
print("Answer:", outputs.answer)
print("References:", outputs.reference_ids)
asyncio.run(main())
Next Steps
- Python SDK: Agent — high-level SDK methods
- Question Answering — single-question answers instead of an agent loop
- Annotations — resolve the answer's
referenceIdsto source passages - API Reference for complete schemas