Skip to content

Commit fbd4ccb

Browse files
committed
feat(chat-cli): Add knowledge tool for persistent context storage
Implement a knowledge tool that allows users to store and retrieve information across chat sessions. This tool provides semantic search capabilities for files, directories, and text content. Key features: - Add files or directories to knowledge base - Show all stored knowledge contexts - Remove contexts by name, ID, or path - Update existing contexts with new content - Clear all knowledge contexts - Search across all contexts with semantic search 🤖 Assisted by [Amazon Q Developer](https://aws.amazon.com/q/developer)
1 parent 2da230c commit fbd4ccb

File tree

9 files changed

+1217
-0
lines changed

9 files changed

+1217
-0
lines changed

crates/chat-cli/.amazonq/mcp.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"mcpServers": {}
3+
}

crates/chat-cli/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ amzn-qdeveloper-streaming-client = { path = "../amzn-qdeveloper-streaming-client
2929
amzn-toolkit-telemetry-client = { path = "../amzn-toolkit-telemetry-client" }
3030
anstream = "0.6.13"
3131
arboard = { version = "3.5.0", default-features = false }
32+
once_cell = "1.19.0"
33+
semantic_search_client = { path = "../semantic_search_client" }
3234
async-trait = "0.1.87"
3335
aws-config = "1.0.3"
3436
aws-credential-types = "1.0.3"

crates/chat-cli/src/cli/chat/command.rs

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ pub enum Command {
3636
Context {
3737
subcommand: ContextSubcommand,
3838
},
39+
Knowledge {
40+
subcommand: KnowledgeSubcommand,
41+
},
3942
PromptEditor {
4043
initial_text: Option<String>,
4144
},
@@ -182,6 +185,16 @@ pub enum ContextSubcommand {
182185
Help,
183186
}
184187

188+
#[derive(Debug, Clone, PartialEq, Eq)]
189+
pub enum KnowledgeSubcommand {
190+
Show,
191+
Add { path: String },
192+
Remove { path: String },
193+
Update { path: String },
194+
Clear,
195+
Help,
196+
}
197+
185198
impl ContextSubcommand {
186199
const ADD_USAGE: &str = "/context add [--global] [--force] <path1> [path2...]";
187200
const AVAILABLE_COMMANDS: &str = color_print::cstr! {"<cyan!>Available commands</cyan!>
@@ -447,6 +460,113 @@ impl Command {
447460
return Ok(match parts[0].to_lowercase().as_str() {
448461
"clear" => Self::Clear,
449462
"help" => Self::Help,
463+
"knowledge" => {
464+
if parts.len() < 2 {
465+
return Ok(Self::Knowledge {
466+
subcommand: KnowledgeSubcommand::Help,
467+
});
468+
}
469+
470+
match parts[1].to_lowercase().as_str() {
471+
"show" => Self::Knowledge {
472+
subcommand: KnowledgeSubcommand::Show,
473+
},
474+
"add" => {
475+
// Parse add command with path
476+
let mut path = None;
477+
478+
let args = match shlex::split(&parts[2..].join(" ")) {
479+
Some(args) => args,
480+
None => return Err("Failed to parse quoted arguments".to_string()),
481+
};
482+
483+
for arg in &args {
484+
if path.is_none() {
485+
path = Some(arg.to_string());
486+
} else {
487+
return Err(format!("Only a single path is allowed. Found extra path: {}", arg));
488+
}
489+
}
490+
491+
let path = path.ok_or_else(|| {
492+
format!(
493+
"Invalid /knowledge arguments.\n\nUsage:\n {}",
494+
KnowledgeSubcommand::ADD_USAGE
495+
)
496+
})?;
497+
498+
Self::Knowledge {
499+
subcommand: KnowledgeSubcommand::Add { path },
500+
}
501+
},
502+
"update" => {
503+
// Parse update command with path
504+
let mut path = None;
505+
506+
let args = match shlex::split(&parts[2..].join(" ")) {
507+
Some(args) => args,
508+
None => return Err("Failed to parse quoted arguments".to_string()),
509+
};
510+
511+
for arg in &args {
512+
if path.is_none() {
513+
path = Some(arg.to_string());
514+
} else {
515+
return Err(format!("Only a single path is allowed. Found extra path: {}", arg));
516+
}
517+
}
518+
519+
let path = path.ok_or_else(|| {
520+
format!(
521+
"Invalid /knowledge arguments.\n\nUsage:\n {}",
522+
KnowledgeSubcommand::UPDATE_USAGE
523+
)
524+
})?;
525+
526+
Self::Knowledge {
527+
subcommand: KnowledgeSubcommand::Update { path },
528+
}
529+
},
530+
"rm" => {
531+
// Parse rm command with path
532+
let mut path = None;
533+
let args = match shlex::split(&parts[2..].join(" ")) {
534+
Some(args) => args,
535+
None => return Err("Failed to parse quoted arguments".to_string()),
536+
};
537+
538+
for arg in &args {
539+
if path.is_none() {
540+
path = Some(arg.to_string());
541+
} else {
542+
return Err(format!("Only a single path is allowed. Found extra path: {}", arg));
543+
}
544+
}
545+
546+
let path = path.ok_or_else(|| {
547+
format!(
548+
"Invalid /knowledge arguments.\n\nUsage:\n {}",
549+
KnowledgeSubcommand::REMOVE_USAGE
550+
)
551+
})?;
552+
Self::Knowledge {
553+
subcommand: KnowledgeSubcommand::Remove { path },
554+
}
555+
},
556+
"clear" => Self::Knowledge {
557+
subcommand: KnowledgeSubcommand::Clear,
558+
},
559+
"help" => Self::Knowledge {
560+
subcommand: KnowledgeSubcommand::Help,
561+
},
562+
other => {
563+
return Err(KnowledgeSubcommand::usage_msg(format!(
564+
"Unknown subcommand '{}'.",
565+
other
566+
)));
567+
},
568+
}
569+
},
450570
"compact" => {
451571
let mut prompt = None;
452572
let show_summary = true;
@@ -1133,3 +1253,46 @@ mod tests {
11331253
}
11341254
}
11351255
}
1256+
impl KnowledgeSubcommand {
1257+
const ADD_USAGE: &str = "/knowledge add <path>";
1258+
const AVAILABLE_COMMANDS: &str = color_print::cstr! {"<cyan!>Available commands</cyan!>
1259+
<em>help</em> <black!>Show an explanation for the knowledge command</black!>
1260+
<em>show</em> <black!>Display the knowledge base contents</black!>
1261+
<em>add <<path>></em> <black!>Add a file or directory to knowledge base</black!>
1262+
<em>update <<path>></em> <black!>Update a file or directory in knowledge base</black!>
1263+
<em>rm <<path>></em> <black!>Remove specified knowledge context by path</black!>
1264+
<em>clear</em> <black!>Remove all knowledge contexts</black!>"};
1265+
const BASE_COMMAND: &str = color_print::cstr! {"<cyan!>Usage: /knowledge [SUBCOMMAND]</cyan!>
1266+
1267+
<cyan!>Description</cyan!>
1268+
Manage knowledge base for semantic search and retrieval.
1269+
Knowledge base is used to store and search information across chat sessions."};
1270+
const REMOVE_USAGE: &str = "/knowledge rm <path>";
1271+
const UPDATE_USAGE: &str = "/knowledge update <path>";
1272+
1273+
fn usage_msg(header: impl AsRef<str>) -> String {
1274+
format!(
1275+
"{}\n\n{}\n\n{}",
1276+
header.as_ref(),
1277+
Self::BASE_COMMAND,
1278+
Self::AVAILABLE_COMMANDS
1279+
)
1280+
}
1281+
1282+
pub fn help_text() -> String {
1283+
color_print::cformat!(
1284+
r#"
1285+
<magenta,em>(Beta) Knowledge Base Management</magenta,em>
1286+
1287+
Knowledge base allows you to store and search information across chat sessions.
1288+
Files and directories added to the knowledge base are indexed for semantic search,
1289+
enabling more relevant and contextual responses.
1290+
1291+
{}
1292+
1293+
{}"#,
1294+
Self::BASE_COMMAND,
1295+
Self::AVAILABLE_COMMANDS
1296+
)
1297+
}
1298+
}

0 commit comments

Comments
 (0)