recovery: change agent-providers as a directory

This commit is contained in:
aiyezi committed 2026-11-05 17:36:42 +08:00
1 parent 91b44fcc16
commit 339ca0f87e
9 files changed
+345 -1

No files matched your search

Submodule crates/agent-providers deleted from d360b650d8.
+1
View File
@@ -0,0 +1 @@
/target
+15
View File
@@ -0,0 +1,15 @@
[package]
name = "agent-providers"
version = "0.1.0"
edition = "2024"
[dependencies]
agent-core = { path = "../agent-core" }
agent-tools = { path = "../agent-tools" }
serde = { workspace = true }
serde_json = { workspace = true }
thiserror = { workspace = true }
async-trait = { workspace = true }
reqwest = { workspace = true }
tokio = { workspace = true }
dotenvy = { workspace = true }
+35
View File
@@ -0,0 +1,35 @@
use std::sync::Arc;
use agent_core::ReActAgent;
use agent_providers::ChatCompletionsProvider;
use agent_tools::{CurrentTimeTool, ReadFileTool};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
dotenvy::dotenv().ok();
let provider = Arc::new(ChatCompletionsProvider::from_env()?);
let agent = ReActAgent::builder(provider, std::env::var("MODEL").unwrap_or_default())
.system_prompt("你是小助手!")
.max_iterations(5)
.tool(Arc::new(CurrentTimeTool))
.tool(Arc::new(ReadFileTool))
.build();
let output = agent
.run("帮我看看 /home/aieyzi/Documents/prompts.txt 第二行里是什么, 你只看第二行。")
.await?;
println!("-- after {} iteration(s) ---", output.iterations);
println!("{}", output.final_message.content.unwrap_or_default());
println!("-- full history ---");
for (i, m) in output.history.iter().enumerate() {
println!("[{}] {:?}: {:?}", i, m.role, m.content);
if let Some(calls) = &m.tool_calls {
for call in calls {
println!("{} -> {}({})", call.id, call.name, call.arguments);
}
}
}
Ok(())
}
@@ -0,0 +1,38 @@
use agent_core::AgentError;
use reqwest::Client;
use crate::chat_completions::types::{
ChatCompletionsErrorBody, ChatCompletionsErrorResponse, ChatCompletionsRequest,
ChatCompletionsResponse,
};
pub async fn send_chat(
client: &Client,
base_url: &str,
api_key: &str,
request: ChatCompletionsRequest,
) -> Result<ChatCompletionsResponse, AgentError> {
let url = format!("{}/chat/completions", base_url);
let response = client
.post(&url)
.header("Authorization", format!("Bearer {}", api_key))
.header("Content-Type", "application/json")
.json(&request)
.send()
.await?;
let status = response.status();
if !status.is_success() {
let body = response.text().await.unwrap_or_default();
let message = serde_json::from_str::<ChatCompletionsErrorResponse>(&body)
.map(|e| e.error.message)
.unwrap_or(body);
return Err(AgentError::Api {
status: status.as_u16(),
message,
});
}
let parsed: ChatCompletionsResponse = response.json().await?;
Ok(parsed)
}
@@ -0,0 +1,110 @@
use agent_core::{ChatRequest, ChatResponse, Message, Role, ToolCall};
use crate::chat_completions::types::{
ChatCompletionsFunctionCall, ChatCompletionsFunctionDef, ChatCompletionsMessage,
ChatCompletionsRequest, ChatCompletionsResponse, ChatCompletionsTool, ChatCompletionsToolCall,
};
impl From<Message> for ChatCompletionsMessage {
fn from(message: Message) -> Self {
let role = match message.role {
Role::System => "system",
Role::User => "user",
Role::Assistant => "assistant",
Role::Tool => "tool",
};
Self {
role: role.into(),
content: message.content.into(),
tool_calls: message.tool_calls.map(|calls| {
calls
.into_iter()
.map(ChatCompletionsToolCall::from)
.collect()
}),
tool_call_id: message.tool_call_id,
}
}
}
impl From<ToolCall> for ChatCompletionsToolCall {
fn from(call: ToolCall) -> Self {
Self {
id: call.id,
r#type: "function".to_string(),
function: ChatCompletionsFunctionCall {
name: call.name,
arguments: call.arguments,
},
}
}
}
impl From<ChatCompletionsToolCall> for ToolCall {
fn from(call: ChatCompletionsToolCall) -> Self {
Self {
id: call.id,
name: call.function.name,
arguments: call.function.arguments,
}
}
}
impl From<ChatRequest> for ChatCompletionsRequest {
fn from(request: ChatRequest) -> Self {
Self {
model: request.model.into(),
messages: request
.messages
.into_iter()
.map(ChatCompletionsMessage::from)
.collect(),
temperature: request.temperature,
max_tokens: request.max_tokens,
tools: request
.tools
.into_iter()
.map(|tool| ChatCompletionsTool {
r#type: "function",
function: ChatCompletionsFunctionDef {
name: tool.name,
description: tool.description.into(),
parameters: tool.parameters,
},
})
.collect(),
}
}
}
impl From<ChatCompletionsResponse> for ChatResponse {
fn from(response: ChatCompletionsResponse) -> Self {
let ChatCompletionsResponse {
model,
choices,
usage,
} = response;
let (content, tool_calls) = match choices.into_iter().next() {
Some(choice) => (
choice.message.content,
choice
.message
.tool_calls
.unwrap_or_default()
.into_iter()
.map(ToolCall::from)
.collect(),
),
None => (None, Vec::new()),
};
Self {
model,
content,
prompt_tokens: usage.as_ref().map(|u| u.prompt_tokens),
completion_tokens: usage.as_ref().map(|u| u.completion_tokens),
tool_calls: tool_calls.into_iter().map(ToolCall::from).collect(),
}
}
}
@@ -0,0 +1,45 @@
mod client;
mod convert;
mod types;
use agent_core::{AgentError, ChatRequest, ChatResponse, LlmProvider};
use async_trait::async_trait;
use reqwest::Client;
use crate::chat_completions::{
client::send_chat,
types::{ChatCompletionsRequest, ChatCompletionsResponse},
};
pub struct ChatCompletionsProvider {
api_key: String,
base_url: String,
client: Client,
}
impl ChatCompletionsProvider {
pub fn new(base_url: impl Into<String>, api_key: impl Into<String>) -> Self {
Self {
base_url: base_url.into(),
api_key: api_key.into(),
client: Client::new(),
}
}
pub fn from_env() -> Result<Self, AgentError> {
let base_url = std::env::var("BASE_URL")
.map_err(|_| AgentError::Provider("BASE_URL NOT SET".into()))?;
let api_key =
std::env::var("API_KEY").map_err(|_| AgentError::Provider("API_KEY NOT SET".into()))?;
Ok(Self::new(base_url, api_key))
}
}
#[async_trait]
impl LlmProvider for ChatCompletionsProvider {
async fn chat(&self, request: ChatRequest) -> Result<ChatResponse, AgentError> {
let req = ChatCompletionsRequest::from(request);
let resp = send_chat(&self.client, &self.base_url, &self.api_key, req).await?;
Ok(ChatResponse::from(resp))
}
}
@@ -0,0 +1,84 @@
use serde::{Deserialize, Serialize};
#[derive(Serialize)]
pub struct ChatCompletionsRequest {
pub model: String,
pub messages: Vec<ChatCompletionsMessage>,
#[serde(skip_serializing_if = "Option::is_none")]
pub temperature: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_tokens: Option<u32>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub tools: Vec<ChatCompletionsTool>,
}
#[derive(Serialize)]
pub struct ChatCompletionsMessage {
pub role: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub content: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_calls: Option<Vec<ChatCompletionsToolCall>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_call_id: Option<String>,
}
#[derive(Serialize)]
pub struct ChatCompletionsTool {
pub r#type: &'static str,
pub function: ChatCompletionsFunctionDef,
}
#[derive(Serialize)]
pub struct ChatCompletionsFunctionDef {
pub name: String,
pub description: Option<String>,
pub parameters: serde_json::Value,
}
#[derive(Clone, Serialize, Deserialize)]
pub struct ChatCompletionsToolCall {
pub id: String,
pub r#type: String,
pub function: ChatCompletionsFunctionCall,
}
#[derive(Clone, Serialize, Deserialize)]
pub struct ChatCompletionsFunctionCall {
pub name: String,
pub arguments: String,
}
#[derive(Deserialize)]
pub struct ChatCompletionsResponse {
pub model: String,
pub choices: Vec<ChatCompletionsChoice>,
pub usage: Option<ChatCompletionsUsage>,
}
#[derive(Deserialize)]
pub struct ChatCompletionsChoice {
pub message: ChatCompletionsResponseMessage,
}
#[derive(Deserialize)]
pub struct ChatCompletionsResponseMessage {
pub content: Option<String>,
pub tool_calls: Option<Vec<ChatCompletionsToolCall>>,
}
#[derive(Deserialize)]
pub struct ChatCompletionsUsage {
pub prompt_tokens: u32,
pub completion_tokens: u32,
}
#[derive(Deserialize)]
pub struct ChatCompletionsErrorResponse {
pub error: ChatCompletionsErrorBody,
}
#[derive(Deserialize)]
pub struct ChatCompletionsErrorBody {
pub message: String,
}
+17
View File
@@ -0,0 +1,17 @@
pub mod chat_completions;
pub use chat_completions::ChatCompletionsProvider;
pub fn add(left: u64, right: u64) -> u64 {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}