Skip to content
Open
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: 2 additions & 0 deletions .cspell-wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -432,3 +432,5 @@ yunjian
yunxi
yunxia
yunyang
apos
minicpm
107 changes: 106 additions & 1 deletion apps/nlp/constants/llm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,77 @@ export function parseLfmToolCalls(text: string): llm.ToolParserResult | undefine
return { toolCalls, textContent: textContent || undefined };
}

function decodeMiniCpmArgument(value: string): string {
const trimmed = value.trim();
if (trimmed.startsWith('<![CDATA[') && trimmed.endsWith(']]>')) {
return trimmed.slice(9, -3);
}
const entities: Record<string, string> = {
amp: '&',
lt: '<',
gt: '>',
quot: '"',
apos: "'",
};
return trimmed.replace(/&(#x[\da-f]+|#\d+|amp|lt|gt|quot|apos);/gi, (_, entity: string) => {
if (entity.startsWith('#')) {
const code = entity.slice(1).toLowerCase();
return String.fromCodePoint(
code.startsWith('x') ? parseInt(code.slice(1), 16) : Number(code)
);
}
return entities[entity.toLowerCase()]!;
});
}

export function parseMiniCpmToolCalls(text: string): llm.ToolParserResult | undefined {
const thinkingEnd = text.lastIndexOf('</think>');
if (thinkingEnd === -1) return undefined;
const answer = text.slice(thinkingEnd + '</think>'.length);
const callRegex =
/<function\s+name=(["'])([^"']+)\1\s*>((?:<!\[CDATA\[[\s\S]*?\]\]>|[\s\S])*?)<\/function>/g;
const toolCalls: llm.ToolCall[] = [];

for (const match of answer.matchAll(callRegex)) {
const name = match[2]!;
const body = match[3]!;
const tool = TOOLS.find((candidate) => candidate.function.name === name);
if (!tool) return undefined;
const params = tool.function.parameters;
const paramRegex =
/<param\s+name=(["'])([^"']+)\1\s*>((?:<!\[CDATA\[[\s\S]*?\]\]>|[\s\S])*?)<\/param>/g;
const args: Record<string, unknown> = {};

try {
for (const param of body.matchAll(paramRegex)) {
const key = param[2]!;
const schema = params?.properties?.[key] as { type?: string } | undefined;
if (!schema || Object.prototype.hasOwnProperty.call(args, key)) return undefined;
const value = decodeMiniCpmArgument(param[3]!);
const parsed = schema.type === 'string' ? value : JSON.parse(value);
if (schema.type === 'number' && (typeof parsed !== 'number' || !Number.isFinite(parsed))) {
return undefined;
}
args[key] = parsed;
}
} catch {
return undefined;
}
if (body.replace(paramRegex, '').trim()) return undefined;
if (params?.required?.some((key) => !Object.prototype.hasOwnProperty.call(args, key))) {
return undefined;
}
toolCalls.push({ type: 'function', function: { name, arguments: args } });
}

if (toolCalls.length === 0) return undefined;
const textContent = answer
.replace(callRegex, '')
.replace(/<\|im_end\|>/g, '')
.trim();
return { toolCalls, textContent: textContent || undefined };
}

export const QWEN_TOOL_STOP_REGEX = /(?:<\/tool_call>|<\|im_end\|>)/;

export function parseQwenToolCalls(text: string): llm.ToolParserResult | undefined {
Expand Down Expand Up @@ -375,6 +446,14 @@ export function parseLlamaToolCalls(text: string): llm.ToolParserResult | undefi

// --- Model Family Generation Configurations ---

/** Sampling defaults recommended by the MiniCPM5-2B model card. */
export const MINICPM5_GENERATION_CONFIG: llm.LLMGenerationConfig = {
temperature: 1.0,
topP: 0.95,
minP: 0.0,
maxNewTokens: 512,
};

/**
* Liquid AI LFM 2.5 recommended generation parameters.
* Sampling parameters are specified in the official Liquid AI GGUF model cards
Expand Down Expand Up @@ -532,6 +611,11 @@ export const LFM_TOOL_OPTS: LLMToolOpts = {
parseToolCalls: parseLfmToolCalls,
};

export const MINICPM5_TOOL_OPTS: LLMToolOpts = {
tools: TOOLS,
parseToolCalls: parseMiniCpmToolCalls,
};

export const GEMMA_TOOL_OPTS: LLMToolOpts = {
tools: TOOLS,
parseToolCalls: parseGemmaToolCalls,
Expand Down Expand Up @@ -571,6 +655,27 @@ export interface LLMModelConfig {
// --- Quantized LLM Models List ---

export const LLM_MODELS: LLMModelConfig[] = [
// MiniCPM5
{
id: 'minicpm-5-2b-xnnpack',
name: 'MiniCPM5 2B',
backend: 'xnnpack',
model: models.llm.MINICPM5_2B.XNNPACK_8DA4W,
generationConfig: MINICPM5_GENERATION_CONFIG,
systemPrompt: `${DEFAULT_SYSTEM_PROMPT}. Always reply in the language of the user's latest message.`,
toolOpts: MINICPM5_TOOL_OPTS,
},
{
id: 'minicpm-5-2b-mlx',
name: 'MiniCPM5 2B',
backend: 'mlx',
model: models.llm.MINICPM5_2B.MLX_INT4,
generationConfig: MINICPM5_GENERATION_CONFIG,
systemPrompt: `${DEFAULT_SYSTEM_PROMPT}. Always reply in the language of the user's latest message.`,
toolOpts: MINICPM5_TOOL_OPTS,
iosOnly: true,
},

// LFM 2.5 family
{
id: 'lfm-2.5-2.6b-xnnpack',
Expand Down Expand Up @@ -1107,7 +1212,7 @@ export const LLM_MODELS: LLMModelConfig[] = [
backend: 'xnnpack',
model: models.llm.BIELIK_V3_1_5B.XNNPACK_8DA4W,
generationConfig: BIELIK_V3_GENERATION_CONFIG,
// eslint-disable-next-line @cspell/spellchecker
// cspell:disable-next-line
systemPrompt: 'Jesteś pomocnym asystentem AI mówiącym po polsku.',
stopRegex: undefined,
toolOpts: undefined,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,7 @@ The library provides ready-to-use models from the [Software Mansion HuggingFace
| **Microsoft Phi-4 Mini** | [`3.8B`](../../06-api-reference/variables/models.md#llmphi4_mini) | 2.62 GB – 7.15 GB | XNNPACK (CPU), MLX (Apple) | High-density reasoning model for STEM problem solving & coding. |
| **HuggingFace SmolLM2** | [`135M`](../../06-api-reference/variables/models.md#llmsmollm2_135m), [`360M`](../../06-api-reference/variables/models.md#llmsmollm2_360m), [`1.7B`](../../06-api-reference/variables/models.md#llmsmollm2_1_7b) | 158 MB – 1.83 GB | XNNPACK (CPU), MLX (Apple) | Smallest footprint family, for constrained devices and fast drafting. |
| **SpeakLeash Bielik v3** | [`1.5B`](../../06-api-reference/variables/models.md#llmbielik_v3_1_5b) | 923 MB – 2.97 GB | XNNPACK (CPU) | Bilingual Polish & English instruction model. |
| **OpenBMB MiniCPM5** | [`2B`](../../06-api-reference/variables/models.md#llmminicpm5_2b) | 1.36 GB – 1.42 GB | XNNPACK (CPU), MLX (Apple) | Compact reasoning model with thinking and tool calling. |

:::tip Using Custom Models
To use your own fine-tuned LLM `.pte` model, pass an [`LLMModel`](../../06-api-reference/type-aliases/LLMModel.md) configuration object to [`useLLMChatSession`](../../06-api-reference/functions/useLLMChatSession.md) or [`createLLMChatSession`](../../06-api-reference/functions/createLLMChatSession.md):
Expand Down
18 changes: 18 additions & 0 deletions packages/react-native-executorch/src/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1874,7 +1874,7 @@

// Chinese is published from the next tag. Its phonemizer segments the text
// into words, with the segmenter data in the tagger slot, and reads each word
// from the lexicon. Upstream's zf_xiaoni is left out: it is unintelligible even

Check warning on line 1877 in packages/react-native-executorch/src/models.ts

View workflow job for this annotation

GitHub Actions / lint

Unknown word: "xiaoni"
// in the reference PyTorch model.
const KOKORO_NEXT_ROOT = `${BASE_URL}-kokoro/${NEXT_VERSION_TAG}`;
const KOKORO_ZH_PHONEMIZER = {
Expand Down Expand Up @@ -2098,6 +2098,19 @@
// =============================================================================
// LLMs
// =============================================================================
const MINICPM5_BASE_URL = `${BASE_URL}-minicpm-5/${NEXT_VERSION_TAG}`;

const MINICPM5_2B_XNNPACK_8DA4W: LLMModel = {
modelPath: `${MINICPM5_BASE_URL}/xnnpack/minicpm_5_2b_xnnpack_8da4w.pte`,
tokenizerPath: `${MINICPM5_BASE_URL}/tokenizer.json`,
tokenizerConfigPath: `${MINICPM5_BASE_URL}/tokenizer_config.json`,
};
const MINICPM5_2B_MLX_INT4: LLMModel = {
modelPath: `${MINICPM5_BASE_URL}/mlx/minicpm_5_2b_mlx_4w.pte`,
tokenizerPath: `${MINICPM5_BASE_URL}/tokenizer.json`,
tokenizerConfigPath: `${MINICPM5_BASE_URL}/tokenizer_config.json`,
};

const LFM2_5_BASE_URL = `${BASE_URL}-lfm-2.5/${NEXT_VERSION_TAG}`;

const LFM2_5_1_2B_XNNPACK_8DA4W: LLMModel = {
Expand Down Expand Up @@ -3225,6 +3238,11 @@
* text generation, and reasoning.
*/
llm: {
/** OpenBMB MiniCPM5 2B chat model with an 8K context window. */
MINICPM5_2B: variants({
XNNPACK_8DA4W: MINICPM5_2B_XNNPACK_8DA4W,
MLX_INT4: MINICPM5_2B_MLX_INT4,
}),
/**
* Liquid AI LFM 2.5 1.2B general-purpose hybrid language model. Built on
* the Liquid Foundation Model architecture for low memory bandwidth usage,
Expand Down
Loading