Skip to main content

ds_api/agent/
agent_core.rs

1use std::collections::HashMap;
2
3use crate::api::ApiClient;
4use crate::conversation::{Conversation, DeepseekConversation, Summarizer};
5use crate::raw::request::message::{Message, Role};
6use crate::tool_trait::Tool;
7use serde_json::Value;
8
9/// Tool call event (result).
10///
11/// Represents a single tool invocation result produced by the agent.
12#[derive(Debug, Clone)]
13pub struct ToolCallEvent {
14    pub id: String,
15    pub name: String,
16    pub args: Value,
17    pub result: Value,
18}
19
20/// Single agent response exposed to callers.
21///
22/// May contain assistant text content or a list of tool call events.
23#[derive(Debug, Clone)]
24pub struct AgentResponse {
25    pub content: Option<String>,
26    pub tool_calls: Vec<ToolCallEvent>,
27}
28
29/// DeepseekAgent: encapsulates a conversation (`Conversation`) and a collection of tools.
30///
31/// Responsible for coordinating API calls and executing tools as requested by the model.
32/// Fields use `pub(crate)` visibility so the sibling `stream` submodule can access internal
33/// state (for example `tools` and `tool_index`).
34pub struct DeepseekAgent {
35    pub(crate) client: ApiClient,
36    pub(crate) conversation: DeepseekConversation,
37    pub(crate) tools: Vec<Box<dyn Tool>>,
38    pub(crate) tool_index: HashMap<String, usize>,
39}
40
41impl DeepseekAgent {
42    /// Create an Agent using the provided token.
43    ///
44    /// This internally constructs an `ApiClient` and attaches a `DeepseekConversation`.
45    pub fn new(token: impl Into<String>) -> Self {
46        let client = ApiClient::new(token.into());
47        let conversation = DeepseekConversation::new(client.clone());
48        Self {
49            client,
50            conversation,
51            tools: vec![],
52            tool_index: HashMap::new(),
53        }
54    }
55
56    /// Add a tool (supports method chaining).
57    ///
58    /// Registers the tool and indexes its raw (protocol) function names so incoming
59    /// tool call requests can be routed to the correct implementation.
60    pub fn add_tool<TT: Tool + 'static>(mut self, tool: TT) -> Self {
61        let idx = self.tools.len();
62        for raw in tool.raw_tools() {
63            // raw.function.name is the protocol-level name used to match tool call requests
64            self.tool_index.insert(raw.function.name.clone(), idx);
65        }
66        self.tools.push(Box::new(tool));
67        self
68    }
69
70    /// Push a user message into the conversation and return an `AgentStream`.
71    ///
72    /// The returned `AgentStream` drives the API request and any subsequent tool execution.
73    /// The return type uses a fully-qualified path and depends on the sibling `stream`
74    /// submodule providing `AgentStream`.
75    pub fn chat(mut self, user_message: &str) -> crate::agent::stream::AgentStream {
76        self.conversation.push_user_input(user_message.to_string());
77        crate::agent::stream::AgentStream::new(self)
78    }
79
80    /// Set a custom system prompt to inject at the start of the conversation.
81    ///
82    /// Builder-style: returns `self` so the call can be chained.
83    pub fn with_system_prompt(mut self, prompt: impl Into<String>) -> Self {
84        let p = prompt.into();
85        // Add a system message to the conversation history
86        self.conversation
87            .add_message(Message::new(Role::System, p.as_str()));
88        self
89    }
90
91    /// Set a summarizer to use for conversation summarization.
92    ///
93    /// Builder-style: returns `self` so the call can be chained.
94    pub fn with_summarizer(mut self, summarizer: impl Summarizer + 'static) -> Self {
95        self.conversation = self.conversation.with_summarizer(summarizer);
96        self
97    }
98}