Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/ask/chat_agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ pub async fn ask_agent(
client: &Client<OpenAIConfig>,
api_model: &str,
max_iterations: Option<usize>,
workspace_name: Option<&str>,
) -> Result<AskOutput> {
let max_iterations = max_iterations.unwrap_or(20);
let mut result = AskOutput {
Expand Down Expand Up @@ -88,7 +89,7 @@ pub async fn ask_agent(

// Call the appropriate tool
let response_content =
call_tool(name, args, &files, model, &mut result).await?;
call_tool(name, args, &files, model, &mut result, workspace_name).await?;

// Print summary of the tool response
print_tool_summary(&response_content);
Expand Down
4 changes: 3 additions & 1 deletion src/ask/responses_agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ pub async fn ask_agent_responses(
client: &Client<OpenAIConfig>,
api_model: &str,
max_iterations: Option<usize>,
workspace_name: Option<&str>,
) -> Result<AskOutput> {
let max_iterations = max_iterations.unwrap_or(20);
let mut result = AskOutput {
Expand Down Expand Up @@ -96,7 +97,8 @@ pub async fn ask_agent_responses(
let args = &function_call.arguments;

// Call the appropriate tool
let response_content = call_tool(name, args, &files, model, &mut result).await?;
let response_content =
call_tool(name, args, &files, model, &mut result, workspace_name).await?;

// Print summary of the tool response
print_tool_summary(&response_content);
Expand Down
11 changes: 10 additions & 1 deletion src/ask/tool_calling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub async fn call_tool(
files: &[String],
model: &StaticModel,
cur_output: &mut AskOutput,
workspace_name: Option<&str>,
) -> Result<String> {
let function_args: Value = serde_json::from_str(args)?;

Expand Down Expand Up @@ -96,7 +97,15 @@ pub async fn call_tool(
println!(" top_k: {}", top_k);
}

SearchTool::search(files, query, model, config, &mut cur_output.files_searched).await
SearchTool::search(
files,
query,
model,
config,
&mut cur_output.files_searched,
workspace_name,
)
.await
}
"read" => {
let path = function_args["path"]
Expand Down
6 changes: 4 additions & 2 deletions src/ask/tools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ impl SearchTool {
model: &StaticModel,
config: SearchConfig,
files_searched: &mut Vec<String>,
workspace_name: Option<&str>,
) -> Result<String> {
let query = if config.ignore_case {
query.to_lowercase()
Expand All @@ -226,9 +227,10 @@ impl SearchTool {

// Handle file input with optional workspace integration
#[cfg(feature = "workspace")]
if Workspace::active().is_ok() {
if Workspace::active(workspace_name).is_ok() {
// Workspace mode: use persisted line embeddings for speed
let ranked_lines = search_with_workspace(files, &query, model, &config).await?;
let ranked_lines =
search_with_workspace(files, &query, model, &config, workspace_name).await?;

// Track files that were searched (have results)
for ranked_line in &ranked_lines {
Expand Down
39 changes: 32 additions & 7 deletions src/bin/semtools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,15 @@ enum WorkspaceCommands {
/// Use or create a workspace (prints export command to run)
Use { name: String },
/// Show active workspace and basic stats
Status,
Status {
#[clap(default_value = None)]
name: Option<String>,
},
/// Remove stale or missing files from store
Prune {},
Prune {
#[clap(default_value = None)]
name: Option<String>,
},
}

#[derive(Subcommand, Debug)]
Expand Down Expand Up @@ -70,6 +76,10 @@ enum Commands {
/// Output results in JSON format
#[clap(short, long)]
json: bool,

/// Use a specific workspace
#[arg(short, long, default_value = None)]
workspace: Option<String>,
},
#[cfg(feature = "ask")]
/// A CLI tool for document-based question-answering
Expand Down Expand Up @@ -104,6 +114,10 @@ enum Commands {
/// Output results in JSON or text format
#[clap(short, long)]
json: bool,

/// Use a specific workspace
#[arg(short, long, default_value = None)]
workspace: Option<String>,
},
#[cfg(feature = "workspace")]
/// Manage semtools workspaces
Expand All @@ -130,9 +144,18 @@ async fn main() -> anyhow::Result<()> {
model,
api_mode,
json,
workspace,
} => {
ask_cmd(
query, files, config, api_key, base_url, model, api_mode, json,
query,
files,
config,
api_key,
base_url,
model,
api_mode,
json,
workspace.as_deref(),
)
.await?;
}
Expand All @@ -152,6 +175,7 @@ async fn main() -> anyhow::Result<()> {
max_distance,
ignore_case,
json,
workspace,
} => {
search_cmd(
query,
Expand All @@ -161,18 +185,19 @@ async fn main() -> anyhow::Result<()> {
max_distance,
ignore_case,
json,
workspace.as_deref(),
)
.await?;
}
Commands::Workspace { json, command } => match command {
WorkspaceCommands::Use { name } => {
workspace_use_cmd(name, json).await?;
}
WorkspaceCommands::Prune {} => {
workspace_prune_cmd(json).await?;
WorkspaceCommands::Prune { name } => {
workspace_prune_cmd(json, name.as_deref()).await?;
}
WorkspaceCommands::Status => {
workspace_status_cmd(json).await?;
WorkspaceCommands::Status { name } => {
workspace_status_cmd(json, name.as_deref()).await?;
}
},
}
Expand Down
23 changes: 21 additions & 2 deletions src/cmds/ask.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub async fn ask_cmd(
model: Option<String>,
api_mode: Option<String>,
json: bool,
workspace_name: Option<&str>,
) -> Result<()> {
// Load configuration
let config_path = config.unwrap_or_else(SemtoolsConfig::default_config_path);
Expand Down Expand Up @@ -134,10 +135,28 @@ pub async fn ask_cmd(
// Run the appropriate agent based on API mode
let output = match api_mode {
ApiMode::Chat => {
ask_agent(files, &query, &model, &client, &model_name, max_iterations).await?
ask_agent(
files,
&query,
&model,
&client,
&model_name,
max_iterations,
workspace_name,
)
.await?
}
ApiMode::Responses => {
ask_agent_responses(files, &query, &model, &client, &model_name, max_iterations).await?
ask_agent_responses(
files,
&query,
&model,
&client,
&model_name,
max_iterations,
workspace_name,
)
.await?
}
};

Expand Down
7 changes: 5 additions & 2 deletions src/cmds/search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ fn print_workspace_search_results(ranked_lines: &[RankedLine], n_lines: usize) {
}
}

#[allow(clippy::too_many_arguments)]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lol oh no. Good to know :p

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah the baseline for clippy is max 7 arguments: I find it both kind of funny and really awesome that Rust is so opinionated to even have a limit on the number of parameters enforced in their linter

pub async fn search_cmd(
query: String,
files: Vec<String>,
Expand All @@ -117,6 +118,7 @@ pub async fn search_cmd(
max_distance: Option<f64>,
ignore_case: bool,
json: bool,
workspace_name: Option<&str>,
) -> Result<()> {
let model = StaticModel::from_pretrained(
MODEL_NAME, // "minishlab/potion-multilingual-128M",
Expand Down Expand Up @@ -192,15 +194,16 @@ pub async fn search_cmd(
// Handle file input with optional workspace integration
#[cfg(feature = "workspace")]
{
if Workspace::active().is_ok() {
if Workspace::active(workspace_name).is_ok() {
// Workspace mode: use persisted line embeddings for speed
let config = SearchConfig {
n_lines,
top_k,
max_distance,
ignore_case,
};
let ranked_lines = search_with_workspace(&files, &query, &model, &config).await?;
let ranked_lines =
search_with_workspace(&files, &query, &model, &config, workspace_name).await?;

if json {
// Convert workspace results to SearchResultJSON
Expand Down
14 changes: 8 additions & 6 deletions src/cmds/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ pub async fn workspace_use_cmd(name: String, json: bool) -> Result<()> {
println!(" export SEMTOOLS_WORKSPACE={name}");
println!();
println!("Or add this to your shell profile (.bashrc, .zshrc, etc.)");
println!();
println!("Or use the `--workspace` option on the commands that support it");
}
}
#[cfg(not(feature = "workspace"))]
Expand All @@ -64,11 +66,11 @@ pub async fn workspace_use_cmd(name: String, json: bool) -> Result<()> {
Ok(())
}

pub async fn workspace_status_cmd(json: bool) -> Result<()> {
pub async fn workspace_status_cmd(json: bool, workspace_name: Option<&str>) -> Result<()> {
#[cfg(feature = "workspace")]
{
let _name = Workspace::active().context("No active workspace")?;
let ws = Workspace::open()?;
let _name = Workspace::active(workspace_name).context("No active workspace")?;
let ws = Workspace::open(workspace_name)?;

// Open store and get stats
let store = Store::open(&ws.config.root_dir)?;
Expand Down Expand Up @@ -110,11 +112,11 @@ pub async fn workspace_status_cmd(json: bool) -> Result<()> {
Ok(())
}

pub async fn workspace_prune_cmd(json: bool) -> Result<()> {
pub async fn workspace_prune_cmd(json: bool, workspace_name: Option<&str>) -> Result<()> {
#[cfg(feature = "workspace")]
{
let _name = Workspace::active().context("No active workspace")?;
let ws = Workspace::open()?;
let _name = Workspace::active(workspace_name).context("No active workspace")?;
let ws = Workspace::open(workspace_name)?;
let store = Store::open(&ws.config.root_dir)?;

// Get all document paths from the workspace
Expand Down
3 changes: 2 additions & 1 deletion src/search/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,9 +148,10 @@ pub async fn search_with_workspace(
query: &str,
model: &StaticModel,
config: &SearchConfig,
workspace_name: Option<&str>,
) -> Result<Vec<RankedLine>> {
let query_embedding = model.encode_single(query);
let ws = Workspace::open()?;
let ws = Workspace::open(workspace_name)?;
let store = Store::open(&ws.config.root_dir)?;

// Step 1: Analyze document states (changed/new/unchanged)
Expand Down
Loading