mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-09-01 09:09:19 +00:00
feat(engine): enable CodeAct/RLM mode with code block detection
The engine now operates in CodeAct/RLM mode: System prompt (executor/prompt.rs): - Instructs LLM to write Python in ```repl fenced blocks - Documents available tools as callable Python functions - Documents llm_query(), llm_query_batched(), FINAL() - Documents context variables (context, goal, step_number, previous_results) - Strategy guidance: examine context, break into steps, use tools, call FINAL() Code block detection (bridge/llm_adapter.rs): - extract_code_block() scans LLM text responses for ```repl or ```python blocks - When detected, returns LlmResponse::Code instead of LlmResponse::Text - The ExecutionLoop routes Code responses through Monty for execution No structured tool definitions sent to LLM: - Tools are described in the system prompt as Python functions - The LLM call sends empty actions array, forcing text-mode responses - This ensures the LLM writes code blocks (CodeAct) instead of structured tool calls (which would bypass the REPL) 85 tests passing, zero clippy warnings. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
This commit is contained in:
@@ -102,7 +102,7 @@ impl LlmBackend for LlmBridgeAdapter {
|
||||
reason: e.to_string(),
|
||||
})?;
|
||||
|
||||
// Convert response
|
||||
// Convert response — check for code blocks (CodeAct/RLM pattern)
|
||||
let llm_response = if !response.tool_calls.is_empty() {
|
||||
LlmResponse::ActionCalls {
|
||||
calls: response
|
||||
@@ -117,7 +117,15 @@ impl LlmBackend for LlmBridgeAdapter {
|
||||
content: response.content.clone(),
|
||||
}
|
||||
} else {
|
||||
LlmResponse::Text(response.content.unwrap_or_default())
|
||||
let text = response.content.unwrap_or_default();
|
||||
// Detect ```repl or ```python fenced code blocks
|
||||
match extract_code_block(&text) {
|
||||
Some(code) => LlmResponse::Code {
|
||||
code,
|
||||
content: Some(text),
|
||||
},
|
||||
None => LlmResponse::Text(text),
|
||||
}
|
||||
};
|
||||
|
||||
Ok(LlmOutput {
|
||||
@@ -181,3 +189,29 @@ fn action_def_to_tool_def(action: &ActionDef) -> ToolDefinition {
|
||||
parameters: action.parameters_schema.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract Python code from ```repl or ```python fenced blocks.
|
||||
///
|
||||
/// Matches the pattern used by RLM implementations (fast-rlm uses ```repl,
|
||||
/// official RLM uses ```repl, some models output ```python).
|
||||
fn extract_code_block(text: &str) -> Option<String> {
|
||||
// Try ```repl first (preferred), then ```python
|
||||
for marker in ["```repl", "```python"] {
|
||||
if let Some(start) = text.find(marker) {
|
||||
let code_start = start + marker.len();
|
||||
// Skip to next line
|
||||
let code_start = text[code_start..]
|
||||
.find('\n')
|
||||
.map(|i| code_start + i + 1)
|
||||
.unwrap_or(code_start);
|
||||
// Find closing ```
|
||||
if let Some(end) = text[code_start..].find("```") {
|
||||
let code = text[code_start..code_start + end].trim();
|
||||
if !code.is_empty() {
|
||||
return Some(code.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user