Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ llama-cpp-log-decoder = { path = "llama-cpp-log-decoder", version = "=0.27.0" }
llama-cpp-test-harness = { path = "llama-cpp-test-harness", version = "=0.27.0" }
llama-cpp-test-harness-macros = { path = "llama-cpp-test-harness-macros", version = "=0.27.0" }
llama-cpp-test-model-provisioning = { path = "llama-cpp-test-model-provisioning", version = "=0.27.0" }
llama-cpp-fault-injection = { path = "llama-cpp-fault-injection" }
llama-cpp-fault-injection = { path = "llama-cpp-fault-injection", version = "=0.27.0" }
llama-cpp-wrapper-error-fixture = { path = "llama-cpp-wrapper-error-fixture" }
llama-cpp-wrapper-sources = { path = "llama-cpp-wrapper-sources", version = "=0.27.0" }
llguidance = "=1.7.0"
Expand Down
265 changes: 148 additions & 117 deletions llama-cpp-bindings-sys/wrapper_chat_session.cpp

Large diffs are not rendered by default.

82 changes: 39 additions & 43 deletions llama-cpp-bindings-sys/wrapper_chat_session.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ extern "C" {
struct llama_rs_chat_session;
typedef struct llama_rs_chat_session * llama_rs_chat_session_handle;

struct llama_rs_chat_spans;
typedef struct llama_rs_chat_spans * llama_rs_chat_spans_handle;
struct llama_rs_chat_reading;
typedef struct llama_rs_chat_reading * llama_rs_chat_reading_handle;

typedef enum llama_rs_chat_tool_choice {
LLAMA_RS_CHAT_TOOL_CHOICE_AUTO = 0,
Expand Down Expand Up @@ -46,8 +46,6 @@ typedef enum llama_rs_chat_session_create_status {
LLAMA_RS_CHAT_SESSION_CREATE_NULL_TOOLS_JSON_ARG,
LLAMA_RS_CHAT_SESSION_CREATE_NULL_OUT_SESSION_ARG,
LLAMA_RS_CHAT_SESSION_CREATE_NULL_OUT_ERROR_ARG,
LLAMA_RS_CHAT_SESSION_CREATE_TOOLS_NOT_JSON,
LLAMA_RS_CHAT_SESSION_CREATE_TOOLS_INVALID,
LLAMA_RS_CHAT_SESSION_CREATE_TEMPLATE_CANNOT_SERVE_REQUEST,
LLAMA_RS_CHAT_SESSION_CREATE_ERROR_STRING_ALLOCATION_FAILED,
LLAMA_RS_CHAT_SESSION_CREATE_LLAMA_CPP_OUT_OF_MEMORY,
Expand All @@ -58,7 +56,9 @@ llama_rs_chat_session_create_status llama_rs_chat_session_create(
llama_rs_chat_templates_handle templates,
const struct llama_vocab * vocab,
const char * messages_json,
size_t messages_json_length,
const char * tools_json,
size_t tools_json_length,
llama_rs_chat_session_options options,
llama_rs_chat_session_handle * out_session,
char ** out_error);
Expand Down Expand Up @@ -118,28 +118,6 @@ const char * llama_rs_chat_session_thinking_end_tag(
size_t index,
size_t * out_length);

typedef enum llama_rs_chat_session_parse_status {
LLAMA_RS_CHAT_SESSION_PARSE_OK = 0,
LLAMA_RS_CHAT_SESSION_PARSE_NULL_SESSION_ARG,
LLAMA_RS_CHAT_SESSION_PARSE_NULL_TEXT_ARG,
LLAMA_RS_CHAT_SESSION_PARSE_NULL_BYTE_TOKENS_ARG,
LLAMA_RS_CHAT_SESSION_PARSE_NULL_OUT_HANDLE_ARG,
LLAMA_RS_CHAT_SESSION_PARSE_NULL_OUT_ERROR_ARG,
LLAMA_RS_CHAT_SESSION_PARSE_OUTPUT_DOES_NOT_MATCH_FORMAT,
LLAMA_RS_CHAT_SESSION_PARSE_ERROR_STRING_ALLOCATION_FAILED,
LLAMA_RS_CHAT_SESSION_PARSE_LLAMA_CPP_OUT_OF_MEMORY,
LLAMA_RS_CHAT_SESSION_PARSE_LLAMA_CPP_THREW_CXX_EXCEPTION,
} llama_rs_chat_session_parse_status;

llama_rs_chat_session_parse_status llama_rs_chat_session_parse(
llama_rs_chat_session_handle session,
const char * text,
size_t text_length,
const llama_token * byte_tokens,
bool is_partial,
llama_rs_parsed_chat_handle * out_handle,
char ** out_error);

typedef enum llama_rs_chat_span_kind {
LLAMA_RS_CHAT_SPAN_KIND_REASONING_BLOCK = 0,
LLAMA_RS_CHAT_SPAN_KIND_REASONING,
Expand All @@ -160,31 +138,49 @@ typedef struct llama_rs_chat_spans_view {
size_t parsed_length;
} llama_rs_chat_spans_view;

typedef enum llama_rs_chat_session_classify_status {
LLAMA_RS_CHAT_SESSION_CLASSIFY_OK = 0,
LLAMA_RS_CHAT_SESSION_CLASSIFY_NULL_SESSION_ARG,
LLAMA_RS_CHAT_SESSION_CLASSIFY_NULL_TEXT_ARG,
LLAMA_RS_CHAT_SESSION_CLASSIFY_NULL_BYTE_TOKENS_ARG,
LLAMA_RS_CHAT_SESSION_CLASSIFY_NULL_OUT_SPANS_ARG,
LLAMA_RS_CHAT_SESSION_CLASSIFY_NULL_OUT_ERROR_ARG,
LLAMA_RS_CHAT_SESSION_CLASSIFY_OUTPUT_DOES_NOT_MATCH_FORMAT,
LLAMA_RS_CHAT_SESSION_CLASSIFY_ERROR_STRING_ALLOCATION_FAILED,
LLAMA_RS_CHAT_SESSION_CLASSIFY_LLAMA_CPP_OUT_OF_MEMORY,
LLAMA_RS_CHAT_SESSION_CLASSIFY_LLAMA_CPP_THREW_CXX_EXCEPTION,
} llama_rs_chat_session_classify_status;

llama_rs_chat_session_classify_status llama_rs_chat_session_classify(
typedef enum llama_rs_chat_session_read_status {
LLAMA_RS_CHAT_SESSION_READ_OK = 0,
LLAMA_RS_CHAT_SESSION_READ_NULL_SESSION_ARG,
LLAMA_RS_CHAT_SESSION_READ_NULL_TEXT_ARG,
LLAMA_RS_CHAT_SESSION_READ_NULL_BYTE_TOKENS_ARG,
LLAMA_RS_CHAT_SESSION_READ_NULL_OUT_READING_ARG,
LLAMA_RS_CHAT_SESSION_READ_NULL_OUT_ERROR_ARG,
LLAMA_RS_CHAT_SESSION_READ_ERROR_STRING_ALLOCATION_FAILED,
LLAMA_RS_CHAT_SESSION_READ_LLAMA_CPP_OUT_OF_MEMORY,
LLAMA_RS_CHAT_SESSION_READ_LLAMA_CPP_THREW_CXX_EXCEPTION,
} llama_rs_chat_session_read_status;

llama_rs_chat_session_read_status llama_rs_chat_session_read(
llama_rs_chat_session_handle session,
const char * text,
size_t text_length,
const llama_token * byte_tokens,
bool is_partial,
llama_rs_chat_spans_handle * out_spans,
llama_rs_chat_reading_handle * out_reading,
char ** out_error);

llama_rs_chat_spans_view llama_rs_chat_spans_view_of(llama_rs_chat_spans_handle spans);
bool llama_rs_chat_reading_matches_format(llama_rs_chat_reading_handle reading);

llama_rs_chat_spans_view llama_rs_chat_reading_spans_view(llama_rs_chat_reading_handle reading);

const char * llama_rs_chat_reading_format_name(llama_rs_chat_reading_handle reading, size_t * out_length);

typedef enum llama_rs_chat_reading_parse_status {
LLAMA_RS_CHAT_READING_PARSE_OK = 0,
LLAMA_RS_CHAT_READING_PARSE_NULL_READING_ARG,
LLAMA_RS_CHAT_READING_PARSE_NULL_OUT_HANDLE_ARG,
LLAMA_RS_CHAT_READING_PARSE_NULL_OUT_ERROR_ARG,
LLAMA_RS_CHAT_READING_PARSE_ERROR_STRING_ALLOCATION_FAILED,
LLAMA_RS_CHAT_READING_PARSE_LLAMA_CPP_OUT_OF_MEMORY,
LLAMA_RS_CHAT_READING_PARSE_LLAMA_CPP_THREW_CXX_EXCEPTION,
} llama_rs_chat_reading_parse_status;

llama_rs_chat_reading_parse_status llama_rs_chat_reading_parse(
llama_rs_chat_reading_handle reading,
llama_rs_parsed_chat_handle * out_handle,
char ** out_error);

void llama_rs_chat_spans_free(llama_rs_chat_spans_handle spans);
void llama_rs_chat_reading_free(llama_rs_chat_reading_handle reading);

typedef enum llama_rs_regex_escape_status {
LLAMA_RS_REGEX_ESCAPE_OK = 0,
Expand Down
27 changes: 26 additions & 1 deletion llama-cpp-bindings-sys/wrapper_chat_templates.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,34 @@
#include <common/chat.h>
#include <llama.h>

#include <exception>
#include <memory>
#include <new>
#include <string>

namespace {
auto default_template_source_of(const struct llama_model * model, const char * template_override) -> std::string {
if (*template_override != '\0') {
return template_override;
}

return llama_model_chat_template(model, nullptr);
}

auto templates_reporting_the_parse_error(const struct llama_model * model, const char * template_override)
-> common_chat_templates_ptr {
try {
return common_chat_templates_init(model, std::string(template_override));
} catch (const std::bad_alloc &) {
throw;
} catch (const std::exception &) {
const auto reparsed_template = std::make_unique<common_chat_template>(
default_template_source_of(model, template_override), std::string(), std::string());
throw;
}
}
} // namespace

extern "C" auto llama_rs_chat_templates_init(
const struct llama_model * model,
const char * template_override,
Expand Down Expand Up @@ -37,7 +62,7 @@ extern "C" auto llama_rs_chat_templates_init(

try {
auto templates = std::make_unique<llama_rs_chat_templates>();
templates->templates = common_chat_templates_init(model, std::string(template_override));
templates->templates = templates_reporting_the_parse_error(model, template_override);

*out_templates = templates.release();

Expand Down
5 changes: 3 additions & 2 deletions llama-cpp-bindings-sys/wrapper_fault_injection.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ typedef enum llama_rs_fault_point {
LLAMA_RS_FAULT_POINT_BACKEND_DEVICE_TYPE_UNKNOWN,
LLAMA_RS_FAULT_POINT_BATCH_EXT_ASSIGN_ENTRIES_THROWS,
LLAMA_RS_FAULT_POINT_BATCH_EXT_INIT_THROWS,
LLAMA_RS_FAULT_POINT_CHAT_SESSION_CLASSIFY_THROWS,
LLAMA_RS_FAULT_POINT_CHAT_READING_FORMAT_NAME_NOT_UTF8,
LLAMA_RS_FAULT_POINT_CHAT_READING_PARSE_THROWS,
LLAMA_RS_FAULT_POINT_CHAT_SESSION_GRAMMAR_NOT_UTF8,
LLAMA_RS_FAULT_POINT_CHAT_SESSION_GRAMMAR_TRIGGER_TYPE_UNRECOGNIZED,
LLAMA_RS_FAULT_POINT_CHAT_SESSION_GRAMMAR_TRIGGER_VALUE_NOT_UTF8,
LLAMA_RS_FAULT_POINT_CHAT_SESSION_PARSE_THROWS,
LLAMA_RS_FAULT_POINT_CHAT_SESSION_PROMPT_PART_TEXT_NOT_UTF8,
LLAMA_RS_FAULT_POINT_CHAT_SESSION_READ_THROWS,
LLAMA_RS_FAULT_POINT_GET_LOGITS_ITH_RETURNS_NULL,
LLAMA_RS_FAULT_POINT_LLGUIDANCE_FACTORY_FAILS,
LLAMA_RS_FAULT_POINT_MTMD_EVAL_CHUNK_SINGLE_THROWS,
Expand Down
63 changes: 28 additions & 35 deletions llama-cpp-bindings-tests/src/chat_answer.rs
Original file line number Diff line number Diff line change
@@ -1,39 +1,32 @@
use llama_cpp_bindings::chat_output_completeness::ChatOutputCompleteness;
use llama_cpp_bindings::chat_session::ChatSession;
use llama_cpp_bindings::chat_message_parse_outcome::ChatMessageParseOutcome;
use llama_cpp_bindings_types::token_usage::TokenUsage;

use crate::classify_sample_loop_outcome::ClassifySampleLoopOutcome;

fn thinking_tags(session: &ChatSession<'_>) -> Vec<String> {
let mut tags = session
.thinking_end_tags()
.expect("the session's thinking end tags must be UTF-8");

tags.push(
session
.thinking_start_tag()
.expect("the session's thinking start tag must be UTF-8"),
);

tags
}

/// A generated answer to a chat session, with what the classifier attributed to each section.
/// A generated answer to a chat session, with what the classifier attributed to each section,
/// the session's thinking tags, and the classifier's own final parse of the answer.
#[derive(Debug)]
pub struct ChatAnswer {
pub outcome: ClassifySampleLoopOutcome,
pub usage: TokenUsage,
pub thinking_tags: Vec<String>,
pub parse: ChatMessageParseOutcome,
}

impl ChatAnswer {
/// Asserts that every generated token is content, and that no thinking tag of `session`
/// Asserts that every generated token is content, and that no thinking tag of the session
/// reached the content stream.
///
/// # Panics
///
/// Panics when the answer reasons, holds a token of no section, or leaks a thinking tag.
pub fn assert_content_without_reasoning(&self, session: &ChatSession<'_>) {
let Self { outcome, usage } = self;
pub fn assert_content_without_reasoning(&self) {
let Self {
outcome,
usage,
thinking_tags,
..
} = self;

assert_ne!(outcome.generated.text(), "", "{outcome:?}");
assert!(outcome.observed_content > 0, "{outcome:?}");
Expand All @@ -47,16 +40,16 @@ impl ChatAnswer {
"{usage:?}"
);

for tag in thinking_tags(session) {
for tag in thinking_tags {
assert!(
!outcome.content_stream.contains(&tag),
!outcome.content_stream.contains(tag),
"the content stream leaked the thinking tag {tag:?}; {outcome:?}"
);
}
}

/// Asserts that the answer reasons, and that the reasoning and content streams are exactly
/// what the session's parser extracts, apart from `prompted_reasoning`, the reasoning text
/// what the classifier's own final parse extracts, apart from `prompted_reasoning`, the reasoning text
/// the generation prompt itself holds, which the parser shows in front of the first
/// generated text.
///
Expand All @@ -65,15 +58,15 @@ impl ChatAnswer {
/// Panics when the parser fails on or does not recognize the answer, or when the answer
/// does not reason, holds a token of no section, leaks a thinking tag, or streams text the
/// parser does not extract.
pub fn assert_reasoning_matches_parse(
&self,
session: &ChatSession<'_>,
prompted_reasoning: &str,
) {
let Self { outcome, usage } = self;
let parsed = session
.parse(&outcome.generated, ChatOutputCompleteness::Finished)
.expect("the session's parser must parse the generated answer")
pub fn assert_reasoning_matches_parse(&self, prompted_reasoning: &str) {
let Self {
outcome,
usage,
thinking_tags,
parse,
} = self;
let parsed = parse
.clone()
.into_recognized()
.expect("the session's parser must recognize the generated answer");

Expand All @@ -96,9 +89,9 @@ impl ChatAnswer {
);
assert_eq!(outcome.content_stream, parsed.content);

for tag in thinking_tags(session) {
assert!(!outcome.reasoning_stream.contains(&tag), "{outcome:?}");
assert!(!outcome.content_stream.contains(&tag), "{outcome:?}");
for tag in thinking_tags {
assert!(!outcome.reasoning_stream.contains(tag), "{outcome:?}");
assert!(!outcome.content_stream.contains(tag), "{outcome:?}");
}
}
}
35 changes: 30 additions & 5 deletions llama-cpp-bindings-tests/src/chat_answer_generation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,33 @@ use llama_cpp_test_harness::llama_fixture::LlamaFixture;
use crate::chat_answer::ChatAnswer;
use crate::classify_sample_loop::ClassifySampleLoop;

fn thinking_tags(session: &ChatSession) -> Vec<String> {
let mut tags = session
.thinking_end_tags()
.expect("the session's thinking end tags must be UTF-8");

tags.push(
session
.thinking_start_tag()
.expect("the session's thinking start tag must be UTF-8"),
);

tags
}

/// Generates the answer to a chat session from its tokenized prompt, classifying every token.
pub struct ChatAnswerGeneration<'borrow, 'model> {
pub fixture: &'borrow LlamaFixture<'model>,
pub session: &'borrow ChatSession<'model>,
pub session: ChatSession,
pub sampler: &'borrow mut LlamaSampler,
pub max_generated_tokens: i32,
}

impl ChatAnswerGeneration<'_, '_> {
/// # Panics
///
/// Panics when creating the context, tokenizing or decoding the prompt, or generating the
/// answer fails.
/// Panics when creating the context, tokenizing or decoding the prompt, generating the
/// answer, or parsing the classifier's final reading fails.
#[must_use]
pub fn run(self) -> ChatAnswer {
let Self {
Expand All @@ -33,9 +47,11 @@ impl ChatAnswerGeneration<'_, '_> {
.build_context()
.expect("the fixture's context must build");
let prompt_tokens = session
.tokenize_prompt()
.tokenize_prompt(fixture.model)
.expect("the session's prompt must tokenize");
let mut classifier = SampledTokenClassifier::for_chat_session(session);
let thinking_tags = thinking_tags(&session);
let mut classifier = SampledTokenClassifier::for_chat_session(fixture.model, session)
.expect("the session must read its empty answer");
let mut batch = LlamaBatch::new(ContextLimits::of(&context));

classifier
Expand All @@ -57,9 +73,18 @@ impl ChatAnswerGeneration<'_, '_> {
}
.run();

let parse = classifier
.latest_reading()
.expect("a generated answer must have been read")
.parse()
.cloned()
.expect("the classifier's final reading must parse");

ChatAnswer {
outcome,
usage: classifier.into_usage(),
thinking_tags,
parse,
}
}
}
Loading
Loading