replace ContainerRuntime CLI to Bollard crate (default: docker socket)
ci/woodpecker/push/tests Pipeline was successful
ci/woodpecker/push/tests Pipeline was successful
limit tool result to open router tool response (with truncated info for ai) Hard kill if graceful shutdown is too long
This commit is contained in:
1 parent
78ad2bf701
commit
624bc1e028
16 files changed
+1327
-354
No files matched your search
@@ -17,8 +17,24 @@ use tracing::instrument;
|
||||
/// OpenRouter API root, version prefix included.
|
||||
const BASE_URL: &str = "https://openrouter.ai/api/v1";
|
||||
|
||||
/// The model decides on its own which tool to call.
|
||||
const TOOL_CHOICE_AUTO: &str = "auto";
|
||||
/// What the model is allowed to do on a given turn.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ToolChoice {
|
||||
/// The model decides whether to call a tool.
|
||||
Auto,
|
||||
/// The model must answer, without calling any tool. The tools stay declared,
|
||||
/// so the conversation remains the same shape as on the other turns.
|
||||
None,
|
||||
}
|
||||
|
||||
impl ToolChoice {
|
||||
fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Auto => "auto",
|
||||
Self::None => "none",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Result of a completion that may contain tool calls.
|
||||
pub struct ToolChatResult {
|
||||
@@ -250,13 +266,18 @@ impl OpenRouterClient {
|
||||
|
||||
/// Sends a conversation with tool definitions and returns either a final
|
||||
/// message or the tool calls requested by the model.
|
||||
///
|
||||
/// The conversation is borrowed rather than taken by value: the caller keeps
|
||||
/// appending to it between iterations, and a copy per iteration would be pure
|
||||
/// waste.
|
||||
#[instrument(skip(self, messages, tools), err)]
|
||||
pub async fn chat_with_tools(
|
||||
&self,
|
||||
messages: Vec<Message>,
|
||||
tools: Vec<Tool>,
|
||||
messages: &[Message],
|
||||
tools: &[Tool],
|
||||
tool_choice: ToolChoice,
|
||||
) -> anyhow::Result<ToolChatResult> {
|
||||
let response = self.complete(&messages, &tools).await?;
|
||||
let response = self.complete(messages, tools, tool_choice).await?;
|
||||
|
||||
let cost = response.usage.and_then(|usage| usage.cost);
|
||||
let message = response
|
||||
@@ -273,13 +294,18 @@ impl OpenRouterClient {
|
||||
})
|
||||
}
|
||||
|
||||
async fn complete(&self, messages: &[Message], tools: &[Tool]) -> anyhow::Result<ChatResponse> {
|
||||
async fn complete(
|
||||
&self,
|
||||
messages: &[Message],
|
||||
tools: &[Tool],
|
||||
tool_choice: ToolChoice,
|
||||
) -> anyhow::Result<ChatResponse> {
|
||||
let request = ChatRequest {
|
||||
model: &self.model,
|
||||
messages,
|
||||
reasoning: Reasoning { enabled: true },
|
||||
tools,
|
||||
tool_choice: TOOL_CHOICE_AUTO,
|
||||
tool_choice: tool_choice.as_str(),
|
||||
};
|
||||
|
||||
let response = self
|
||||
@@ -364,7 +390,7 @@ mod tests {
|
||||
messages: &messages,
|
||||
reasoning: Reasoning { enabled: true },
|
||||
tools: &tools,
|
||||
tool_choice: TOOL_CHOICE_AUTO,
|
||||
tool_choice: ToolChoice::Auto.as_str(),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
@@ -375,6 +401,12 @@ mod tests {
|
||||
assert_eq!(request["messages"][0]["role"], "user");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_forced_answer_turn_disables_the_tools() {
|
||||
assert_eq!(ToolChoice::None.as_str(), "none");
|
||||
assert_eq!(ToolChoice::Auto.as_str(), "auto");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn response_parses_tool_calls_and_cost() {
|
||||
let response: ChatResponse = serde_json::from_value(json!({
|
||||
|
||||
Reference in new issue
Block a user