thalamus: add thinking mode toggles (native + tool)

Two independent toggles on the thalamus screen:
- 't' toggles native Qwen <think> tags (adds <think>\n to generation prompt)
- 'T' toggles think tool (Anthropic-style structured reasoning tool)

Both can be enabled simultaneously. Native thinking is on by default.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
Kent Overstreet 2026-04-14 18:25:00 -04:00
parent be909028a7
commit 5d6e663b60
5 changed files with 128 additions and 1 deletions

View file

@ -159,6 +159,10 @@ pub struct AgentState {
pub mcp_tools: McpToolAccess,
pub last_prompt_tokens: u32,
pub reasoning_effort: String,
/// Native Qwen thinking — add `<think>\n` to generation prompt.
pub think_native: bool,
/// Tool-based thinking — add a "think" tool for structured reasoning.
pub think_tool: bool,
pub temperature: f32,
pub top_p: f32,
pub top_k: u32,
@ -224,6 +228,8 @@ impl Agent {
mcp_tools: McpToolAccess::All,
last_prompt_tokens: 0,
reasoning_effort: "none".to_string(),
think_native: true,
think_tool: false,
temperature: 0.6,
top_p: 0.95,
top_k: 20,
@ -261,6 +267,8 @@ impl Agent {
mcp_tools: McpToolAccess::None,
last_prompt_tokens: 0,
reasoning_effort: "none".to_string(),
think_native: st.think_native,
think_tool: st.think_tool,
temperature: st.temperature,
top_p: st.top_p,
top_k: st.top_k,
@ -282,12 +290,39 @@ impl Agent {
pub async fn assemble_prompt_tokens(&self) -> Vec<u32> {
let ctx = self.context.lock().await;
let st = self.state.lock().await;
let mut tokens = ctx.token_ids();
tokens.push(tokenizer::IM_START);
tokens.extend(tokenizer::encode("assistant\n"));
if st.think_native {
tokens.extend(tokenizer::encode("assistant\n<think>\n"));
} else {
tokens.extend(tokenizer::encode("assistant\n"));
}
tokens
}
/// Rebuild the tools section of the system prompt from the current tools list.
pub async fn rebuild_tools(&self) {
let st = self.state.lock().await;
let tool_defs: Vec<String> = st.tools.iter().map(|t| t.to_json()).collect();
drop(st);
let mut ctx = self.context.lock().await;
ctx.clear(Section::System);
if !tool_defs.is_empty() {
let tools_text = format!(
"# Tools\n\nYou have access to the following functions:\n\n<tools>\n{}\n</tools>\n\n\
If you choose to call a function ONLY reply in the following format with NO suffix:\n\n\
<tool_call>\n<function=example_function_name>\n\
<parameter=example_parameter_1>\nvalue_1\n</parameter>\n\
</function>\n</tool_call>\n\n\
IMPORTANT: Function calls MUST follow the specified format.",
tool_defs.join("\n"),
);
ctx.push_no_log(Section::System, AstNode::system_msg(&tools_text));
}
}
pub async fn push_node(&self, node: AstNode) {
let node = node.with_timestamp(chrono::Utc::now());
self.context.lock().await.push_log(Section::Conversation, node);