Troubleshooting and FAQ
Each heading on this page names a symptom. Find yours, apply the fix, and your code runs again. Answers to frequently asked questions (FAQ) close the page.
Pipeline errors
Section titled “Pipeline errors”let stats = indexing::Pipeline::from_loader(FileLoader::new("docs").with_extensions(&["md"])) .then_chunk(ChunkMarkdown::from_chunk_range(100..2048)) .then_in_batch(Embed::new(openai)) .log_errors() .filter_errors() .then_store_with(qdrant) .run() .await?;println!("{} nodes stored", stats.nodes_stored);Source: snippets/src/reference/troubleshooting.rs#L18-L26
An indexing pipeline stops at the first node that fails, and run returns that error. When it finishes, run returns the pipeline statistics, such as nodes_stored.
log_errors logs each error at ERROR level and passes it on. filter_errors then drops the errors, so the other nodes still reach storage. Keep log_errors first: filter_errors on its own drops errors without a trace. Both only see errors from the steps before them, so place them after the steps you want to cover. A storage error still stops the pipeline: Qdrant rejects a node without vectors, so embed first.
log_all also logs every node at DEBUG level. filter keeps each Result its closure returns true for, so you can skip one kind of error. The logs need a tracing subscriber; see Tracing.
let result = indexing::Pipeline::from_loader(FileLoader::new("docs").with_extensions(&["md"])) .then_chunk(ChunkMarkdown::from_chunk_range(100..2048)) .then_in_batch(Embed::new(openai)) .then_store_with(qdrant) .run() .await;
if let Err(error) = result { match error.downcast_ref::<LanguageModelError>() { Some(LanguageModelError::ContextLengthExceeded(_)) => { eprintln!("a chunk is too large for the embedding model: {error:#}"); } Some(model_error) => eprintln!("the model failed: {model_error}"), None => return Err(error), }}Source: snippets/src/reference/troubleshooting.rs#L34-L49
The error a step fails with reaches run unchanged. A failed model call stays a LanguageModelError, so downcast_ref tells it apart from a storage or loader error, and chain() finds it when other code wrapped it in context. ContextLengthExceeded means a chunk is too large for the model; lower the chunker’s maximum size.
Crypto provider panic
Section titled “Crypto provider panic”No rustls crypto provider is configured. When using the `rustls-no-provider` feature you must install a crypto provider before building a Client.Swiftide’s HTTP clients need no TLS (Transport Layer Security) setup. Each one uses the process-wide rustls crypto provider if your application installed one, and otherwise its own. Redis over rediss:// needs a process-wide provider, and Swiftide installs aws-lc-rs before it connects if none is installed. TLS has the details.
On 0.33, Swiftide builds its clients without a provider, so building a client panics with this message, or with No provider set for the Anthropic client. Install a provider with rustls’s CryptoProvider::install_default at the start of main, or upgrade to 0.34.
Another crate in your application that builds its own rustls configuration also relies on the process-wide provider. When the build compiles in both ring and aws-lc-rs, for example with openai and qdrant, that crate panics until your application installs one.
Missing features
Section titled “Missing features”A use of swiftide::agents or swiftide::integrations::openai fails with error E0432, “unresolved import”, when the feature flag behind it is off. Agents, tasks and each integration have their own feature flag:
cargo add swiftide@0.34 --features openai,swiftide-agentsFeature flags lists what each flag turns on. If #[tool] is missing after default-features = false, add the macros flag back; see Defaults.
Unreachable stores
Section titled “Unreachable stores”Each store needs its feature flag: qdrant, pgvector or redis.
-
Qdrant: the client reads
QDRANT_URLandQDRANT_API_KEY, and falls back tohttp://localhost:6334. That is Qdrant’s gRPC (gRPC Remote Procedure Calls) port, not the 6333 HTTP port. Publish both ports:Terminal window docker run -p 6333:6333 -p 6334:6334 qdrant/qdrant -
PgVector: setup runs
CREATE EXTENSION IF NOT EXISTS vector. The server needs pgvector installed, and your database user needs permission to create the extension. The connection pool tries 3 times, 3 seconds apart, before it gives up. -
Redis: pass the full URL and a key prefix to
Redis::try_from_url, for exampleRedis::try_from_url("redis://localhost:6379", "swiftide"). This call does not connect, so a Redis that is down fails at the first store.
Qdrant and PgVector set up before the first node, so if either is down, run fails before any node is processed.
Rejected tool schemas
Section titled “Rejected tool schemas”ToolSpecBuilder::build rejects schemas that break the strict rules providers share: maps such as HashMap<String, T>, open objects and patternProperties. The #[tool] macro unwraps that result, so a bad argument type panics when tool_spec is called.
The OpenAI client also rejects oneOf and array-valued type, with “tool schema must be OpenAI compatible”. Enums whose variants carry data or doc comments produce oneOf.
Model arguments as structs with named fields and unit-only enums. Option<T> is fine. See Tools.
Agent loop limits
Section titled “Agent loop limits”Agents need the swiftide-agents feature flag.
let mut agent = Agent::builder() .llm(&openai) .on_stream(|_agent, response| { if let Some(chunk) = response .delta .as_ref() .and_then(|d| d.message_chunk.as_deref()) { print!("{chunk}"); } Box::pin(async move { Ok(()) }) }) .limit(10) .build()?;
agent.query("Explain Rust lifetimes").await?;
if let Some(reason) = agent.stop_reason() { println!("stopped: {reason:?}");}Source: snippets/src/reference/troubleshooting.rs#L57-L76
With the default context, an agent stops when the model answers without calling a tool. Three settings change when it stops:
with_stop_on_assistant(false)on the context keeps the loop going until a tool stops it. The defaultstoptool does that. An agent has no loop limit by default, so if you remove that tool withwithout_default_stop_tool, register your own or setlimit.limit(10)caps the loop at 10 iterations. At the cap Swiftide logs the warning “Agent loop limit reached” and stops withStopReason::NoNewMessages. A normal finish has the same stop reason, so watch for the warning.- When the same tool call keeps failing, the agent sends the error back to the model up to
tool_retry_limittimes (default 3). The next failure stops it withStopReason::ToolCallsOverLimit, andqueryreturns the error.
stop_reason separates a tool stop and ToolCallsOverLimit from the other cases. It does not separate the loop limit from a normal finish. See Stopping and structured results.
Silent streaming
Section titled “Silent streaming”An agent prints nothing until the answer is complete. Register on_stream, as in the agent snippet on this page. The hook switches the agent to streaming and runs for every chunk. streaming(true) on the builder streams without a hook.
In the hook, delta.message_chunk holds the new text. The OpenAI client also puts the full message so far in response.message on each chunk, because stream_full defaults to true. The final chunk carries no new text. Before 0.34 it repeated the last text fragment, so the snippet printed it twice. See Streaming.
SimilaritySingleEmbedding type annotations
Section titled “SimilaritySingleEmbedding type annotations”let pipeline = query::Pipeline::from_search_strategy(SimilaritySingleEmbedding::<()>::default()) .then_transform_query(query::query_transformers::Embed::from_client( openai.clone(), )) .then_retrieve(pgvector) .then_answer(answers::Simple::from_client(openai));Source: snippets/src/reference/troubleshooting.rs#L84-L90
SimilaritySingleEmbedding::default() exists only for the strategy without a filter, so its filter type is inferred. Build a filtered strategy with from_filter or with_filter. On 0.33, default() fails with error E0283, “type annotations needed”; name the filter type, as in the snippet.
HybridSearch::default() still needs the annotation on stores that implement the retriever for more than one filter type. The search strategy is generic over its filter type, and Rust does not use the default type parameter to pick an implementation.
Swiftide and Rig
Section titled “Swiftide and Rig”Swiftide ships agents, task graphs, and indexing and query pipelines in one crate family. See What is Swiftide.
Python
Section titled “Python”Swiftide has no Python bindings. Wrap it in your own service to use it from Python.
Versioning
Section titled “Versioning”Swiftide is pre-1.0. Breaking changes land in minor releases, such as 0.32 to 0.33. Read Upgrading and the Changelog before you move to a new release.
Production use
Section titled “Production use”Swiftide is part of the bosun.ai project, and Bosun runs it in production.
Reference
Section titled “Reference”indexing::PipelineAgentBuilderStopReasonDefaultContextSimilaritySingleEmbeddingLanguageModelErrorToolSpecToolSpecBuilderQdrantPgVectorRedisOpenAI
Next: Glossary