mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-09-02 17:49:20 +00:00
Merge remote-tracking branch 'origin/main' into feat/lancedb-backend
This commit is contained in:
@@ -242,6 +242,16 @@ mod tests {
|
||||
"create_job should return a job_id: {:?}",
|
||||
create_result.1
|
||||
);
|
||||
assert!(
|
||||
create_result.1.contains("in_progress"),
|
||||
"create_job should dispatch through the scheduler, not stay pending: {:?}",
|
||||
create_result.1
|
||||
);
|
||||
assert!(
|
||||
!create_result.1.contains("scheduler unavailable"),
|
||||
"create_job should not fall back to the unscheduled path: {:?}",
|
||||
create_result.1
|
||||
);
|
||||
let status_result = results
|
||||
.iter()
|
||||
.find(|(n, _)| n == "job_status")
|
||||
|
||||
@@ -545,16 +545,14 @@ impl TestRigBuilder {
|
||||
.await
|
||||
.expect("AppBuilder::build_all() failed in test rig");
|
||||
|
||||
let scheduler_slot: ironclaw::tools::builtin::SchedulerSlot =
|
||||
Arc::new(tokio::sync::RwLock::new(None));
|
||||
|
||||
// 6. Register job tools, routine tools, and extra tools.
|
||||
{
|
||||
use ironclaw::context::ContextManager;
|
||||
|
||||
let ctx_mgr = Arc::new(ContextManager::new(
|
||||
components.config.agent.max_parallel_jobs,
|
||||
));
|
||||
components.tools.register_job_tools(
|
||||
ctx_mgr,
|
||||
None,
|
||||
Arc::clone(&components.context_manager),
|
||||
Some(scheduler_slot.clone()),
|
||||
None,
|
||||
components.db.clone(),
|
||||
None,
|
||||
@@ -657,10 +655,13 @@ impl TestRigBuilder {
|
||||
None, // heartbeat_config
|
||||
None, // hygiene_config
|
||||
routine_config,
|
||||
None, // context_manager
|
||||
Some(Arc::clone(&components.context_manager)),
|
||||
None, // session_manager
|
||||
);
|
||||
|
||||
// Match main.rs: fill the scheduler slot once Agent::new has created it.
|
||||
*scheduler_slot.write().await = Some(agent.scheduler());
|
||||
|
||||
// 9. Spawn agent in background task.
|
||||
let agent_handle = tokio::spawn(async move {
|
||||
if let Err(e) = agent.run().await {
|
||||
|
||||
+35
-26
@@ -513,32 +513,41 @@ impl LlmProvider for TraceLlm {
|
||||
}
|
||||
|
||||
async fn complete(&self, request: CompletionRequest) -> Result<CompletionResponse, LlmError> {
|
||||
let step = self.next_step(&request.messages)?;
|
||||
match step.response {
|
||||
TraceResponse::Text {
|
||||
content,
|
||||
input_tokens,
|
||||
output_tokens,
|
||||
} => Ok(CompletionResponse {
|
||||
content,
|
||||
input_tokens,
|
||||
output_tokens,
|
||||
finish_reason: FinishReason::Stop,
|
||||
cache_read_input_tokens: 0,
|
||||
cache_creation_input_tokens: 0,
|
||||
}),
|
||||
TraceResponse::ToolCalls { .. } => Err(LlmError::RequestFailed {
|
||||
provider: self.model_name.clone(),
|
||||
reason: "TraceLlm::complete() called but current step is a tool_calls response; \
|
||||
use complete_with_tools() instead"
|
||||
.to_string(),
|
||||
}),
|
||||
TraceResponse::UserInput { .. } => Err(LlmError::RequestFailed {
|
||||
provider: self.model_name.clone(),
|
||||
reason: "TraceLlm::complete() encountered a user_input step; \
|
||||
these should have been filtered out during construction"
|
||||
.to_string(),
|
||||
}),
|
||||
// complete() is called when Reasoning has force_text=true (no tools
|
||||
// available). Skip any remaining ToolCalls steps in the trace and
|
||||
// return the next Text step, since in real usage the LLM would
|
||||
// produce text when no tools are offered.
|
||||
loop {
|
||||
let step = self.next_step(&request.messages)?;
|
||||
match step.response {
|
||||
TraceResponse::Text {
|
||||
content,
|
||||
input_tokens,
|
||||
output_tokens,
|
||||
} => {
|
||||
return Ok(CompletionResponse {
|
||||
content,
|
||||
input_tokens,
|
||||
output_tokens,
|
||||
finish_reason: FinishReason::Stop,
|
||||
cache_read_input_tokens: 0,
|
||||
cache_creation_input_tokens: 0,
|
||||
});
|
||||
}
|
||||
TraceResponse::ToolCalls { .. } => {
|
||||
// Skip tool_calls steps — complete() is called in
|
||||
// force_text mode so the LLM can't use tools anyway.
|
||||
continue;
|
||||
}
|
||||
TraceResponse::UserInput { .. } => {
|
||||
return Err(LlmError::RequestFailed {
|
||||
provider: self.model_name.clone(),
|
||||
reason: "TraceLlm::complete() encountered a user_input step; \
|
||||
these should have been filtered out during construction"
|
||||
.to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -571,22 +571,29 @@ mod trace_llm_tests {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn complete_errors_on_tool_calls_step() {
|
||||
async fn complete_skips_tool_calls_step() {
|
||||
// complete() is called in force_text mode where tools aren't available.
|
||||
// When the trace has a ToolCalls step followed by a Text step, complete()
|
||||
// should skip the ToolCalls and return the Text response.
|
||||
let trace = LlmTrace::single_turn(
|
||||
"test-model",
|
||||
"hi",
|
||||
vec![tool_calls_step(vec![simple_tool_call("echo")], 10, 5)],
|
||||
vec![
|
||||
tool_calls_step(vec![simple_tool_call("echo")], 10, 5),
|
||||
text_step("skipped past tools", 20, 8),
|
||||
],
|
||||
);
|
||||
let llm = TraceLlm::from_trace(trace);
|
||||
|
||||
let result = llm.complete(make_completion_request("hi")).await;
|
||||
let resp = llm
|
||||
.complete(make_completion_request("hi"))
|
||||
.await
|
||||
.expect("complete() should skip ToolCalls and return the Text step");
|
||||
|
||||
assert!(result.is_err());
|
||||
let err_msg = result.unwrap_err().to_string();
|
||||
assert!(
|
||||
err_msg.contains("tool_calls"),
|
||||
"Expected 'tool_calls' in error: {err_msg}"
|
||||
);
|
||||
assert_eq!(resp.content, "skipped past tools");
|
||||
assert_eq!(resp.input_tokens, 20);
|
||||
assert_eq!(resp.output_tokens, 8);
|
||||
assert_eq!(resp.finish_reason, FinishReason::Stop);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
Reference in New Issue
Block a user