gem/tests/chat_test.c
2026-08-30 18:24:55 -06:00

163 lines
6.4 KiB
C

#include "gem/chat.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define CHECK(condition) do { if (!(condition)) { \
fprintf(stderr, "check failed at %s:%d: %s\n", __FILE__, __LINE__, #condition); return 1; \
} } while (0)
static struct gem_sampler_config defaults(void)
{
return (struct gem_sampler_config){
.temperature = 0.8f, .top_k = 40, .top_p = 0.95f,
.repeat_penalty = 1.0f, .repeat_last_n = 64, .seed = 1,
};
}
static int basic_prompt(void)
{
const char body[] =
"{\"model\":\"gemma-4-31b\",\"stream\":true,"
"\"messages\":[{\"role\":\"system\",\"content\":\" Be useful. \"},"
"{\"role\":\"user\",\"content\":\"Hello\\nworld\"}],"
"\"stream_options\":{\"include_usage\":true},\"temperature\":0} ";
struct gem_chat_request request;
struct gem_sampler_config config = defaults();
char error[256];
CHECK(gem_chat_request_parse(&request, body, sizeof(body) - 1, 128, &config,
error, sizeof(error)) == 0);
CHECK(strcmp(request.model, "gemma-4-31b") == 0);
CHECK(strcmp(request.prompt,
"<|turn>system\nBe useful.<turn|>\n"
"<|turn>user\nHello\nworld<turn|>\n<|turn>model\n") == 0);
CHECK(request.stream && request.include_usage && request.max_tokens == 128);
CHECK(request.sampler.temperature == 0.0f);
gem_chat_request_destroy(&request);
return 0;
}
static int tool_prompt(void)
{
const char body[] =
"{\"model\":\"gem\",\"stream\":true,\"reasoning_effort\":\"high\","
"\"messages\":[{\"role\":\"user\",\"content\":\"Weather?\"}],"
"\"tools\":[{\"type\":\"function\",\"function\":{"
"\"name\":\"weather\",\"description\":\"Gets weather\","
"\"parameters\":{\"type\":\"object\",\"properties\":{"
"\"city\":{\"type\":\"string\",\"description\":\"City\"}},"
"\"required\":[\"city\"]}}}]}";
struct gem_chat_request request;
struct gem_sampler_config config = defaults();
char error[256];
CHECK(gem_chat_request_parse(&request, body, sizeof(body) - 1, 64, &config,
error, sizeof(error)) == 0);
CHECK(strstr(request.prompt, "<|turn>system\n<|think|>\n") == request.prompt);
CHECK(strstr(request.prompt, "<|tool>declaration:weather{") != NULL);
CHECK(strstr(request.prompt, "description:<|\"|>Gets weather<|\"|>") != NULL);
CHECK(strstr(request.prompt, "type:<|\"|>OBJECT<|\"|>") != NULL);
CHECK(strstr(request.prompt, "<|turn>user\nWeather?<turn|>\n<|turn>model\n") != NULL);
gem_chat_request_destroy(&request);
return 0;
}
static int tool_round_trip_prompt(void)
{
const char body[] =
"{\"model\":\"gem\",\"messages\":["
"{\"role\":\"user\",\"content\":\"List files\"},"
"{\"role\":\"assistant\",\"content\":null,\"tool_calls\":[{"
"\"id\":\"call_7\",\"type\":\"function\",\"function\":{"
"\"name\":\"shell\",\"arguments\":\"{\\\"path\\\":\\\"/tmp\\\"}\"}}]},"
"{\"role\":\"tool\",\"tool_call_id\":\"call_7\",\"content\":\"a\\nb\"}]}";
struct gem_chat_request request;
struct gem_sampler_config config = defaults();
char error[256];
CHECK(gem_chat_request_parse(&request, body, sizeof(body) - 1, 64, &config,
error, sizeof(error)) == 0);
CHECK(strstr(request.prompt,
"<|tool_call>call:shell{path:<|\"|>/tmp<|\"|>}<tool_call|>") != NULL);
CHECK(strstr(request.prompt,
"<|tool_response>response:shell{value:<|\"|>a\nb<|\"|>}<tool_response|>") != NULL);
CHECK(request.prompt[strlen(request.prompt) - 1] == '>');
gem_chat_request_destroy(&request);
return 0;
}
static int tool_continuation_prompt(void)
{
const char body[] =
"{\"model\":\"gem\",\"messages\":["
"{\"role\":\"user\",\"content\":\"Where?\"},"
"{\"role\":\"assistant\",\"content\":null,\"tool_calls\":[{"
"\"id\":\"c\",\"function\":{\"name\":\"pwd\",\"arguments\":\"{}\"}}]},"
"{\"role\":\"tool\",\"tool_call_id\":\"c\",\"content\":\"/tmp\"},"
"{\"role\":\"assistant\",\"content\":\"You are in /tmp.\"},"
"{\"role\":\"user\",\"content\":\"Thanks\"}]}";
struct gem_chat_request request;
struct gem_sampler_config config = defaults();
char error[256];
CHECK(gem_chat_request_parse(&request, body, sizeof(body) - 1, 64, &config,
error, sizeof(error)) == 0);
const char *response = strstr(request.prompt, "<tool_response|>");
CHECK(response != NULL);
CHECK(strstr(response,
"<tool_response|>You are in /tmp.<turn|>\n<|turn>user\n"
"Thanks<turn|>\n<|turn>model\n") == response);
gem_chat_request_destroy(&request);
return 0;
}
static int tool_call_parse(void)
{
const char input[] =
"call:write{path:<|\"|>/tmp/a,b<|\"|>,count:2,flags:[true,false],"
"meta:{note:<|\"|>x{y}<|\"|>}}";
char *name = NULL;
char *arguments = NULL;
char error[256];
CHECK(gem_chat_tool_call_parse(input, sizeof(input) - 1, &name, &arguments,
error, sizeof(error)) == 0);
CHECK(strcmp(name, "write") == 0);
CHECK(strcmp(arguments,
"{\"path\":\"/tmp/a,b\",\"count\":2,\"flags\":[true,false],"
"\"meta\":{\"note\":\"x{y}\"}}") == 0);
free(arguments);
free(name);
static const char bare[] = "call:bash{command:pwd}";
name = NULL;
arguments = NULL;
CHECK(gem_chat_tool_call_parse(bare, sizeof(bare) - 1, &name, &arguments,
error, sizeof(error)) == 0);
CHECK(strcmp(name, "bash") == 0);
CHECK(strcmp(arguments, "{\"command\":\"pwd\"}") == 0);
free(arguments);
free(name);
return 0;
}
static int malformed(void)
{
struct gem_chat_request request;
struct gem_sampler_config config = defaults();
char error[256];
const char body[] = "{\"model\":\"gem\",\"messages\":[]}";
CHECK(gem_chat_request_parse(&request, body, sizeof(body) - 1, 64, &config,
error, sizeof(error)) != 0);
CHECK(strstr(error, "malformed") != NULL);
return 0;
}
int main(void)
{
CHECK(basic_prompt() == 0);
CHECK(tool_prompt() == 0);
CHECK(tool_round_trip_prompt() == 0);
CHECK(tool_continuation_prompt() == 0);
CHECK(tool_call_parse() == 0);
CHECK(malformed() == 0);
puts("chat tests passed");
return 0;
}