#pragma once
// SafeC Standard Library — LLM orchestration (LangChain/LangGraph/
// LangSmith-inspired).
//
// A thin layer over std::http/std::Value: LlmClient speaks the
// OpenAI-chat-completions wire shape (the de facto standard most local
// and hosted LLM servers — vLLM's own OpenAI-compatible server included —
// implement), PromptTemplate does '{var}' substitution, Chain runs a
// fixed step sequence, the Graph executor runs named nodes connected by
// fixed or conditional edges over a shared Value state (LangGraph's core
// idea), and Tracer keeps an in-memory, JSON-dumpable log of named
// input/output/timing events (LangSmith's core idea) — none of these
// call out to any hosted service themselves; verification in this
// session used a local mock HTTP server standing in for an LLM endpoint,
// the same technique std/rpc/server_fn.h's own tests used.
#include <std/serial/value.h>
#include <std/collections/vec.h>
#include <std/collections/string.h>
namespace std {
// ── LlmClient ────────────────────────────────────────────────────────────────
struct LlmMessage {
struct String role; // "system" | "user" | "assistant"
struct String content;
};
struct LlmClient {
struct String host;
unsigned short port;
struct String model;
struct String apiKey; // sent as 'Authorization: Bearer <apiKey>' when non-empty
// POSTs 'messages' (Vec<struct LlmMessage>) to
// '<client.host>:<client.port>/v1/chat/completions' as
// '{"model":..., "messages":[{"role":...,"content":...}, ...]}' and
// returns 'choices[0].message.content' from the JSON response.
// *ok is set to 0 on any transport, HTTP-status, or JSON-shape failure.
struct String chat(&Vec<struct LlmMessage> messages, ?&int ok);
static struct LlmClient new(const &char host, unsigned short port, const &char model, const &char apiKey);
};
// ── PromptTemplate ───────────────────────────────────────────────────────────
// Replaces every '{name}' occurrence in 'tmpl' with 'vars' object field
// 'name' (stringified: strings pass through as-is, numbers/bools render
// via their natural text form). A '{name}' whose key isn't present in
// 'vars' is left as literal text, unexpanded.
struct String prompt_template_render(const &char tmpl, const &Value vars);
// ── Chain ────────────────────────────────────────────────────────────────────
// A fixed sequence of Value -> Value steps, run in order — the output of
// each step becomes the input to the next.
typedef fn struct Value(const &Value input) ChainStepFn;
struct Chain {
struct Vec<ChainStepFn> steps;
void add_step(ChainStepFn step);
struct Value run(const &Value input);
void drop();
void free() consume; // = drop()
static struct Chain new();
};
// ── Graph executor ───────────────────────────────────────────────────────────
// Named nodes transform a shared Value state; each node either always
// advances to a fixed 'nextNode', or (if 'router' is set) calls 'router'
// on the post-node state to pick the next node by name. Graph::run()
// starts at the graph's entry node and keeps following edges until a
// node returns/routes to "__end__", a named node isn't found (also
// treated as end), or 'maxSteps' is hit (guards against an accidental
// infinite loop in a misconfigured graph).
typedef fn struct Value(const &Value state) GraphNodeFn;
typedef fn struct String(const &Value state) GraphRouterFn;
struct GraphNode {
struct String name;
GraphNodeFn nodeFn;
GraphRouterFn router; // NULL -> use 'nextNode' unconditionally
struct String nextNode;
};
struct Graph {
struct Vec<struct GraphNode> nodes;
struct String entryNode;
void add_node(const &char name, GraphNodeFn nodeFn, const &char nextNode);
void add_conditional_node(const &char name, GraphNodeFn nodeFn, GraphRouterFn router);
struct Value run(struct Value initialState, int maxSteps);
void drop();
void free() consume; // = drop()
static struct Graph new(const &char entryNode);
};
// ── Tracer ───────────────────────────────────────────────────────────────────
struct TraceEvent {
struct String name;
struct String inputJson;
struct String outputJson;
double durationMs;
};
struct Tracer {
struct Vec<struct TraceEvent> events;
void record(const &char name, const &Value input, const &Value output, double durationMs);
// Dumps every recorded event as a JSON array: '[{"name":...,"input":...,
// "output":...,"durationMs":...}, ...]'.
struct String to_json() const;
void drop();
void free() consume; // = drop()
static struct Tracer new();
};
} // namespace std