117 lines
3.4 KiB
Rust
117 lines
3.4 KiB
Rust
use std::pin::Pin;
|
|
|
|
use db::cache::AppCache;
|
|
use db::database::AppDatabase;
|
|
use models::agents::model;
|
|
use models::projects::{project, project_context_setting};
|
|
use models::repos::repo;
|
|
use models::rooms::{room, room_message};
|
|
use models::users::user;
|
|
use config::AppConfig;
|
|
use std::collections::HashMap;
|
|
use uuid::Uuid;
|
|
|
|
/// Maximum recursion rounds for tool-call loops (AI → tool → result → AI).
|
|
/// Previous default of 3 caused frequent silent termination on realistic multi-step queries.
|
|
pub const DEFAULT_MAX_TOOL_DEPTH: usize = 99;
|
|
|
|
/// A single chunk from an AI streaming response.
|
|
#[derive(Debug, Clone)]
|
|
pub struct AiStreamChunk {
|
|
pub content: String,
|
|
pub done: bool,
|
|
/// What kind of content this chunk contains — helps the frontend render
|
|
/// thinking, tool calls, and results with different styles.
|
|
pub chunk_type: AiChunkType,
|
|
}
|
|
|
|
/// Type of streaming chunk, used by the frontend for rendering.
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub enum AiChunkType {
|
|
/// AI reasoning/thinking text before a tool call or answer.
|
|
Thinking,
|
|
/// Final answer text from the AI.
|
|
Answer,
|
|
/// A tool call is being executed (content = tool name + args summary).
|
|
ToolCall,
|
|
/// Tool execution result (content = result or error).
|
|
ToolResult,
|
|
}
|
|
|
|
impl Default for AiChunkType {
|
|
fn default() -> Self {
|
|
Self::Answer
|
|
}
|
|
}
|
|
|
|
const THINK_OPEN: &str = "\x3cthinking\x3e";
|
|
const THINK_CLOSE: &str = "\x3c/response\x3e";
|
|
|
|
/// Strip XML-format thinking tags that some models (e.g. DeepSeek-R1) embed
|
|
/// in reasoning output. Also normalizes excessive consecutive newlines (3+ → 2).
|
|
pub fn normalize_thinking_content(content: &str) -> String {
|
|
let content = content
|
|
.replace(THINK_CLOSE, "")
|
|
.replace(THINK_OPEN, "")
|
|
.replace("\x3cthinking", "")
|
|
.replace("/response\x3e", "");
|
|
let mut result = String::with_capacity(content.len());
|
|
let mut newline_count = 0usize;
|
|
for ch in content.chars() {
|
|
if ch == '\n' {
|
|
newline_count += 1;
|
|
if newline_count <= 2 {
|
|
result.push(ch);
|
|
}
|
|
} else {
|
|
newline_count = 0;
|
|
result.push(ch);
|
|
}
|
|
}
|
|
result.trim().to_string()
|
|
}
|
|
pub type StreamCallback = Box<
|
|
dyn Fn(AiStreamChunk) -> Pin<Box<dyn std::future::Future<Output = ()> + Send>> + Send + Sync,
|
|
>;
|
|
|
|
pub struct AiRequest {
|
|
pub db: AppDatabase,
|
|
pub cache: AppCache,
|
|
pub config: AppConfig,
|
|
pub model: model::Model,
|
|
pub project: project::Model,
|
|
pub context_setting: Option<project_context_setting::Model>,
|
|
pub sender: user::Model,
|
|
pub room: room::Model,
|
|
pub input: String,
|
|
pub mention: Vec<Mention>,
|
|
pub history: Vec<room_message::Model>,
|
|
pub user_names: HashMap<Uuid, String>,
|
|
pub temperature: f64,
|
|
pub max_tokens: i32,
|
|
pub top_p: f64,
|
|
pub frequency_penalty: f64,
|
|
pub presence_penalty: f64,
|
|
pub think: bool,
|
|
pub tools: Option<Vec<serde_json::Value>>,
|
|
pub max_tool_depth: usize,
|
|
}
|
|
|
|
pub enum Mention {
|
|
User(user::Model),
|
|
Repo(repo::Model),
|
|
}
|
|
|
|
pub mod chat_execution;
|
|
pub mod context;
|
|
pub mod message_builder;
|
|
pub mod nonstreaming_execution;
|
|
pub mod react_execution;
|
|
pub mod service;
|
|
pub mod session_recording;
|
|
pub mod state;
|
|
pub mod streaming_execution;
|
|
pub use context::{AiContextSenderType, RoomMessageContext};
|
|
pub use service::ChatService;
|
|
pub use state::{AgentRuntime, AgentState};
|