sqlite_graphrag/commands/ingest/
args.rs1use crate::cli::MemoryType;
4use crate::output::JsonOutputFormat;
5use std::path::PathBuf;
6
7#[derive(clap::Args)]
8#[command(after_long_help = "EXAMPLES:\n \
9 # Ingest every Markdown file under ./docs as `document` memories\n \
10 sqlite-graphrag ingest ./docs --type document\n\n \
11 # Ingest .txt files recursively under ./notes\n \
12 sqlite-graphrag ingest ./notes --type note --pattern '*.txt' --recursive\n\n \
13 # Namespace derived names with a kebab-case prefix (projx-<derived>)\n \
14 sqlite-graphrag ingest ./docs --name-prefix projx- --dry-run\n\n \
15 # Enable automatic URL extraction (URL-regex only since v1.0.79)\n \
16 sqlite-graphrag ingest ./big-corpus --type reference --enable-ner\n\n \
17 # Preview file-to-name mapping without ingesting\n \
18 sqlite-graphrag ingest ./docs --dry-run\n\n \
19NOTES:\n \
20 Each file becomes a separate memory. Names derive from file basenames\n \
21 (kebab-case, lowercase, ASCII). Output is NDJSON: one JSON object per file,\n \
22 followed by a final summary line with counts. Per-file errors are reported\n \
23 inline and processing continues unless --fail-fast is set.")]
24pub struct IngestArgs {
26 #[arg(
28 value_name = "DIR",
29 help = "Directory to ingest recursively (each matching file becomes a memory)"
30 )]
31 pub dir: PathBuf,
32
33 #[arg(long, value_enum, default_value_t = MemoryType::Document)]
35 pub r#type: MemoryType,
36
37 #[arg(long, default_value = "*.md")]
40 pub pattern: String,
41
42 #[arg(long, default_value_t = false)]
44 pub recursive: bool,
45
46 #[arg(
47 long,
48 value_parser = crate::parsers::parse_bool_flexible,
49 action = clap::ArgAction::Set,
50 num_args = 0..=1,
51 default_missing_value = "true",
52 default_value = "false",
53 help = "Enable automatic URL-regex extraction (URL-regex only since v1.0.79)"
54 )]
55 pub enable_ner: bool,
57
58 #[arg(
62 long,
63 default_value_t = true,
64 overrides_with = "no_auto_describe",
65 help = "Derive memory description from the first meaningful body line instead of the legacy `ingested from <path>` placeholder."
66 )]
67 pub auto_describe: bool,
68 #[arg(
69 long = "no-auto-describe",
70 default_value_t = false,
71 help = "Disable `--auto-describe` and fall back to the legacy `ingested from <path>` description placeholder."
72 )]
73 pub no_auto_describe: bool,
75
76 #[arg(long, default_value_t = false, hide = true)]
78 pub skip_extraction: bool,
79
80 #[arg(long, default_value_t = false)]
82 pub fail_fast: bool,
83
84 #[arg(long, default_value_t = false)]
86 pub dry_run: bool,
87
88 #[arg(long, default_value_t = 10_000)]
90 pub max_files: usize,
91
92 #[arg(long)]
94 pub namespace: Option<String>,
95
96 #[arg(long)]
99 pub db: Option<String>,
100
101 #[arg(long, value_enum, default_value_t = JsonOutputFormat::Json)]
103 pub format: JsonOutputFormat,
104
105 #[arg(long, hide = true, help = "No-op; JSON is always emitted on stdout")]
107 pub json: bool,
108
109 #[arg(
111 long,
112 help = "Number of files to extract+embed in parallel; default = max(1, cpus/2).min(4)"
113 )]
114 pub ingest_parallelism: Option<usize>,
115
116 #[arg(
124 long,
125 default_value_t = false,
126 help = "Forces single-threaded ingest (--ingest-parallelism 1) to reduce RSS pressure. \
127 Recommended for environments with <4 GB available RAM or container/cgroup \
128 constraints. Trade-off: 3-4x longer wall time. Also honored via \
129 XDG ingest.low_memory=1."
130 )]
131 pub low_memory: bool,
132
133 #[arg(long, default_value_t = crate::constants::DEFAULT_MAX_RSS_MB,
135 help = "Maximum process RSS in MiB; abort if exceeded during embedding (default: 8192)")]
136 pub max_rss_mb: u64,
137
138 #[arg(long, default_value_t = 2, value_name = "N",
143 value_parser = clap::value_parser!(u64).range(1..=32),
144 help = "Maximum simultaneous LLM embedding subprocesses per file (default: 2, clamp [1,32])")]
145 pub llm_parallelism: u64,
146
147 #[arg(long, default_value_t = crate::constants::DERIVED_NAME_MAX_LEN,
152 help = "Maximum length for derived memory names (default: 60)")]
153 pub max_name_length: usize,
154
155 #[arg(
161 long,
162 value_name = "PREFIX",
163 help = "Kebab-case prefix applied to every derived memory name (e.g. 'projx-')"
164 )]
165 pub name_prefix: Option<String>,
166
167 #[arg(long, value_enum, default_value_t = IngestMode::None)]
169 pub mode: IngestMode,
170
171 #[arg(long)]
173 pub max_cost_usd: Option<f64>,
174
175 #[arg(long, value_name = "SECONDS")]
178 pub wait_job_singleton: Option<u64>,
179
180 #[arg(long, default_value_t = false)]
183 pub force_job_singleton: bool,
184
185 #[arg(
188 long,
189 default_value_t = false,
190 help = "Run enrich --operation memory-bindings after all files are ingested"
191 )]
192 pub enrich_after: bool,
193
194 #[arg(
199 long,
200 default_value_t = false,
201 help = "Update existing memories on name collision instead of skipping (idempotent re-ingest)"
202 )]
203 pub force_merge: bool,
204}
205
206#[derive(Clone, Debug, PartialEq, Eq, clap::ValueEnum)]
208pub enum IngestMode {
209 None,
211}
212
213pub(crate) fn low_memory_setting_enabled() -> bool {
222 match crate::config::get_setting("ingest.low_memory") {
223 Ok(Some(v)) if v.is_empty() => false,
224 Ok(Some(v)) => match v.to_lowercase().as_str() {
225 "1" | "true" | "yes" | "on" => true,
226 "0" | "false" | "no" | "off" => false,
227 other => {
228 tracing::warn!(
229 target: "ingest",
230 value = %other,
231 "ingest.low_memory value not recognized; treating as disabled"
232 );
233 false
234 }
235 },
236 _ => false,
237 }
238}
239
240pub(crate) fn resolve_parallelism(
252 low_memory_flag: bool,
253 ingest_parallelism: Option<usize>,
254) -> usize {
255 let setting_flag = low_memory_setting_enabled();
256 let low_memory = low_memory_flag || setting_flag;
257
258 if low_memory {
259 if let Some(n) = ingest_parallelism {
260 if n > 1 {
261 tracing::warn!(
262 target: "ingest",
263 requested = n,
264 "--ingest-parallelism overridden by --low-memory; using 1"
265 );
266 }
267 }
268 if low_memory_flag {
269 tracing::info!(
270 target: "ingest",
271 source = "flag",
272 "low-memory mode enabled: forcing --ingest-parallelism 1"
273 );
274 } else {
275 tracing::info!(
276 target: "ingest",
277 source = "xdg",
278 "low-memory mode enabled via XDG ingest.low_memory: forcing --ingest-parallelism 1"
279 );
280 }
281 return 1;
282 }
283
284 ingest_parallelism
285 .unwrap_or_else(|| {
286 std::thread::available_parallelism()
287 .map(|v| v.get() / 2)
288 .unwrap_or(1)
289 .clamp(1, 4)
290 })
291 .max(1)
292}