The LlmSendMessageEx function provides the structured form of
LlmSendMessage. It should be used by applications that need to
specify additional message properties, inspect detailed response
metadata, or preserve information such as response identifiers, token
usage, status values, elapsed time, and the actual model name returned
by the provider.
This function performs a blocking operation. It will not return until
the request has completed, an error has occurred, or the operation has
timed out. Applications that call this function from a user interface
thread should provide visual feedback while waiting for the response,
or perform the request on a background thread to maintain responsiveness.
The lpMessage and lpResponse structures must be
initialized before calling this function. At a minimum, the
dwSize member of each structure must be set to the size of the
structure, and all unused members should be initialized to zero or NULL.
If a message identifier is returned in the response structure, it is
unique within the current client session. Message identifiers are
assigned sequentially by the library, but applications should treat
them as opaque values. They are not guaranteed to be globally unique
across multiple sessions or between different clients.
This function automatically adds the message and response to the
conversation history for the session. Subsequent calls may include
previous messages as context, subject to any configured history or token
limits. To clear the conversation context, call LlmClearHistory
or LlmResetSession.
Applications that accept user input should apply appropriate
validation, filtering, or normalization before sending requests to the
service provider. The LlmValidateText function verifies that
the prompt contains valid message text and the LlmNormalizeText
function can be used to normalize text formatting. These functions
do not perform any semantic analysis of the prompt and do not provide
protection against malicious or intentionally misleading input.
Line breaks in the response text are normalized before being stored
in the client history. Line feed and carriage return characters are
converted to the standard Windows CRLF end-of-line sequence, ensuring
consistent formatting across different providers and applications.
This also helps ensure compatibility with Windows common controls and
other user interface components.
Some models are capable of performing additional internal reasoning
before generating a response. The amount of reasoning performed and
whether it is exposed by the provider is model-specific. The library
returns the final response intended for the user and does not
currently expose model reasoning or intermediate processing steps.
This behavior ensures consistent results across providers, regardless
of how individual models implement or expose reasoning capabilities.
The string members of the LLM_MESSAGE and LLM_RESPONSE structures
are managed by the library. When a message is sent, any string pointers
provided in the LLM_MESSAGE structure are copied internally, and the
application is not required to preserve or maintain those buffers
after the function returns.
String pointers returned in the LLM_RESPONSE structure refer to
memory managed by the client session. These values remain valid until
the message history is cleared or the session is reset or disconnected.
Applications should not attempt to free or modify these strings.
The contents of these strings must be treated as read-only. They
are owned by the library, and modifying them may result in access
violations or other undefined behavior. If your application needs to
retain a string beyond the lifetime of the session or modify its
contents, it should create its own copy.
#include "cstools12.h"
#include <stdio.h>
int _tmain(void)
{
HCLIENT hClient = INVALID_CLIENT;
DWORD dwError = NO_ERROR;
// Initialize the SocketTools library
if (!LlmInitialize(CSTOOLS12_LICENSE_KEY, NULL))
{
_tprintf(_T("Initialization failed: %lu\n"), LlmGetLastError());
return 1;
}
// Establish a connection to OpenAI using the default endpoint
// and a lightweight model
hClient = LlmConnect(
LLM_PROVIDER_OPENAI,
LLM_OPTION_DEFAULT,
LLM_TIMEOUT,
NULL, // Use default base URL
_T("gpt-5.4-nano"), // Model name
_T("%OPENAI_API_KEY%") // Provider API key
);
if (hClient == INVALID_CLIENT)
{
dwError = LlmGetLastError();
_tprintf(_T("Unable to connect, error %lu\n"), dwError);
LlmUninitialize();
return 1;
}
LLM_MESSAGE clientMessage;
ZeroMemory(&clientMessage, sizeof(LLM_MESSAGE));
clientMessage.dwSize = sizeof(clientMessage);
clientMessage.lpszContent = _T("What is the capital city of Denmark?");
clientMessage.nContentLength = _tcslen(clientMessage.lpszContent);
LLM_RESPONSE serverResponse;
ZeroMemory(&serverResponse, sizeof(serverResponse));
serverResponse.dwSize = sizeof(serverResponse);
if (LlmSendMessageEx(hClient, 0, &clientMessage, &serverResponse))
{
_tprintf(_T("Flags: 0x%08lX\n"), serverResponse.dwFlags);
_tprintf(_T("Status: %u\n"), serverResponse.dwStatus);
_tprintf(_T("Elapsed: %u ms\n"), serverResponse.dwElapsed);
_tprintf(_T("Output Tokens: %u\n"), serverResponse.nOutputTokens);
_tprintf(_T("Content Length: %u\n"), serverResponse.nContentLength);
_tprintf(_T("Response ID: %s\n"), (serverResponse.lpszResponseId ? serverResponse.lpszResponseId : _T("None")));
_tprintf(_T("Model Name: %s\n"), (serverResponse.lpszModelName ? serverResponse.lpszModelName : _T("None")));
_tprintf(_T("Status Text: %s\n"), (serverResponse.lpszStatusText ? serverResponse.lpszStatusText : _T("None")));
if (serverResponse.nContentLength > 0)
_tprintf(_T("\n%s\n"), serverResponse.lpszContent);
}
else
{
dwError = LlmGetLastError();
_tprintf(_T("Request failed, error %lu\n"), dwError);
}
// Clean up
LlmDisconnect(hClient);
LlmUninitialize();
return 0;
}