Skip to content

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.

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.

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.

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:

Terminal window
cargo add swiftide@0.34 --features openai,swiftide-agents

Feature flags lists what each flag turns on. If #[tool] is missing after default-features = false, add the macros flag back; see Defaults.

Each store needs its feature flag: qdrant, pgvector or redis.

  • Qdrant: the client reads QDRANT_URL and QDRANT_API_KEY, and falls back to http://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 example Redis::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.

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.

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 default stop tool does that. An agent has no loop limit by default, so if you remove that tool with without_default_stop_tool, register your own or set limit.
  • limit(10) caps the loop at 10 iterations. At the cap Swiftide logs the warning “Agent loop limit reached” and stops with StopReason::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_limit times (default 3). The next failure stops it with StopReason::ToolCallsOverLimit, and query returns 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.

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 ships agents, task graphs, and indexing and query pipelines in one crate family. See What is Swiftide.

Swiftide has no Python bindings. Wrap it in your own service to use it from Python.

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.

Swiftide is part of the bosun.ai project, and Bosun runs it in production.

Next: Glossary