Skip to content

Commit de005ee

Browse files
committed
feat: support multi models
1 parent 0fdacb2 commit de005ee

File tree

24 files changed

+204
-86
lines changed

24 files changed

+204
-86
lines changed

agents/anda_assistant/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name = "anda_assistant"
33
description = "Anda -- an AI Assistant powered by the Knowledge Interaction Protocol (KIP)."
44
repository = "https://github.com/ldclabs/anda/tree/main/agents/anda_assistant"
55
publish = true
6-
version = "0.4.0"
6+
version = "0.4.1"
77
edition.workspace = true
88
keywords.workspace = true
99
categories.workspace = true

agents/anda_assistant/src/agent.rs

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
use anda_cognitive_nexus::{CognitiveNexus, ConceptPK};
22
use anda_core::{
3-
Agent, AgentContext, AgentOutput, BoxError, CompletionRequest, Document, Documents,
4-
Message, Principal, Resource, StateFeatures, Tool, ToolSet, Usage, evaluate_tokens,
5-
update_resources,
3+
Agent, AgentContext, AgentOutput, BoxError, CompletionRequest, Document, Documents, Message,
4+
Principal, Resource, StateFeatures, Tool, ToolSet, Usage, evaluate_tokens, update_resources,
65
};
76
use anda_db::{database::AndaDB, index::BTree};
87
use anda_engine::{
@@ -219,14 +218,36 @@ impl Agent<AgentCtx> for Assistant {
219218
((evaluate_tokens(&instructions) + evaluate_tokens(&prompt)) as f64 * 1.2) as usize,
220219
) * 3; // Rough estimate of bytes per token
221220
let mut writer: Vec<u8> = Vec::with_capacity(256);
222-
let _ = serde_json::to_writer(&mut writer, &conversations);
223-
let mut history_bytes = writer.len();
221+
let mut history_bytes = if serde_json::to_writer(&mut writer, &conversations).is_ok() {
222+
writer.len()
223+
} else {
224+
0
225+
};
226+
227+
// Keep the most recent conversations; remove the oldest first.
224228
while history_bytes > max_history_bytes && !conversations.is_empty() {
229+
let oldest_idx = conversations
230+
.iter()
231+
.enumerate()
232+
.min_by_key(|(_, c)| c.created_at)
233+
.map(|(idx, _)| idx)
234+
.unwrap_or(0);
235+
225236
writer.clear();
226-
let conv = conversations.remove(0);
227-
cursor = BTree::to_cursor(&conv._id);
228-
let _ = serde_json::to_writer(&mut writer, &conv);
229-
history_bytes = history_bytes.saturating_sub(writer.len());
237+
if serde_json::to_writer(&mut writer, &conversations[oldest_idx]).is_ok() {
238+
history_bytes = history_bytes.saturating_sub(writer.len());
239+
} else {
240+
break;
241+
}
242+
243+
conversations.remove(oldest_idx);
244+
}
245+
246+
// Cursor should allow listing conversations older than the oldest one we kept.
247+
if !conversations.is_empty()
248+
&& let Some(min_id) = conversations.iter().map(|c| c._id).min()
249+
{
250+
cursor = BTree::to_cursor(&min_id);
230251
}
231252

232253
let mut history_docs: Vec<Document> = Vec::with_capacity(conversations.len() + 2);
@@ -257,7 +278,7 @@ impl Agent<AgentCtx> for Assistant {
257278
content: vec![
258279
format!(
259280
"Current Datetime: {}\n---\n{}",
260-
rfc3339_datetime(created_at).unwrap(),
281+
rfc3339_datetime(created_at).unwrap_or_else(|| format!("unix_ms:{created_at}")),
261282
Documents::new("history".to_string(), history_docs)
262283
)
263284
.into(),
@@ -334,8 +355,16 @@ impl Agent<AgentCtx> for Assistant {
334355
conversation.messages.clear(); // clear the first pending message.
335356
conversation.append_messages(res.chat_history);
336357
} else {
337-
res.chat_history.drain(0..conversation.messages.len());
338-
conversation.append_messages(res.chat_history);
358+
let existing_len = conversation.messages.len();
359+
if res.chat_history.len() >= existing_len {
360+
res.chat_history.drain(0..existing_len);
361+
conversation.append_messages(res.chat_history);
362+
} else {
363+
// Unexpected: runner returned shorter full history.
364+
// Fall back to replacing stored messages.
365+
conversation.messages.clear();
366+
conversation.append_messages(res.chat_history);
367+
}
339368
}
340369

341370
conversation.artifacts = artifacts;

agents/anda_bot/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,5 @@ hex = { workspace = true }
3737

3838
[dev-dependencies]
3939

40-
[patch.crates-io]
41-
candid = { git = "https://github.com/ldclabs/candid.git", rev = "4cf7d02bad9530172cb4cafe733cb1e80689b793" } # remove check_recursion on stack for TEE
40+
# [patch.crates-io]
41+
# candid = { git = "https://github.com/ldclabs/candid.git", rev = "4cf7d02bad9530172cb4cafe733cb1e80689b793" } # remove check_recursion on stack for TEE

agents/anda_nexus/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name = "anda_nexus"
33
description = "Anda -- an AI Nexus powered by the Knowledge Interaction Protocol (KIP)."
44
repository = "https://github.com/ldclabs/anda/tree/main/agents/anda_nexus"
55
publish = true
6-
version = "0.2.0"
6+
version = "0.2.1"
77
edition.workspace = true
88
keywords.workspace = true
99
categories.workspace = true

agents/anda_nexus/src/bin/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ async fn main() -> Result<(), BoxError> {
135135
visibility: Visibility::Public,
136136
}))
137137
.register_tools(tools)?
138-
.register_agent(agent)?
138+
.register_agent(agent, None)?
139139
.export_tools(tools_name);
140140

141141
// Initialize and start the server

anda_core/src/agent.rs

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,8 @@ pub trait AgentDyn<C>: Send + Sync
144144
where
145145
C: AgentContext + Send + Sync,
146146
{
147+
fn label(&self) -> &str;
148+
147149
fn name(&self) -> String;
148150

149151
fn definition(&self) -> FunctionDefinition;
@@ -163,34 +165,43 @@ where
163165
}
164166

165167
/// Adapter for converting static Agent to dynamic dispatch.
166-
struct AgentWrapper<T, C>(Arc<T>, PhantomData<C>)
168+
struct AgentWrapper<T, C>
167169
where
168170
T: Agent<C> + 'static,
169-
C: AgentContext + Send + Sync + 'static;
171+
C: AgentContext + Send + Sync + 'static,
172+
{
173+
inner: Arc<T>,
174+
label: String,
175+
_phantom: PhantomData<C>,
176+
}
170177

171178
impl<T, C> AgentDyn<C> for AgentWrapper<T, C>
172179
where
173180
T: Agent<C> + 'static,
174181
C: AgentContext + Send + Sync + 'static,
175182
{
183+
fn label(&self) -> &str {
184+
&self.label
185+
}
186+
176187
fn name(&self) -> String {
177-
self.0.name()
188+
self.inner.name()
178189
}
179190

180191
fn definition(&self) -> FunctionDefinition {
181-
self.0.definition()
192+
self.inner.definition()
182193
}
183194

184195
fn tool_dependencies(&self) -> Vec<String> {
185-
self.0.tool_dependencies()
196+
self.inner.tool_dependencies()
186197
}
187198

188199
fn supported_resource_tags(&self) -> Vec<String> {
189-
self.0.supported_resource_tags()
200+
self.inner.supported_resource_tags()
190201
}
191202

192203
fn init(&self, ctx: C) -> BoxPinFut<Result<(), BoxError>> {
193-
let agent = self.0.clone();
204+
let agent = self.inner.clone();
194205
Box::pin(async move { agent.init(ctx).await })
195206
}
196207

@@ -200,7 +211,7 @@ where
200211
prompt: String,
201212
resources: Vec<Resource>,
202213
) -> BoxPinFut<Result<AgentOutput, BoxError>> {
203-
let agent = self.0.clone();
214+
let agent = self.inner.clone();
204215
Box::pin(async move { agent.run(ctx, prompt, resources).await })
205216
}
206217
}
@@ -275,11 +286,13 @@ where
275286
/// # Returns
276287
/// - Vec<[`Function`]>: Vector of agent functions.
277288
pub fn functions(&self, names: Option<&[&str]>) -> Vec<Function> {
289+
let names: Option<Vec<String>> =
290+
names.map(|names| names.iter().map(|n| n.to_ascii_lowercase()).collect());
278291
self.set
279292
.iter()
280-
.filter_map(|(name, agent)| match names {
293+
.filter_map(|(name, agent)| match &names {
281294
Some(names) => {
282-
if names.contains(&name.as_str()) {
295+
if names.contains(name) {
283296
Some(Function {
284297
definition: agent.definition(),
285298
supported_resource_tags: agent.supported_resource_tags(),
@@ -299,7 +312,7 @@ where
299312
/// Extracts resources from the provided list based on the agent's supported tags.
300313
pub fn select_resources(&self, name: &str, resources: &mut Vec<Resource>) -> Vec<Resource> {
301314
self.set
302-
.get(name)
315+
.get(&name.to_ascii_lowercase())
303316
.map(|agent| {
304317
let supported_tags = agent.supported_resource_tags();
305318
select_resources(resources, &supported_tags)
@@ -311,7 +324,7 @@ where
311324
///
312325
/// # Arguments
313326
/// - `agent`: The agent to register, must implement [`Agent`] trait.
314-
pub fn add<T>(&mut self, agent: T) -> Result<(), BoxError>
327+
pub fn add<T>(&mut self, agent: T, label: Option<String>) -> Result<(), BoxError>
315328
where
316329
T: Agent<C> + Send + Sync + 'static,
317330
{
@@ -321,13 +334,17 @@ where
321334
}
322335

323336
validate_function_name(&name)?;
324-
let agent_dyn = AgentWrapper(Arc::new(agent), PhantomData);
337+
let agent_dyn = AgentWrapper {
338+
inner: Arc::new(agent),
339+
label: label.unwrap_or_else(|| name.clone()),
340+
_phantom: PhantomData,
341+
};
325342
self.set.insert(name, Box::new(agent_dyn));
326343
Ok(())
327344
}
328345

329346
/// Retrieves an agent by name.
330347
pub fn get(&self, name: &str) -> Option<&dyn AgentDyn<C>> {
331-
self.set.get(name).map(|v| &**v)
348+
self.set.get(&name.to_ascii_lowercase()).map(|v| &**v)
332349
}
333350
}

anda_core/src/http.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ pub async fn cbor_rpc(
231231
body: Vec<u8>,
232232
) -> Result<ByteBufB64, HttpRPCError> {
233233
let mut headers = headers.unwrap_or_default();
234-
let ct: http::HeaderValue = CONTENT_TYPE_CBOR.parse().unwrap();
234+
let ct: http::HeaderValue = http::HeaderValue::from_static(CONTENT_TYPE_CBOR);
235235
headers.insert(header::CONTENT_TYPE, ct.clone());
236236
headers.insert(header::ACCEPT, ct);
237237
let res = client

anda_core/src/model/resource.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,7 @@ pub fn update_resources(user: &Principal, resources: Vec<Resource>) -> Vec<Resou
7979
}
8080

8181
if r._id == 0 {
82-
if r.metadata.is_none() {
83-
r.metadata = Some(Map::new());
84-
}
85-
let meta = r.metadata.as_mut().unwrap();
82+
let meta = r.metadata.get_or_insert_with(Map::new);
8683
meta.insert("user".to_string(), user.clone().into());
8784
meta.insert("created_at".to_string(), utc.clone().into());
8885
}

anda_core/src/tool.rs

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ where
221221

222222
/// Checks if a tool with the given name exists in the set
223223
pub fn contains(&self, name: &str) -> bool {
224-
self.set.contains_key(name)
224+
self.set.contains_key(&name.to_ascii_lowercase())
225225
}
226226

227227
/// Returns the names of all tools in the set
@@ -231,7 +231,9 @@ where
231231

232232
/// Retrieves definition for a specific agent.
233233
pub fn definition(&self, name: &str) -> Option<FunctionDefinition> {
234-
self.set.get(name).map(|tool| tool.definition())
234+
self.set
235+
.get(&name.to_ascii_lowercase())
236+
.map(|tool| tool.definition())
235237
}
236238

237239
/// Returns definitions for all or specified tools.
@@ -242,11 +244,13 @@ where
242244
/// # Returns
243245
/// - Vec<[`FunctionDefinition`]>: Vector of agent definitions.
244246
pub fn definitions(&self, names: Option<&[&str]>) -> Vec<FunctionDefinition> {
247+
let names: Option<Vec<String>> =
248+
names.map(|names| names.iter().map(|n| n.to_ascii_lowercase()).collect());
245249
self.set
246250
.iter()
247-
.filter_map(|(name, tool)| match names {
251+
.filter_map(|(name, tool)| match &names {
248252
Some(names) => {
249-
if names.contains(&name.as_str()) {
253+
if names.contains(name) {
250254
Some(tool.definition())
251255
} else {
252256
None
@@ -265,11 +269,13 @@ where
265269
/// # Returns
266270
/// - Vec<[`Function`]>: Vector of tool functions.
267271
pub fn functions(&self, names: Option<&[&str]>) -> Vec<Function> {
272+
let names: Option<Vec<String>> =
273+
names.map(|names| names.iter().map(|n| n.to_ascii_lowercase()).collect());
268274
self.set
269275
.iter()
270-
.filter_map(|(name, tool)| match names {
276+
.filter_map(|(name, tool)| match &names {
271277
Some(names) => {
272-
if names.contains(&name.as_str()) {
278+
if names.contains(name) {
273279
Some(Function {
274280
definition: tool.definition(),
275281
supported_resource_tags: tool.supported_resource_tags(),
@@ -289,7 +295,7 @@ where
289295
/// Extracts resources from the provided list based on the tool's supported tags.
290296
pub fn select_resources(&self, name: &str, resources: &mut Vec<Resource>) -> Vec<Resource> {
291297
self.set
292-
.get(name)
298+
.get(&name.to_ascii_lowercase())
293299
.map(|tool| {
294300
let supported_tags = tool.supported_resource_tags();
295301
select_resources(resources, &supported_tags)
@@ -305,7 +311,7 @@ where
305311
where
306312
T: Tool<C> + Send + Sync + 'static,
307313
{
308-
let name = tool.name();
314+
let name = tool.name().to_ascii_lowercase();
309315
validate_function_name(&name)?;
310316
if self.set.contains_key(&name) {
311317
return Err(format!("tool {} already exists", name).into());
@@ -318,6 +324,6 @@ where
318324

319325
/// Retrieves a tool by name
320326
pub fn get(&self, name: &str) -> Option<&dyn ToolDyn<C>> {
321-
self.set.get(name).map(|v| &**v)
327+
self.set.get(&name.to_ascii_lowercase()).map(|v| &**v)
322328
}
323329
}

0 commit comments

Comments
 (0)