pub mod types;
pub mod runtime_adapter;
use async_trait::async_trait;
use futures_util::stream::BoxStream;
use serde_json::Value;
use crate::channel::types::ChannelEvent;
#[derive(Debug, Clone)]
pub enum AgentDecision {
Chat {
request: Box<crate::provider::types::ChatRequest>,
},
ToolCall {
name: String,
input: Value,
},
Return(Value),
ThinkAgain,
Stop,
}
#[derive(Debug, Clone)]
pub struct AgentContext {
pub input: AgentInput,
pub messages: Vec<crate::provider::types::ChatMessage>,
pub tool_results: Vec<ToolResult>,
pub step: usize,
pub max_steps: usize,
}
impl AgentContext {
pub fn new(input: AgentInput, max_steps: usize) -> Self {
Self {
input,
messages: Vec::new(),
tool_results: Vec::new(),
step: 0,
max_steps,
}
}
pub fn add_message(&mut self, message: crate::provider::types::ChatMessage) {
self.messages.push(message);
}
pub fn add_tool_result(&mut self, tool_name: String, result: Value) {
self.tool_results.push(ToolResult { tool_name, result });
}
pub fn default_chat_request(&self) -> crate::provider::types::ChatRequest {
crate::provider::types::ChatRequest {
messages: self.messages.clone(),
model: None,
tools: None,
temperature: None,
max_tokens: None,
response_format: None,
metadata: None,
top_p: None,
top_k: None,
frequency_penalty: None,
presence_penalty: None,
stop: None,
extra: None,
}
}
pub fn default_chat_request_with(
&self,
params: &crate::provider::types::LlmParams,
) -> crate::provider::types::ChatRequest {
let mut request = self.default_chat_request();
params.apply_to(&mut request);
request
}
}
#[derive(Debug, Clone)]
pub struct ToolResult {
pub tool_name: String,
pub result: Value,
}
#[derive(Debug, Clone)]
pub struct AgentInput {
pub text: String,
pub context: serde_json::Value,
}
impl AgentInput {
pub fn new(text: impl Into<String>) -> Self {
let text = text.into();
assert!(!text.is_empty(), "AgentInput text must not be empty");
Self {
text,
context: serde_json::Value::Object(serde_json::Map::new()),
}
}
pub fn with_context(text: impl Into<String>, context: serde_json::Value) -> Self {
let text = text.into();
assert!(!text.is_empty(), "AgentInput text must not be empty");
Self {
text,
context,
}
}
pub fn builder(text: impl Into<String>) -> AgentInputBuilder {
AgentInputBuilder::new(text)
}
pub fn text(&self) -> &str {
&self.text
}
pub fn context(&self) -> &serde_json::Value {
&self.context
}
}
pub struct AgentInputBuilder {
text: String,
context: serde_json::Value,
}
impl AgentInputBuilder {
pub fn new(text: impl Into<String>) -> Self {
let text = text.into();
assert!(!text.is_empty(), "AgentInput text must not be empty");
Self {
text,
context: serde_json::Value::Object(serde_json::Map::new()),
}
}
pub fn with_context(
mut self,
key: impl Into<String>,
value: impl Into<serde_json::Value>,
) -> Self {
if let serde_json::Value::Object(ref mut map) = self.context {
map.insert(key.into(), value.into());
}
self
}
pub fn context(mut self, context: serde_json::Value) -> Self {
self.context = context;
self
}
pub fn build(self) -> AgentInput {
AgentInput {
text: self.text,
context: self.context,
}
}
}
impl From<String> for AgentInput {
fn from(text: String) -> Self {
Self::new(text)
}
}
impl From<&str> for AgentInput {
fn from(text: &str) -> Self {
Self::new(text)
}
}
#[derive(Debug, Clone)]
pub struct AgentOutput {
pub value: Value,
pub messages: Vec<crate::provider::types::ChatMessage>,
pub tool_calls: Vec<ToolCallRecord>,
pub usage: Option<crate::provider::types::Usage>,
}
impl AgentOutput {
pub fn new(value: Value) -> Self {
Self {
value,
messages: Vec::new(),
tool_calls: Vec::new(),
usage: None,
}
}
pub fn with_history(
value: Value,
messages: Vec<crate::provider::types::ChatMessage>,
tool_calls: Vec<ToolCallRecord>,
) -> Self {
Self {
value,
messages,
tool_calls,
usage: None,
}
}
pub fn with_usage(
value: Value,
messages: Vec<crate::provider::types::ChatMessage>,
tool_calls: Vec<ToolCallRecord>,
usage: Option<crate::provider::types::Usage>,
) -> Self {
Self {
value,
messages,
tool_calls,
usage,
}
}
pub fn text(&self) -> Option<&str> {
self.value.get("content").and_then(|v| v.as_str())
}
pub fn text_unwrap(&self) -> &str {
self.text().unwrap_or("")
}
pub fn text_or<'a>(&'a self, default: &'a str) -> &'a str {
self.text().unwrap_or(default)
}
pub fn into_text(self) -> String {
self.text().map(String::from).unwrap_or_default()
}
pub fn message_count(&self) -> usize {
self.messages.len()
}
pub fn tool_call_count(&self) -> usize {
self.tool_calls.len()
}
pub fn usage(&self) -> Option<&crate::provider::types::Usage> {
self.usage.as_ref()
}
pub fn total_tokens(&self) -> u32 {
self.usage.as_ref().map_or(0, |u| u.total_tokens)
}
pub fn prompt_tokens(&self) -> u32 {
self.usage.as_ref().map_or(0, |u| u.prompt_tokens)
}
pub fn completion_tokens(&self) -> u32 {
self.usage.as_ref().map_or(0, |u| u.completion_tokens)
}
pub fn usage_summary(&self) -> String {
match &self.usage {
Some(u) => format!(
"Tokens: {} total ({} prompt + {} completion)",
u.total_tokens, u.prompt_tokens, u.completion_tokens
),
None => "Tokens: N/A".to_string(),
}
}
}
impl std::fmt::Display for AgentOutput {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.text() {
Some(text) => write!(f, "{text}"),
None => Ok(()),
}
}
}
#[derive(Debug, Clone)]
pub struct ToolCallRecord {
pub name: String,
pub input: Value,
pub result: Value,
}
#[async_trait]
pub trait Agent: Send + Sync {
async fn think(&self, context: &AgentContext) -> AgentDecision;
fn name(&self) -> &str;
fn description(&self) -> Option<&str> {
None
}
async fn run(&self, input: AgentInput) -> Result<AgentOutput, AgentError> {
const DEFAULT_MAX_STEPS: usize = 20;
let mut context = AgentContext::new(input.clone(), DEFAULT_MAX_STEPS);
loop {
let decision = self.think(&context).await;
match decision {
AgentDecision::Return(value) => {
return Ok(AgentOutput::with_history(
value,
context.messages,
Vec::new(),
));
}
AgentDecision::Stop => {
return Ok(AgentOutput::with_history(
Value::Null,
context.messages,
Vec::new(),
));
}
AgentDecision::ThinkAgain => {
context.step += 1;
if context.step >= context.max_steps {
return Err(AgentError::MaxStepsExceeded {
max_steps: context.max_steps,
});
}
}
AgentDecision::Chat { request: _ } => {
return Err(AgentError::RequiresRuntime);
}
AgentDecision::ToolCall { .. } => {
return Err(AgentError::RequiresRuntime);
}
}
}
}
fn run_stream(
&self,
_input: AgentInput,
) -> BoxStream<'static, Result<ChannelEvent, AgentError>> {
use futures_util::stream;
Box::pin(stream::once(async {
Err(AgentError::Message("此 Agent 不支持流式输出".to_string()))
}))
}
async fn run_with(
&self,
executor: &dyn AgentExecutor,
input: AgentInput,
) -> Result<AgentOutput, AgentError>
where
Self: Sized,
{
executor.run(self, input).await
}
}
#[async_trait]
pub trait AgentExecutor: Send + Sync {
async fn run(&self, agent: &dyn Agent, input: AgentInput) -> Result<AgentOutput, AgentError>;
fn run_stream(&self, input: AgentInput)
-> BoxStream<'static, Result<ChannelEvent, AgentError>>;
}
pub use crate::error::AgentError;
pub use runtime_adapter::{
LogLevel, NativeRuntimeAdapter, RestrictedRuntimeAdapter, RuntimeAdapter, RuntimeCapabilities,
RuntimeError, RuntimePlatform, ShellResult,
};