CMSDK 2.0.1
Cross-platform C++ base library and SDK for the Psyclone AIOS platform
Loading...
Searching...
No Matches
LLMConnection.h
Go to the documentation of this file.
1#if !defined(_LLMCONNECTION_H_)
2#define _LLMCONNECTION_H_
3
4#pragma once
5
6#include "Utils.h"
7#include "xml_parser.h"
8
9#include <string>
10#include <vector>
11
12namespace cmlabs{
13
49
50// ============================================================================
51// LLMConnection (T1.1 step 1.1a)
52//
53// Named, node-owned connection object to an LLM endpoint, configured from
54// the reserved <llm> named-connection XML element (D1/F1.8) which contains
55// an <llmvendor> vendor-descriptor sub-block:
56//
57// <llm name="main" apikeyref="OPENAI_API_KEY" global="true" schemaversion="1"
58// maxtokensperrequest="4096" maxrequestspermin="60"
59// maxconcurrent="2" costceiling="10.0">
60// <llmvendor type="openai" endpoint="https://api.openai.com/v1/chat/completions">
61// <header name="Authorization" value="Bearer %apikey%"/>
62// <requesttemplate>{"model":"%model%","messages":[...]}</requesttemplate>
63// <responsetemplate>choices[0].message.content</responsetemplate>
64// <models default="gpt-4o">
65// <model name="gpt-4o"/>
66// <model name="gpt-4o-mini"/>
67// </models>
68// </llmvendor>
69// </llm>
70//
71// Secrets are NEVER inline in the XML (Q-C). `apikeyref` is an indirect
72// reference, resolved at configure() time:
73// apikeyref="env:NAME" -> environment variable NAME (set, non-empty)
74// apikeyref="file:/path" -> whole file contents, trailing newline/
75// whitespace trimmed (single-line key files)
76// apikeyref="NAME" -> legacy bare form: env var NAME, else a
77// key=value entry in optional `secretsfile`
78// A missing env var or unreadable file fails typed at connect() with
79// LLM_ERR_SECRET_UNRESOLVED; the secret value (and the failure detail)
80// never appears in logs, errors, or getSystemStatus(). An inline `apikey`
81// attribute or <apikey> child is a typed configuration error
82// (LLM_ERR_CONFIG_INLINE_SECRET).
83//
84// C13 constraint fields (max tokens/request, max requests/min, max
85// concurrent, optional cost ceiling) are parsed and stored only — no
86// enforcement logic in this step.
87//
88// Networking mode A (blocking request/reply over NetworkManager::
89// makeHTTPRequest) is implemented in step 1.1b: connect() validates config +
90// resolved secret and marks the connection live (endpoint reachability is
91// checked lazily, on the first interact() — the underlying HTTP client is
92// connectionless/blocking, so there is nothing to dial eagerly); interact()
93// shapes the request from <requesttemplate> (%model%/%input%) and the
94// configured headers (%apikey%/%model%), sends it, and extracts the reply
95// text via the <responsetemplate> JSON path. reset() drops the live state
96// but retains config and the final stats record (ended=true). Streaming
97// (mode B) is step 1.2; constraint ENFORCEMENT is a later step.
98// ============================================================================
99
105 LLM_ERR_NOT_CONFIGURED, // API used before a successful configure()
106 LLM_ERR_NOT_CONNECTED, // no live connection (networking is step 1.1b)
107 LLM_ERR_NOT_IMPLEMENTED, // behaviour deferred to a later step
108 LLM_ERR_CONFIG_EMPTY, // missing/empty <llm> element
109 LLM_ERR_CONFIG_NOT_LLM, // element is not an <llm> element
110 LLM_ERR_CONFIG_PARSE, // XML string did not parse
111 LLM_ERR_CONFIG_NO_NAME, // <llm> is a NAMED connection: name required
112 LLM_ERR_CONFIG_NO_VENDOR, // missing <llmvendor> sub-block
113 LLM_ERR_CONFIG_UNKNOWN_VENDOR, // unrecognised <llmvendor type="...">
114 LLM_ERR_CONFIG_NO_ENDPOINT, // missing endpoint URL
115 LLM_ERR_CONFIG_INLINE_SECRET, // inline apikey in XML — use apikeyref
116 LLM_ERR_CONFIG_INVALID, // other structural configuration error
117 LLM_ERR_UNKNOWN_MODEL, // selectModel() name not in the model list
118 LLM_ERR_SECRET_UNRESOLVED, // apikeyref set but no secret could be resolved
119 LLM_ERR_NO_INPUT, // interact() called with empty input
120 LLM_ERR_HTTP_UNREACHABLE, // endpoint unreachable / no HTTP reply
121 LLM_ERR_HTTP, // endpoint replied with a non-200 status
122 LLM_ERR_REPLY_PARSE, // reply body missing/malformed vs responsetemplate
123 LLM_ERR_CONFIG_BAD_TRANSPORT, // unrecognised <llmvendor transport="...">
124 LLM_ERR_STREAM_CANCELLED // streaming interact stopped early by the sink
125};
126
130const char* LLMResultText(LLMResult result);
131
143
147const char* LLMVendorTypeText(LLMVendorType type);
148
158 LLM_STREAM_NONE = 0, // unconfigured / vendor does not stream
159 LLM_STREAM_SSE, // chunked transfer + text/event-stream SSE events
160 LLM_STREAM_EVENTSTREAM, // AWS/Bedrock binary eventstream framing
161 LLM_STREAM_CHUNKED // raw chunked-transfer body, no event framing
162};
163
167const char* LLMStreamTransportText(LLMStreamTransport transport);
168
175typedef bool (*LLMStreamCallback)(const char* token, uint32 size, void* userData);
176
180struct LLMHeader {
181 std::string name;
182 std::string value;
183};
184
199
203struct LLMStats {
204 uint64 uptimeMS;
205 uint64 bytesSent;
208 uint64 tokensIn;
209 uint64 tokensOut;
210 uint64 replyChunks; // streamed token chunks delivered (mode B)
211 double cost;
212 bool ended;
213
216 cost(0.0), ended(false) {}
217};
218
219class LLMConnection;
220
230typedef void (*LLMOwnerEndCallback)(void* owner, LLMConnection* connection,
231 const LLMStats& finalStats);
232
236public:
239
240 // ---- Ownership ----------------------------------------------------------
245 void setOwner(void* owner, LLMOwnerEndCallback callback);
246
247 // ---- Configuration -----------------------------------------------------
257 LLMResult configure(const XMLNode& llmNode);
258
264 LLMResult configureFromString(const char* xml);
265
267 bool isConfigured() const { return configured; }
269 LLMResult getLastError() const { return lastError; }
270
271 // ---- Parsed fields -----------------------------------------------------
274 const std::string& getName() const { return name; }
275 LLMVendorType getVendorType() const { return vendorType; }
276 const std::string& getVendorTypeName() const { return vendorTypeName; }
277 const std::string& getEndpoint() const { return endpoint; }
281 bool isGlobal() const { return globalConnection; }
282 const std::string& getSchemaVersion() const { return schemaVersion; }
285 const std::string& getAPIKeyRef() const { return apiKeyRef; }
286
289 bool hasResolvedSecret() const { return !apiKeySecret.empty(); }
290 const std::string& getRequestTemplate() const { return requestTemplate; }
291 const std::string& getResponseTemplate() const { return responseTemplate; }
292 const std::vector<LLMHeader>& getHeaders() const { return headers; }
293 const LLMConstraints& getConstraints() const { return constraints; }
294
295 // ---- Models ------------------------------------------------------------
298 std::vector<std::string> listModels() const;
299
303 LLMResult selectModel(const char* modelName);
304
307 const std::string& getSelectedModel() const { return selectedModel; }
308
309 // ---- Networking --------------------------------------------------------
318
326
337 LLMResult interact(const char* input, std::string& reply);
349 LLMResult interactStream(const char* input, LLMStreamCallback callback,
350 void* userData, std::string* fullReply = NULL);
351
353 bool isConnected() const { return connected; }
354
357 LLMStreamTransport getStreamTransport() const { return streamTransport; }
358
359 // ---- Stats -------------------------------------------------------------
363 LLMStats stats() const;
364
365private:
366 LLMResult fail(LLMResult result);
367 LLMResult interactFail(LLMResult result);
368 std::string applyTemplates(const std::string& text, const char* input) const;
369 // Extract a dotted/indexed JSON path (e.g. choices[0].message.content).
370 static bool extractJSONPath(const char* json, uint32 size, const char* path,
371 std::string& out);
372 void clearConfig();
373 LLMResult parseVendor(const XMLNode& vendorNode);
374 // Resolve transport from an explicit attribute value (may be NULL/empty)
375 // + the already-parsed vendorType default. Sets streamTransport.
376 LLMResult resolveStreamTransport(const char* transportAttr);
377 void resolveSecret();
378 static bool nodeHasInlineSecret(const XMLNode& node);
379 // 1.2c-ii: reads the HTTP reply header off `con`, then drives the
380 // transport decoder, delivering token text to callback (con is a
381 // NetworkConnection*, typed void* to keep networking out of this header).
382 LLMResult pumpStreamReply(void* con, LLMStreamCallback callback,
383 void* userData, std::string* fullReply);
384
385 bool configured;
386 bool connected;
387 LLMResult lastError;
388
389 std::string name;
390 LLMVendorType vendorType;
391 std::string vendorTypeName;
392 std::string endpoint;
393 LLMStreamTransport streamTransport;
394 bool globalConnection;
395 std::string schemaVersion;
396 std::string apiKeyRef;
397 std::string secretsFile;
398 std::string apiKeySecret; // resolved secret — never logged/exposed
399 std::string requestTemplate;
400 std::string responseTemplate;
401 std::vector<LLMHeader> headers;
402 LLMConstraints constraints;
403
404 std::vector<std::string> models;
405 std::string selectedModel;
406
407 // Extract vendor usage token counts from one reply/event JSON body into
408 // currentStats (mode A whole reply and mode B per-event share this).
409 void accountUsageJSON(const char* json, uint32 size);
410
411 // Invoke the owner-end callback (if set and not yet fired for this cycle).
412 void notifyOwnerEnded();
413
414 void* owner; // opaque owner handle (1.2d-i)
415 LLMOwnerEndCallback ownerEndCallback;
416 bool ownerNotified; // callback fired for current cycle
417
418 LLMStats currentStats;
419 volatile bool cancelRequested; // set by reset() to abort a live stream
420 uint64 connectTimeMS; // wall-clock ms at connect() (0 = never)
421 class NetworkManager* netManager; // owned; created on connect()
422};
423
424} // namespace cmlabs
425
426#endif // !defined(_LLMCONNECTION_H_)
Cross-platform utility toolbox for CMSDK: threading, synchronization, shared memory,...
Named, node-owned connection to an LLM endpoint.
const std::string & getVendorTypeName() const
LLMResult getLastError() const
Most recent typed error recorded by this connection.
const std::string & getName() const
Connection name from <llm name="...">; code looks connections up by this name.
LLMResult interact(const char *input, std::string &reply)
One blocking request/reply exchange (mode A).
LLMResult configure(const XMLNode &llmNode)
Configure from a parsed <llm> element.
const std::string & getSelectedModel() const
Currently selected model (the <models default="..."> value until selectModel() is called).
const std::vector< LLMHeader > & getHeaders() const
void setOwner(void *owner, LLMOwnerEndCallback callback)
Set, or clear with NULLs, the owner-end callback.
LLMResult selectModel(const char *modelName)
Select a model by name from the parsed list.
LLMResult configureFromString(const char *xml)
Convenience overload: parse the <llm> element from an XML string, then configure().
const std::string & getEndpoint() const
const LLMConstraints & getConstraints() const
LLMResult connect()
Validate configuration and resolved secret, and mark the connection live.
LLMVendorType getVendorType() const
LLMResult interactStream(const char *input, LLMStreamCallback callback, void *userData, std::string *fullReply=NULL)
Streaming request (mode B): delivers decoded tokens as they arrive.
LLMStreamTransport getStreamTransport() const
Streaming transport resolved at configure() time.
std::vector< std::string > listModels() const
Model names from the parsed <models> list.
const std::string & getSchemaVersion() const
LLMResult reset()
Drop the live connection.
bool hasResolvedSecret() const
true if apikeyref resolved to a secret.
const std::string & getResponseTemplate() const
const std::string & getRequestTemplate() const
bool isConfigured() const
true once configure() has succeeded.
bool isGlobal() const
Value of the global attribute.
bool isConnected() const
true between a successful connect() and reset().
const std::string & getAPIKeyRef() const
The credential reference text, e.g.
LLMStats stats() const
Snapshot of this connection's statistics.
Central owner of all channels, listeners and connections in a process.
LLMStreamTransport
Streaming wire format used by mode B.
@ LLM_STREAM_EVENTSTREAM
@ LLM_STREAM_NONE
@ LLM_STREAM_CHUNKED
LLMVendorType
Vendor family recognised from <llmvendor type="...">.
@ LLM_VENDOR_CUSTOM
@ LLM_VENDOR_GOOGLE
@ LLM_VENDOR_OPENAI
@ LLM_VENDOR_NONE
@ LLM_VENDOR_BEDROCK
@ LLM_VENDOR_ANTHROPIC
const char * LLMResultText(LLMResult result)
Human-readable name for an LLMResult, for logs and error reporting.
const char * LLMStreamTransportText(LLMStreamTransport transport)
Human-readable name for an LLMStreamTransport.
const char * LLMVendorTypeText(LLMVendorType type)
Human-readable name for an LLMVendorType.
bool(* LLMStreamCallback)(const char *token, uint32 size, void *userData)
Sink for incremental streaming output (mode B).
void(* LLMOwnerEndCallback)(void *owner, LLMConnection *connection, const LLMStats &finalStats)
Owner-notification hook, fired when a connection ends.
LLMResult
Typed result codes returned by every LLMConnection operation.
@ LLM_ERR_CONFIG_EMPTY
@ LLM_ERR_CONFIG_NO_VENDOR
@ LLM_ERR_CONFIG_INVALID
@ LLM_ERR_CONFIG_NO_ENDPOINT
@ LLM_ERR_NOT_CONNECTED
@ LLM_ERR_CONFIG_PARSE
@ LLM_ERR_SECRET_UNRESOLVED
@ LLM_ERR_CONFIG_BAD_TRANSPORT
@ LLM_ERR_CONFIG_NOT_LLM
@ LLM_ERR_CONFIG_UNKNOWN_VENDOR
@ LLM_ERR_CONFIG_INLINE_SECRET
@ LLM_ERR_UNKNOWN_MODEL
@ LLM_ERR_NOT_IMPLEMENTED
@ LLM_ERR_NOT_CONFIGURED
@ LLM_ERR_CONFIG_NO_NAME
@ LLM_ERR_HTTP_UNREACHABLE
@ LLM_ERR_REPLY_PARSE
@ LLM_ERR_STREAM_CANCELLED
@ LLM_ERR_NO_INPUT
Per-connection constraints declared on the <llm> element.
One configured HTTP header.
Connection statistics: uptime, bytes, requests, tokens and cost.
Small recursive XML DOM parser (XMLNode) used by CMSDK for all PsySpec XML parsing.