The uniform JSON envelope that every Local AI call returns — both the ChatRequest Display action and the TK.AIExecute script API — including success, error, disabled, and truncated cases.
AI Integration → Local AI → Reply Envelope Schema (10.1.5 draft)
info10.1.5 draft. This page documents the Local AI reply envelope as it ships in FrameworX 10.1.5. Content may change before GA.
Both Local AI consumer paths return the same JSON shape on every call. The shape is the same for success, for errors, for the master kill-switch being off, and for wall-clock truncation. Customers parse one structure and dispatch on the status field — never on a thrown exception, never on a missing field, never on a special-cased error string.
| Code Block | ||
|---|---|---|
| ||
{
"text": "<the LLM answer text, or empty string on non-ok status>",
"status": "ok | error | disabled | truncated",
"toolTrace": [ ... ],
"latencyMs": 480,
"warnings": [ ... ]
} |
Field | Type | Always present? | Notes |
|---|---|---|---|
| string | Yes | The LLM's terminal answer. Empty string ( |
| enum string | Yes | Exactly one of |
| array | Yes | Sequence of tool dispatches the LLM made during the turn. Empty array ( |
| integer (long) | Yes — including on errors | Wall-clock milliseconds from entry to envelope-build. Reports |
| array of string | Yes | Empty array ( |
No optional fields. Every field present on every envelope, every code path. Callers can parse with confidence:
| Code Block | ||
|---|---|---|
| ||
var reply = JObject.Parse(replyJson);
string status = reply.Value<string>("status"); // never null
long ms = reply.Value<long>("latencyMs"); // always parseable
string text = reply.Value<string>("text") ?? ""; // empty on non-ok |
okThe LLM POST completed and returned a terminal reply. text contains the model's answer. warnings may still be non-empty if a hook handler threw mid-turn (the chat continues; warnings list the failure).
errorPre-POST validation failed, or the HTTP call failed, or response parsing failed, or an exception was caught. text is empty. warnings carries the message(s) describing the failure. latencyMs reflects elapsed time to the error point.
disabledA master gate is off:
SolutionSettings.ModelEnabled = false — applies to BOTH paths.SolutionSettings.ModelOptions bit 0x02 (EnableRuntimeMCP) is OFF — applies to ChatRequest only. TK.AIExecute does not consult this bit.text is empty. warnings carries the gate identifier so the caller can log which gate blocked the call. latencyMs is 0 (early-exit before any work).
truncatedOne of the budget caps tripped mid-turn:
ChatRequest only; the LLM kept asking for more tool calls than the platform permits in one turn).ChatRequest only).text carries the partial reply if the LLM produced one before truncation; otherwise empty. warnings describes which cap tripped.
Situation |
|
|
|
|
|---|---|---|---|---|
Normal successful chat turn |
| LLM answer |
| elapsed |
Successful turn, hook handler threw |
| LLM answer | handler error message(s) | elapsed |
|
|
| kill-switch identifier |
|
|
|
| master-bit identifier |
|
Query JSON malformed |
|
| parse error message | elapsed |
LLM endpoint unreachable |
|
| HTTP error message | elapsed |
LLM endpoint returned non-2xx |
|
| HTTP status code + reason | elapsed |
Wall-clock budget (60 s) exceeded |
| partial reply, if any | budget-exceeded message | ≥ 60000 |
Tool-dispatch cap reached ( |
| partial reply, if any | cap-reached message | elapsed |
Each entry in toolTrace[] records one tool dispatch (cached ChatRequest path only — atomic TK.AIExecute calls always return an empty toolTrace):
| Code Block | ||
|---|---|---|
| ||
{
"name": "runtime_get_value",
"args": { "tag": "Pump1.MotorCurrent" },
"result": { "value": 12.4, "quality": "Good" },
"status": "ok",
"timestamp": "2026-04-26T14:32:15.123Z",
"elapsedMs": 3
} |
Field | Type | Notes |
|---|---|---|
| string | Tool method the LLM invoked, e.g. |
| parsed JSON or string | Arguments the LLM supplied to the tool. Parsed JSON object when the tool's parameters are structured; raw string when the LLM passed an unstructured argument. |
| parsed JSON or string | Tool's return value. Parsed JSON when structured, raw string otherwise. |
| enum string |
|
| ISO 8601 UTC string | When the tool was dispatched. |
| integer | Wall-clock milliseconds the tool dispatch took. |
TK.AIExecute)| Code Block | ||
|---|---|---|
| ||
{
"text": "The likely cause is a partially blocked impeller given the high motor current with low flow rate.",
"status": "ok",
"toolTrace": [],
"latencyMs": 832,
"warnings": []
} |
| Code Block | ||
|---|---|---|
| ||
{
"text": "Pump1.MotorCurrent is currently 12.4 A.",
"status": "ok",
"toolTrace": [
{
"name": "runtime_get_value",
"args": { "tag": "Pump1.MotorCurrent" },
"result": { "value": 12.4, "quality": "Good" },
"status": "ok",
"timestamp": "2026-04-26T14:32:15.123Z",
"elapsedMs": 3
}
],
"latencyMs": 1247,
"warnings": []
} |
| Code Block | ||
|---|---|---|
| ||
{
"text": "",
"status": "disabled",
"toolTrace": [],
"latencyMs": 0,
"warnings": [ "Local AI master kill-switch (ModelEnabled) is off." ]
} |
| Code Block | ||
|---|---|---|
| ||
{
"text": "",
"status": "error",
"toolTrace": [],
"latencyMs": 1043,
"warnings": [ "LLM endpoint HTTP error: Connection refused (http://localhost:11434/v1/chat/completions)" ]
} |
| Code Block | ||
|---|---|---|
| ||
{
"text": "Based on the readings I have so far, the early indicators suggest...",
"status": "truncated",
"toolTrace": [ /* dispatched tools up to truncation */ ],
"latencyMs": 60012,
"warnings": [ "LLM POST wall-clock budget (60s) exceeded." ]
} |
| Code Block | ||
|---|---|---|
| ||
string replyJson = TK.AIExecute(query);
JObject reply = JObject.Parse(replyJson);
switch (reply.Value<string>("status"))
{
case "ok":
@Tag.Last.Answer = reply.Value<string>("text");
break;
case "disabled":
@Tag.Last.Answer = "Local AI is currently disabled.";
break;
case "truncated":
@Tag.Last.Answer = reply.Value<string>("text");
// log warnings for diagnostics
break;
default: // "error"
@Tag.Last.Answer = "Could not generate a reply.";
// warnings array carries the cause
break;
} |
If the reply tag is typed JSON, the platform's tag-method surface lets a Display Expression extract fields directly:
@Tag.Chat.ReplyJson.JsonString("text") — the answer text.@Tag.Chat.ReplyJson.JsonString("status") — the envelope status.@Tag.Chat.ReplyJson.JsonValue<long>("latencyMs") — the wall-clock latency.Wire these into Result expressions on the ChatRequest Action; see ChatRequest Action Reference (10.1.5 draft).
JObject.Parse(reply) never throws on a Local AI reply.status="error" and a populated warnings array.| Page Tree | ||
|---|---|---|
|