Home Playground Docs hub

Guide G-10 · Applied Intelligence

Your first AI chat session

Verified against local · 2026-09-23 · evergreen-ai-service 3.0.1 (workspace HEAD)
Journey. As a developer, I want to open a conversation with an AI model I
choose by capability, send messages, and get answers — without hosting or
managing any model infrastructure.

Evergreen AI is the platform's model-front-door service: you describe what you need (criteria), it selects and loads a model, and you exchange messages over a session. This guide walks the full lifecycle — create, chat, stream, review history, end — against the local stack (Evergreen AI :40071). Verified with a locally-running Ollama backend.

Prerequisites

dev subdomain).

in TOKEN (obtain one via guide G-01; the full example below fetches one via the QA sign-in shortcut).

this is Ollama (http://127.0.0.1:11434) with a model pulled, e.g. qwen3-vl:4b.

model-load cost (tens of seconds on a laptop); later messages in the same session are fast. On a memory-constrained dev machine the first call can stretch to several minutes — size your client timeouts accordingly.

Concepts (only what this journey needs)

records generation parameters (temperature, system prompt, …), and allocates server-side history. Sessions expire on their own (history_ttl_in_seconds, default 24 h) — treat the id as ephemeral.

filter list (specific model, architecture, parameter count, minimum context window, capability, …) each marked MustHave or NiceToHave. The service picks the best locally-available match. This guide pins one model (MustHave + Model) for determinism.

AiChatMessage JSON; POST …/streamed-chat-messages returns SSE message events carrying text deltas you concatenate, terminated by done.

GET …/chat-history replays it. Ending the session (DELETE) discards it.

Step 1 — Create a session

bash
curl -s -X POST "${BASE_URL}/v3/sessions" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "x-api-key: ${INAYA_API_KEY}" \
  -H "x-client-app-key: ${INAYA_APP_KEY}" \
  -d '{
    "required_modalities": ["Text"],
    "selection_criteria": [
      { "Model": { "optionality": "MustHave", "name": "Qwen3_VL_4B_Q4" } }
    ],
    "system_prompt": "You are a helpful assistant. Answer in one short sentence.",
    "temperature": 0.2
  }'
python
import json, os, urllib.request

req = urllib.request.Request(
    f"{os.environ['BASE_URL']}/v3/sessions",
    data=json.dumps({
        "required_modalities": ["Text"],
        "selection_criteria": [
            {"Model": {"optionality": "MustHave", "name": "Qwen3_VL_4B_Q4"}},
        ],
        "system_prompt": "You are a helpful assistant. Answer in one short sentence.",
        "temperature": 0.2,
    }).encode(),
    headers={
        "Content-Type": "application/json",
        "Authorization": f"Bearer {os.environ['TOKEN']}",
        "x-api-key": os.environ["INAYA_API_KEY"],
        "x-client-app-key": os.environ["INAYA_APP_KEY"],
    },
)
print(json.dumps(json.load(urllib.request.urlopen(req, timeout=60)), indent=2))
javascript
const res = await fetch(`${process.env.BASE_URL}/v3/sessions`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer ${process.env.TOKEN}`,
    "x-api-key": process.env.INAYA_API_KEY,
    "x-client-app-key": process.env.INAYA_APP_KEY,
  },
  body: JSON.stringify({
    required_modalities: ["Text"],
    selection_criteria: [
      { Model: { optionality: "MustHave", name: "Qwen3_VL_4B_Q4" } },
    ],
    system_prompt: "You are a helpful assistant. Answer in one short sentence.",
    temperature: 0.2,
  }),
});
console.log(await res.json());

Expected response — 200 with the session id and the selected model's default image constraints (relevant when you later send images):

json
{
  "ai_session_id": "01M38BVKKPGTB0D4YRRQD4BV7M",
  "image_size_constraints": { "max_supported": { "width": 1344, "height": 1344 }, "standard_size": { "width": 448, "height": 448 } }
}

TIP

Criteria compose. A realistic production pick looks like [{ "MinContextWindowLength": { "optionality": "MustHave", "length": 32768 } }, { "ParameterCount": { "optionality": "NiceToHave", "count": "7B" } }] — hard requirements plus preferences, letting the service choose the best available model. Pinning Model directly, as above, is best for tests and docs.

PITFALL

404 on session creation almost always means no suitable model: either the pinned model isn't pulled in the backend, or no model satisfies your MustHave criteria. Check the backend's model list before retrying.

Step 2 — Send a message and get the full reply

Messages are multipart/form-data — the same call accepts a binary image_path field later, so text rides along as a form field:

bash
curl -s -X POST "${BASE_URL}/v3/sessions/${SESSION_ID}/chat-messages" \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "x-api-key: ${INAYA_API_KEY}" \
  -H "x-client-app-key: ${INAYA_APP_KEY}" \
  -F "user_message=Say hello in exactly three words."
python
import json, os, urllib.request

boundary = "----g10boundary"
body = (
    f"--{boundary}\r\n"
    'Content-Disposition: form-data; name="user_message"\r\n\r\n'
    "Say hello in exactly three words.\r\n"
    f"--{boundary}--\r\n"
).encode()

req = urllib.request.Request(
    f"{os.environ['BASE_URL']}/v3/sessions/{os.environ['SESSION_ID']}/chat-messages",
    data=body,
    headers={
        "Content-Type": f"multipart/form-data; boundary={boundary}",
        "Authorization": f"Bearer {os.environ['TOKEN']}",
        "x-api-key": os.environ["INAYA_API_KEY"],
        "x-client-app-key": os.environ["INAYA_APP_KEY"],
    },
)
# Generous timeout: the first call of a session includes model-load time
print(json.dumps(json.load(urllib.request.urlopen(req, timeout=300)), indent=2))
javascript
const form = new FormData();
form.set("user_message", "Say hello in exactly three words.");

const res = await fetch(
  `${process.env.BASE_URL}/v3/sessions/${process.env.SESSION_ID}/chat-messages`,
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.TOKEN}`,
      "x-api-key": process.env.INAYA_API_KEY,
      "x-client-app-key": process.env.INAYA_APP_KEY,
    },
    body: form,
  },
);
console.log(await res.json());

Expected response — 200 with the complete assistant message (shape of response_time_ms grows to the full generation time; the first call of a session also includes model load):

json
{
  "author": "Assistant",
  "content": [{ "Text": "Hello, how are you?" }],
  "creation_time": "2026-09-24T00:08:36Z",
  "id": "60OKQCPO89BJGC1IAGQL6C9JB90LGDQ1AT534L2E8K",
  "model_name": "Qwen3_VL_4B_Q4",
  "response_time_ms": 20695
}

PITFALL

This endpoint has a generous internal timeout, but the route-level timeout

does not cover streaming (see Step 3). If a non-streamed call returns 500 with an engine error after a long wait, the model likely failed to load — check the service logs and the backend's health before retrying.

Step 3 — Stream the reply (SSE)

For interactive UIs, stream tokens as they generate:

bash
curl -s -N --max-time 180 -X POST \
  "${BASE_URL}/v3/sessions/${SESSION_ID}/streamed-chat-messages" \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "x-api-key: ${INAYA_API_KEY}" \
  -H "x-client-app-key: ${INAYA_APP_KEY}" \
  -F "user_message=Count from one to three."

Expected response — SSE events; concatenate the Text of every message event until done:

event: message
data: {"author":"Assistant","content":[{"Text":"One"}],"creation_time":"…","id":"…","model_name":"Qwen3_VL_4B_Q4","response_time_ms":0}

event: message
data: {"author":"Assistant","content":[{"Text":", two"}],"creation_time":"…","id":"…","model_name":"Qwen3_VL_4B_Q4","response_time_ms":0}

event: done
data: true

NOTE

Chunks are deltas, not cumulative — each message event carries only the

newly generated fragment. Some fragments may be empty (e.g. reasoning markers with thinking models); skip them. An event: error terminates the stream on failure. The --max-time on the snippet is a safety cap for scripts, not a requirement.

Step 4 — Review the conversation

History is stored server-side per session:

bash
curl -s "${BASE_URL}/v3/sessions/${SESSION_ID}/chat-history" \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "x-api-key: ${INAYA_API_KEY}" \
  -H "x-client-app-key: ${INAYA_APP_KEY}"
python
import json, os, urllib.request

req = urllib.request.Request(
    f"{os.environ['BASE_URL']}/v3/sessions/{os.environ['SESSION_ID']}/chat-history",
    headers={
        "Authorization": f"Bearer {os.environ['TOKEN']}",
        "x-api-key": os.environ["INAYA_API_KEY"],
        "x-client-app-key": os.environ["INAYA_APP_KEY"],
    },
)
print(json.dumps(json.load(urllib.request.urlopen(req, timeout=60)), indent=2))
javascript
const res = await fetch(
  `${process.env.BASE_URL}/v3/sessions/${process.env.SESSION_ID}/chat-history`,
  {
    headers: {
      Authorization: `Bearer ${process.env.TOKEN}`,
      "x-api-key": process.env.INAYA_API_KEY,
      "x-client-app-key": process.env.INAYA_APP_KEY,
    },
  },
);
console.log(await res.json());

Expected response — 200 with every recorded message in order (user and assistant):

json
{ "messages": [ { "author": "User", "content": [{ "Text": "Say hello in exactly three words." }], "creation_time": "…", "id": "…" }, { "author": "Assistant", "content": [{ "Text": "Hello, how are you?" }], "creation_time": "…", "id": "…", "model_name": "Qwen3_VL_4B_Q4", "response_time_ms": 20695 } ] }

Step 5 — End the session

bash
curl -s -o /dev/null -w "HTTP %{http_code}\n" -X DELETE \
  "${BASE_URL}/v3/sessions/${SESSION_ID}" \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "x-api-key: ${INAYA_API_KEY}" \
  -H "x-client-app-key: ${INAYA_APP_KEY}"

Expected response — 200; the session id and its history are discarded.

Full working example

A complete create → chat → history → end cycle (first_chat.sh) — it fetches a user JWT via the QA shortcut first (local/QA environments only; production uses the G-01 flows):

bash
#!/usr/bin/env bash
set -euo pipefail

# 0. Obtain a user JWT (QA shortcut — see G-01 for production flows)
TOKEN=$(curl -sf -X POST "${AUTH_URL}/v3/auth/qa/login" \
  -H "Content-Type: application/json" \
  -H "x-api-key: ${INAYA_API_KEY}" \
  -H "x-client-app-key: ${INAYA_APP_KEY}" \
  -d "{\"device_id\": \"evg-demo-device\", \"locale\": \"en-US\", \"tenant_id\": \"${TENANT_ID}\", \"username\": \"evg-demo\"}" \
  | python3 -c 'import json,sys; print(json.load(sys.stdin)["token"])')
AUTH=(-H "Authorization: Bearer ${TOKEN}"
      -H "x-api-key: ${INAYA_API_KEY}"
      -H "x-client-app-key: ${INAYA_APP_KEY}")

# 1. Create a session
SESSION_ID=$(curl -sf -X POST "${BASE_URL}/v3/sessions" "${AUTH[@]}" \
  -H "Content-Type: application/json" \
  -d '{
    "required_modalities": ["Text"],
    "selection_criteria": [ { "Model": { "optionality": "MustHave", "name": "Qwen3_VL_4B_Q4" } } ],
    "temperature": 0.2
  }' | python3 -c 'import json,sys; print(json.load(sys.stdin)["ai_session_id"])')
echo "session: ${SESSION_ID}"

# 2. Chat (first call loads the model — allow time)
curl -sf -X POST "${BASE_URL}/v3/sessions/${SESSION_ID}/chat-messages" \
  "${AUTH[@]}" -F "user_message=Say hello in exactly three words."
echo

# 3. History
curl -sf "${BASE_URL}/v3/sessions/${SESSION_ID}/chat-history" "${AUTH[@]}" \
  | python3 -c 'import json,sys; d=json.load(sys.stdin); print("messages:", len(d["messages"]))'

# 4. End the session
curl -sf -X DELETE "${BASE_URL}/v3/sessions/${SESSION_ID}" "${AUTH[@]}" \
  && echo "session ended"

(AUTH_URL is the Auth Service base URL — http://127.0.0.1:40004 on the local stack; TENANT_ID is your tenant's id.)

Try it live

Open the Playground → Applied Intelligence → Evergreen AI · Session Console — the same create/chat flow with model-criteria controls, running against a live service.

Copy agent prompt

Agent prompt — copy and hand to your agent
You are working with the Inaya Labs Evergreen AI Service at ${BASE_URL}. The
tenant key pair is in INAYA_API_KEY / INAYA_APP_KEY env vars; obtain a user JWT
via the QA login from guide G-01 if TOKEN is not already set. Steps: (1) POST
/v3/sessions with required_modalities ["Text"] and selection_criteria pinning
model "Qwen3_VL_4B_Q4" as MustHave; report the ai_session_id. (2) POST
/v3/sessions/<id>/chat-messages as multipart with field user_message "Say hello
in exactly three words."; report the reply text and model_name. Note the first
call may take tens of seconds while the model loads. (3) GET
/v3/sessions/<id>/chat-history and report the message count. (4) DELETE
/v3/sessions/<id> to end the session. Never log the JWT.

API reference

Next journeys