Skip to main content

Agent

Conversational reasoning over your documents

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 a conversationId to 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.

Job launch config

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 -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
}'

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 -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"] }
}'

Request Fields

FieldDescription
messageStateful mode: the new user message for this turn. Mutually exclusive with messages.
messagesStateless mode: full list of { role, content } items (role is USER/ASSISTANT/SYSTEM).
filesFiles the agent can read from: { fileIds, collectionIds }.
output_model_schemaOptional 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_configPer-completion LLM knobs: model (FAST/BALANCED), temperature, max_completion_tokens.
max_iterationsHard cap on the number of agent-loop iterations.
conversation_idStateful 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:

FieldDescription
answerThe agent's answer — a structured object (if a schema was provided) or plain text
explanationA short explanation of how the answer was produced
referenceIdsAnnotation 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