diff --git a/sdk/runanywhere-commons/exports/RACommons.exports b/sdk/runanywhere-commons/exports/RACommons.exports index 764cbbcac..55fb08a21 100644 --- a/sdk/runanywhere-commons/exports/RACommons.exports +++ b/sdk/runanywhere-commons/exports/RACommons.exports @@ -534,16 +534,22 @@ _rac_platform_tts_set_callbacks # Kotlin bridges during the proto-byte migration (CPP_PROTO_OWNERSHIP.md ยง2.1). _rac_llm_cancel _rac_llm_component_cancel +_rac_llm_component_append_context _rac_llm_component_cleanup +_rac_llm_component_clear_context _rac_llm_component_create _rac_llm_component_destroy _rac_llm_component_generate +_rac_llm_component_generate_from_context _rac_llm_component_generate_stream _rac_llm_component_get_model_id +_rac_llm_component_inject_system_prompt _rac_llm_component_is_loaded _rac_llm_component_load_model _rac_llm_component_supports_streaming _rac_llm_component_unload +_rac_llm_append_context_lifecycle +_rac_llm_clear_context_lifecycle _rac_llm_create _rac_llm_generate _rac_llm_generate_stream @@ -949,7 +955,9 @@ _rac_llm_component_load_lora _rac_llm_component_remove_lora _rac_llm_destroy _rac_llm_generate_from_context +_rac_llm_generate_from_context_proto _rac_llm_get_info +_rac_llm_inject_system_prompt_lifecycle _rac_llm_initialize _rac_llm_inject_system_prompt _rac_llm_platform_create diff --git a/sdk/runanywhere-commons/include/rac/features/llm/rac_llm_component.h b/sdk/runanywhere-commons/include/rac/features/llm/rac_llm_component.h index aa0185996..ce3caa642 100644 --- a/sdk/runanywhere-commons/include/rac/features/llm/rac_llm_component.h +++ b/sdk/runanywhere-commons/include/rac/features/llm/rac_llm_component.h @@ -226,6 +226,64 @@ RAC_API rac_lifecycle_state_t rac_llm_component_get_state(rac_handle_t handle); RAC_API rac_result_t rac_llm_component_get_metrics(rac_handle_t handle, rac_lifecycle_metrics_t* out_metrics); +// ============================================================================= +// ADAPTIVE CONTEXT API +// ============================================================================= + +/** + * @brief Inject a system prompt into the loaded model's adaptive context + * + * Clears existing KV cache, then seeds it with the given prompt. + * Optional - returns RAC_ERROR_NOT_SUPPORTED if backend doesn't support it. + * + * @param handle Component handle + * @param prompt System prompt text + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_llm_component_inject_system_prompt(rac_handle_t handle, + const char* prompt); + +/** + * @brief Append text to the loaded model's adaptive context + * + * Does not clear existing KV state - accumulates context incrementally. + * Optional - returns RAC_ERROR_NOT_SUPPORTED if backend doesn't support it. + * + * @param handle Component handle + * @param text Text to append + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_llm_component_append_context(rac_handle_t handle, const char* text); + +/** + * @brief Generate a response from accumulated adaptive context + * + * Unlike rac_llm_component_generate(), this does not clear the KV cache first. + * Use after inject_system_prompt + append_context to generate from accumulated state. + * Optional - returns RAC_ERROR_NOT_SUPPORTED if backend doesn't support it. + * + * @param handle Component handle + * @param query Query/suffix text to append before generation + * @param options Generation options (can be NULL for defaults) + * @param out_result Output: Generation result (caller must free with rac_llm_result_free) + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_llm_component_generate_from_context(rac_handle_t handle, + const char* query, + const rac_llm_options_t* options, + rac_llm_result_t* out_result); + +/** + * @brief Clear all adaptive context state + * + * Resets the LLM's context for a fresh adaptive query cycle. + * Optional - returns RAC_ERROR_NOT_SUPPORTED if backend doesn't support it. + * + * @param handle Component handle + * @return RAC_SUCCESS or error code + */ +RAC_API rac_result_t rac_llm_component_clear_context(rac_handle_t handle); + // ============================================================================= // LORA ADAPTER API // ============================================================================= diff --git a/sdk/runanywhere-commons/include/rac/features/llm/rac_llm_service.h b/sdk/runanywhere-commons/include/rac/features/llm/rac_llm_service.h index ed718edb7..ae016d7b1 100644 --- a/sdk/runanywhere-commons/include/rac/features/llm/rac_llm_service.h +++ b/sdk/runanywhere-commons/include/rac/features/llm/rac_llm_service.h @@ -10,7 +10,8 @@ * - rac_llm_service_ops_t and rac_llm_service_t: `internal`. Engine * dispatch contract. * - Proto-byte APIs (rac_llm_generate_proto, - * rac_llm_generate_stream_proto, rac_llm_cancel_proto): + * rac_llm_generate_stream_proto, rac_llm_cancel_proto, plus + * lifecycle adaptive-context helpers): * `SDK-facing default` over runanywhere.v1.LLMGenerateRequest / * LLMGenerationResult / LLMStreamEvent / SDKEvent bytes. * - Struct APIs (rac_llm_create, initialize, generate, @@ -277,6 +278,37 @@ RAC_API rac_result_t rac_llm_generate_stream_proto(const uint8_t* request_proto_ */ RAC_API rac_result_t rac_llm_cancel_proto(rac_proto_buffer_t* out_event); +/** + * @brief Inject a system prompt into the lifecycle-owned LLM's adaptive context + * + * Uses the LLM model loaded through rac_model_lifecycle_load_proto(). + */ +RAC_API rac_result_t rac_llm_inject_system_prompt_lifecycle(const char* prompt); + +/** + * @brief Append text to the lifecycle-owned LLM's adaptive context + * + * Uses the LLM model loaded through rac_model_lifecycle_load_proto(). + */ +RAC_API rac_result_t rac_llm_append_context_lifecycle(const char* text); + +/** + * @brief Generate from the lifecycle-owned LLM's accumulated adaptive context + * + * Accepts serialized runanywhere.v1.LLMGenerateRequest bytes and returns a + * serialized runanywhere.v1.LLMGenerationResult in out_result. + */ +RAC_API rac_result_t rac_llm_generate_from_context_proto(const uint8_t* request_proto_bytes, + size_t request_proto_size, + rac_proto_buffer_t* out_result); + +/** + * @brief Clear the lifecycle-owned LLM's adaptive context + * + * Uses the LLM model loaded through rac_model_lifecycle_load_proto(). + */ +RAC_API rac_result_t rac_llm_clear_context_lifecycle(void); + // ============================================================================= // ADAPTIVE CONTEXT API - For RAG and similar pipelines // ============================================================================= diff --git a/sdk/runanywhere-commons/src/features/llm/llm_module.cpp b/sdk/runanywhere-commons/src/features/llm/llm_module.cpp index 5c5a447a9..b503e738a 100644 --- a/sdk/runanywhere-commons/src/features/llm/llm_module.cpp +++ b/sdk/runanywhere-commons/src/features/llm/llm_module.cpp @@ -1076,6 +1076,87 @@ extern "C" rac_result_t rac_llm_component_cancel(rac_handle_t handle) { return RAC_SUCCESS; } +// ============================================================================= +// ADAPTIVE CONTEXT API +// ============================================================================= + +/** Seed a component's adaptive KV context with a reusable system prompt. */ +extern "C" rac_result_t rac_llm_component_inject_system_prompt(rac_handle_t handle, + const char* prompt) { + if (!handle) + return RAC_ERROR_INVALID_HANDLE; + if (!prompt) + return RAC_ERROR_INVALID_ARGUMENT; + + auto* component = reinterpret_cast(handle); + std::lock_guard lock(component->mtx); + + rac_handle_t service = rac_lifecycle_get_service(component->lifecycle); + if (!service) { + RAC_LOG_ERROR("LLM.Component", "Cannot inject system prompt: no model loaded"); + return RAC_ERROR_COMPONENT_NOT_READY; + } + + return rac_llm_inject_system_prompt(service, prompt); +} + +/** Append text to a component's existing adaptive KV context. */ +extern "C" rac_result_t rac_llm_component_append_context(rac_handle_t handle, const char* text) { + if (!handle) + return RAC_ERROR_INVALID_HANDLE; + if (!text) + return RAC_ERROR_INVALID_ARGUMENT; + + auto* component = reinterpret_cast(handle); + std::lock_guard lock(component->mtx); + + rac_handle_t service = rac_lifecycle_get_service(component->lifecycle); + if (!service) { + RAC_LOG_ERROR("LLM.Component", "Cannot append context: no model loaded"); + return RAC_ERROR_COMPONENT_NOT_READY; + } + + return rac_llm_append_context(service, text); +} + +/** Generate from a component's accumulated adaptive context. */ +extern "C" rac_result_t rac_llm_component_generate_from_context( + rac_handle_t handle, const char* query, const rac_llm_options_t* options, + rac_llm_result_t* out_result) { + if (!handle) + return RAC_ERROR_INVALID_HANDLE; + if (!query || !out_result) + return RAC_ERROR_INVALID_ARGUMENT; + + auto* component = reinterpret_cast(handle); + std::lock_guard lock(component->mtx); + + rac_handle_t service = rac_lifecycle_get_service(component->lifecycle); + if (!service) { + RAC_LOG_ERROR("LLM.Component", "Cannot generate from context: no model loaded"); + return RAC_ERROR_COMPONENT_NOT_READY; + } + + const rac_llm_options_t* effective_options = options ? options : &component->default_options; + return rac_llm_generate_from_context(service, query, effective_options, out_result); +} + +/** Clear all adaptive context retained by a component. */ +extern "C" rac_result_t rac_llm_component_clear_context(rac_handle_t handle) { + if (!handle) + return RAC_ERROR_INVALID_HANDLE; + + auto* component = reinterpret_cast(handle); + std::lock_guard lock(component->mtx); + + rac_handle_t service = rac_lifecycle_get_service(component->lifecycle); + if (!service) { + return RAC_SUCCESS; + } + + return rac_llm_clear_context(service); +} + // ============================================================================= // LORA ADAPTER API // ============================================================================= @@ -1989,6 +2070,26 @@ rac_bool_t stream_token_callback(const char* token, void* user_data) { #endif // RAC_HAVE_PROTOBUF +template +rac_result_t call_lifecycle_op(Op op, Args&&... args) { +#if !defined(RAC_HAVE_PROTOBUF) + (void)op; + return RAC_ERROR_FEATURE_NOT_AVAILABLE; +#else + rac::llm::LifecycleLlmRef ref; + rac_result_t rc = rac::llm::acquire_lifecycle_llm(&ref); + if (rc != RAC_SUCCESS) { + return rc; + } + + rc = (ref.ops && (ref.ops->*op)) + ? (ref.ops->*op)(ref.impl, std::forward(args)...) + : RAC_ERROR_NOT_SUPPORTED; + rac::llm::release_lifecycle_llm(&ref); + return rc; +#endif +} + } // namespace extern "C" { @@ -2069,22 +2170,29 @@ rac_result_t rac_llm_generate_proto(const uint8_t* request_proto_bytes, size_t r thinking_tags_from_request_or_model(request, ref, &thinking_open_tag, &thinking_close_tag); (void)rac_llm_extract_thinking_with_tags( raw_text, thinking_open_tag.empty() ? nullptr : thinking_open_tag.c_str(), - thinking_close_tag.empty() ? nullptr : thinking_close_tag.c_str(), &response, &response_len, - &thinking, &thinking_len); + thinking_close_tag.empty() ? nullptr : thinking_close_tag.c_str(), &response, + &response_len, &thinking, &thinking_len); + const std::string response_text = + response ? std::string(response, response_len) : std::string(); + const std::string thinking_text = + thinking ? std::string(thinking, thinking_len) : std::string(); + const char* response_cstr = response_text.c_str(); + const char* thinking_cstr = thinking_text.empty() ? nullptr : thinking_text.c_str(); int32_t thinking_tokens = 0; int32_t response_tokens = raw.completion_tokens; - (void)rac_llm_split_thinking_tokens(raw.completion_tokens, response, thinking, &thinking_tokens, - &response_tokens); + (void)rac_llm_split_thinking_tokens(raw.completion_tokens, response_cstr, thinking_cstr, + &thinking_tokens, &response_tokens); LLMGenerationResult result; - set_result_from_raw(ref, raw, response, response_len, thinking, thinking_len, thinking_tokens, - response_tokens, options.max_tokens, &result); - set_structured_output_if_present(response, &result); + set_result_from_raw(ref, raw, response_cstr, response_text.size(), thinking_cstr, + thinking_text.size(), thinking_tokens, response_tokens, options.max_tokens, + &result); + set_structured_output_if_present(response_cstr, &result); publish_generation_event( runanywhere::v1::GENERATION_EVENT_KIND_COMPLETED, request.prompt().c_str(), nullptr, - response, nullptr, ref.model_id, raw.completion_tokens, + response_cstr, nullptr, ref.model_id, raw.completion_tokens, raw.total_time_ms > 0 ? raw.total_time_ms : elapsed, raw.prompt_tokens > 0 ? raw.prompt_tokens : estimate_tokens(request.prompt().c_str()), ref.framework_name, static_cast(raw.tokens_per_second), @@ -2272,4 +2380,128 @@ rac_result_t rac_llm_cancel_proto(rac_proto_buffer_t* out_event) { #endif } + +/** Seed the lifecycle-owned LLM's adaptive context with a system prompt. */ +rac_result_t rac_llm_inject_system_prompt_lifecycle(const char* prompt) { + if (!prompt) { + return RAC_ERROR_INVALID_ARGUMENT; + } + return call_lifecycle_op(&rac_llm_service_ops_t::inject_system_prompt, prompt); +} + +/** Append text to the lifecycle-owned LLM's adaptive context. */ +rac_result_t rac_llm_append_context_lifecycle(const char* text) { + if (!text) { + return RAC_ERROR_INVALID_ARGUMENT; + } + return call_lifecycle_op(&rac_llm_service_ops_t::append_context, text); +} + +/** Generate a protobuf result from the lifecycle-owned LLM's adaptive context. */ +rac_result_t rac_llm_generate_from_context_proto(const uint8_t* request_proto_bytes, + size_t request_proto_size, + rac_proto_buffer_t* out_result) { + if (!out_result) { + return RAC_ERROR_NULL_POINTER; + } +#if !defined(RAC_HAVE_PROTOBUF) + (void)request_proto_bytes; + (void)request_proto_size; + return feature_unavailable(out_result); +#else + if (!valid_bytes(request_proto_bytes, request_proto_size)) { + return parse_error(out_result, "LLMGenerateRequest bytes are empty or too large"); + } + + LLMGenerateRequest request; + if (!request.ParseFromArray(parse_data(request_proto_bytes, request_proto_size), + static_cast(request_proto_size))) { + return parse_error(out_result, "failed to parse LLMGenerateRequest"); + } + + rac::llm::LifecycleLlmRef ref; + rac_result_t rc = rac::llm::acquire_lifecycle_llm(&ref); + if (rc != RAC_SUCCESS) { + return rac_proto_buffer_set_error(out_result, rc, "no lifecycle LLM model loaded"); + } + + rac::llm::clear_lifecycle_llm_cancel(&ref); + publish_generation_event(runanywhere::v1::GENERATION_EVENT_KIND_STARTED, + request.prompt().c_str(), nullptr, nullptr, nullptr, ref.model_id, 0, + 0); + + const std::string system_prompt = system_prompt_from_request(request); + std::vector stop_storage; + std::vector stop_ptrs; + std::string grammar_storage; + std::vector history_storage; + std::vector history_ptrs; + rac_llm_options_t options = options_from_request( + request, system_prompt, stop_storage, stop_ptrs, grammar_storage, history_storage, + history_ptrs); + options.streaming_enabled = RAC_FALSE; + + rac_llm_result_t raw{}; + const std::string effective_query = + rac::llm::apply_no_think_directive(request.prompt(), options.disable_thinking); + const int64_t started = now_ms(); + rc = (ref.ops && ref.ops->generate_from_context) + ? ref.ops->generate_from_context(ref.impl, effective_query.c_str(), &options, &raw) + : RAC_ERROR_NOT_SUPPORTED; + const int64_t elapsed = now_ms() - started; + + if (rc != RAC_SUCCESS) { + publish_generation_event(runanywhere::v1::GENERATION_EVENT_KIND_FAILED, + request.prompt().c_str(), nullptr, nullptr, rac_error_message(rc), + ref.model_id, 0, elapsed); + rac::llm::release_lifecycle_llm(&ref); + return rac_proto_buffer_set_error(out_result, rc, rac_error_message(rc)); + } + + const char* response = nullptr; + size_t response_len = 0; + const char* thinking = nullptr; + size_t thinking_len = 0; + const char* raw_text = raw.text ? raw.text : ""; + std::string thinking_open_tag; + std::string thinking_close_tag; + thinking_tags_from_request_or_model(request, ref, &thinking_open_tag, &thinking_close_tag); + (void)rac_llm_extract_thinking_with_tags( + raw_text, thinking_open_tag.empty() ? nullptr : thinking_open_tag.c_str(), + thinking_close_tag.empty() ? nullptr : thinking_close_tag.c_str(), &response, + &response_len, &thinking, &thinking_len); + const std::string response_text = + response ? std::string(response, response_len) : std::string(); + const std::string thinking_text = + thinking ? std::string(thinking, thinking_len) : std::string(); + const char* response_cstr = response_text.c_str(); + const char* thinking_cstr = thinking_text.empty() ? nullptr : thinking_text.c_str(); + + int32_t thinking_tokens = 0; + int32_t response_tokens = raw.completion_tokens; + (void)rac_llm_split_thinking_tokens(raw.completion_tokens, response_cstr, thinking_cstr, + &thinking_tokens, &response_tokens); + + LLMGenerationResult result; + set_result_from_raw(ref, raw, response_cstr, response_text.size(), thinking_cstr, + thinking_text.size(), thinking_tokens, response_tokens, options.max_tokens, + &result); + set_structured_output_if_present(response_cstr, &result); + + publish_generation_event( + runanywhere::v1::GENERATION_EVENT_KIND_COMPLETED, request.prompt().c_str(), nullptr, + response_cstr, nullptr, ref.model_id, raw.completion_tokens, + raw.total_time_ms > 0 ? raw.total_time_ms : elapsed, raw.prompt_tokens); + + rac_llm_result_free(&raw); + rac::llm::release_lifecycle_llm(&ref); + return copy_proto(result, out_result); +#endif +} + +/** Clear adaptive context retained by the lifecycle-owned LLM. */ +rac_result_t rac_llm_clear_context_lifecycle(void) { + return call_lifecycle_op(&rac_llm_service_ops_t::clear_context); +} + } // extern "C" diff --git a/sdk/runanywhere-swift/Sources/RunAnywhere/Foundation/Bridge/Extensions/CppBridge+LLM.swift b/sdk/runanywhere-swift/Sources/RunAnywhere/Foundation/Bridge/Extensions/CppBridge+LLM.swift index 61912a5d2..c4945371f 100644 --- a/sdk/runanywhere-swift/Sources/RunAnywhere/Foundation/Bridge/Extensions/CppBridge+LLM.swift +++ b/sdk/runanywhere-swift/Sources/RunAnywhere/Foundation/Bridge/Extensions/CppBridge+LLM.swift @@ -6,11 +6,31 @@ // // All generic scaffolding (handle creation, isLoaded, loadModel, // unload, destroy) lives in `CppBridge.ComponentActor`; this file -// only adds the LLM-specific `cancel()` op on top. +// adds LLM-specific operations such as cancel and adaptive context on top. // import CRACommons +private enum LLMAdaptiveContextABI { + typealias StringCall = @convention(c) (UnsafePointer?) -> rac_result_t + typealias VoidCall = @convention(c) () -> rac_result_t + typealias Generate = @convention(c) ( + UnsafePointer?, + Int, + UnsafeMutablePointer? + ) -> rac_result_t + + static let injectSystemPromptName = "rac_llm_inject_system_prompt_lifecycle" + static let appendContextName = "rac_llm_append_context_lifecycle" + static let generateFromContextName = "rac_llm_generate_from_context_proto" + static let clearContextName = "rac_llm_clear_context_lifecycle" + + static let injectSystemPrompt = NativeProtoABI.load(injectSystemPromptName, as: StringCall.self) + static let appendContext = NativeProtoABI.load(appendContextName, as: StringCall.self) + static let generateFromContext = NativeProtoABI.load(generateFromContextName, as: Generate.self) + static let clearContext = NativeProtoABI.load(clearContextName, as: VoidCall.self) +} + // MARK: - LLM Component Bridge extension CppBridge { @@ -57,11 +77,71 @@ extension CppBridge { rac_llm_component_cancel(handle.rawValue) } + // MARK: - Adaptive Context + + /// Seed the lifecycle-owned LLM context with a reusable system prompt. + public func injectSystemPrompt(_ prompt: String) async throws { + let symbol = try NativeProtoABI.require( + LLMAdaptiveContextABI.injectSystemPrompt, + named: LLMAdaptiveContextABI.injectSystemPromptName + ) + let status = prompt.withCString { + symbol($0) + } + try Self.throwIfFailed(status, operation: "inject system prompt") + } + + /// Append user or retrieval text to the lifecycle-owned LLM context. + public func appendContext(_ text: String) async throws { + let symbol = try NativeProtoABI.require( + LLMAdaptiveContextABI.appendContext, + named: LLMAdaptiveContextABI.appendContextName + ) + let status = text.withCString { + symbol($0) + } + try Self.throwIfFailed(status, operation: "append context") + } + + /// Generate without clearing the accumulated lifecycle-owned LLM context. + public func generateFromContext( + query: String, + options: RALLMGenerationOptions? = nil + ) async throws -> RALLMGenerationResult { + var request = (options ?? .defaults()).toRALLMGenerateRequest(prompt: query) + request.options.streamingEnabled = false + return try NativeProtoABI.invoke( + request, + symbol: LLMAdaptiveContextABI.generateFromContext, + symbolName: LLMAdaptiveContextABI.generateFromContextName, + responseType: RALLMGenerationResult.self + ) + } + + /// Clear accumulated adaptive context on the lifecycle-owned LLM. + public func clearContext() async throws { + let symbol = try NativeProtoABI.require( + LLMAdaptiveContextABI.clearContext, + named: LLMAdaptiveContextABI.clearContextName + ) + let status = symbol() + try Self.throwIfFailed(status, operation: "clear context") + } + // MARK: - Cleanup /// Destroy the component public func destroy() async { await inner.destroy() } + + /// Convert a failed native adaptive-context status into an SDK error with backend detail. + private static func throwIfFailed(_ status: rac_result_t, operation: String) throws { + guard let exception = SDKException.from(rcResult: status) else { return } + let nativeMessage = String(cString: rac_error_message(status)) + var proto = exception.proto + proto.message = "LLM adaptive context \(operation) failed: \(nativeMessage) (\(status))" + throw SDKException(proto: proto, underlying: exception.underlying) + } } } diff --git a/sdk/runanywhere-swift/Sources/RunAnywhere/Public/Extensions/LLM/RunAnywhere+TextGeneration.swift b/sdk/runanywhere-swift/Sources/RunAnywhere/Public/Extensions/LLM/RunAnywhere+TextGeneration.swift index 63977822a..b43d77d1a 100644 --- a/sdk/runanywhere-swift/Sources/RunAnywhere/Public/Extensions/LLM/RunAnywhere+TextGeneration.swift +++ b/sdk/runanywhere-swift/Sources/RunAnywhere/Public/Extensions/LLM/RunAnywhere+TextGeneration.swift @@ -41,6 +41,45 @@ public extension RunAnywhere { return try await generateStream(request) } + /// Seed the loaded on-device model's adaptive context with a reusable system prompt. + static func injectSystemPrompt(_ prompt: String) async throws { + guard isInitialized else { + throw SDKException(code: .notInitialized, message: "SDK not initialized", category: .internal) + } + try await ensureServicesReady() + try await CppBridge.LLM.shared.injectSystemPrompt(prompt) + } + + /// Append text to the loaded on-device model's adaptive context. + static func appendContext(_ text: String) async throws { + guard isInitialized else { + throw SDKException(code: .notInitialized, message: "SDK not initialized", category: .internal) + } + try await ensureServicesReady() + try await CppBridge.LLM.shared.appendContext(text) + } + + /// Generate from the accumulated adaptive context without clearing the KV cache first. + static func generateFromContext( + query: String, + options: RALLMGenerationOptions? = nil + ) async throws -> RALLMGenerationResult { + guard isInitialized else { + throw SDKException(code: .notInitialized, message: "SDK not initialized", category: .internal) + } + try await ensureServicesReady() + return try await CppBridge.LLM.shared.generateFromContext(query: query, options: options) + } + + /// Clear the loaded on-device model's adaptive context. + static func clearContext() async throws { + guard isInitialized else { + throw SDKException(code: .notInitialized, message: "SDK not initialized", category: .internal) + } + try await ensureServicesReady() + try await CppBridge.LLM.shared.clearContext() + } + /// Generate text through the generated-proto C++ LLM service ABI. static func generate(_ request: RALLMGenerateRequest) async throws -> RALLMGenerationResult { guard isInitialized else {