Entropic 2.3.8
Local-first agentic inference engine
Loading...
Searching...
No Matches
gemma4_adapter.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
15#include "gemma4_adapter.h"
16
17#include <regex>
18
19namespace entropic {
20
35ParseResult Gemma4Adapter::parse_tool_calls(const std::string& content) const {
36 ParseResult result;
37
38 auto calls = parse_tagged_tool_calls(content);
39 if (calls.empty()) {
40 calls = parse_bare_json_tool_calls(content);
41 }
42 result.tool_calls = std::move(calls);
43
44 // gh#65 (v2.3.3) / gh#69 (v2.3.8): match the asymmetric open
45 // variants here too so the cleaned_content (what the user sees)
46 // doesn't leave stray `<|tool_call>{json}</tool_call>` or
47 // `<|im_start|>tool_call ... </tool_call>` markup behind when
48 // Gemma 4 emits the pipe-prefixed or channel-header form. Mirror
49 // the openings accepted by parse_tagged_tool_calls.
50 std::string cleaned = std::regex_replace(content,
51 std::regex(R"((?:<tool_call>|<\|tool_call\|?>|<\|im_start\|>tool_call))"
52 R"(\s*[\s\S]*?\s*</tool_call>)"),
53 "");
54 cleaned = strip_think_blocks(cleaned);
55
56 // gh#68 (v2.3.5): scrub Gemma 4 chat-template turn-boundary
57 // markers from surface content. The v2.3.4 detokenize special=false
58 // change does NOT catch these — Gemma 4 emits them as multi-token
59 // *regular* surface tokens (e.g. `<`, `|`, `im`, `_end`, `|>`)
60 // that llama.cpp doesn't classify as special, so they slip through
61 // detokenize unchanged. Same family of bug as gh#65's asymmetric
62 // `<|tool_call>`: chat-template artifacts spelled by regular tokens.
63 //
64 // Without this scrub, `<|im_end|>` leaked into the assistant
65 // content stream, echoed back into the next turn's prompt, and
66 // the engine's "no tool call this iteration" retry cascade fired
67 // until iteration cap.
68 //
69 // Asymmetric variants are matched explicitly (`\|?`) for parity
70 // with the gh#65 tool-call regex — Gemma 4's tokenizer surface
71 // drops the trailing `|>` on some emit paths.
72 //
73 // gh#69 (v2.3.8): `tool_call` joins the channel-role list so a
74 // stray `<|im_start|>tool_call` header (one whose `</tool_call>`
75 // close didn't pair up, e.g. a truncated emit) is scrubbed instead
76 // of leaking into the assistant-visible body. The paired
77 // `<|im_start|>tool_call ... </tool_call>` block is already removed
78 // above; this catches the degenerate unpaired header.
79 static const std::regex kGemmaTemplateMarkers(
80 R"(<\|im_end\|?>|<\|im_start\|?>(?:user|assistant|system|tool_call)?|)"
81 R"(<end_of_turn>|<start_of_turn>(?:user|model)?)");
82 cleaned = std::regex_replace(cleaned, kGemmaTemplateMarkers, "");
83
84 result.cleaned_content = std::move(cleaned);
85 return result;
86}
87
88} // namespace entropic
std::vector< ToolCall > parse_tagged_tool_calls(const std::string &content) const
Parse <tool_call>JSON</tool_call> tagged blocks.
std::vector< ToolCall > parse_bare_json_tool_calls(const std::string &content) const
Parse bare JSON lines containing "name" key.
std::string strip_think_blocks(const std::string &content) const
Strip all <think>...</think> blocks from content.
ParseResult parse_tool_calls(const std::string &content) const override
Parse tool calls via tagged JSON, falling back to bare JSON.
Gemma 4 chat adapter (v2.1.9, gh#46).
Activate model on GPU (WARM → ACTIVE).
Parsed tool call result: cleaned content + extracted calls.
std::string cleaned_content
Content with tool calls removed.
std::vector< ToolCall > tool_calls
Extracted tool calls.