# `Perplexed.Pipeline`
[🔗](https://github.com/agoodway/perplexed/blob/v0.2.0/lib/perplexed/pipeline.ex#L1)

One-shot web answering, in fixed steps:

  1. **Plan** - one model call turns the question into a few search
     queries (skipped when the caller passes `:queries`).
  2. **Search** - every query runs concurrently; hits are de-duplicated by
     URL.
  3. **Rank** - one model call scores each hit's relevance from 0 to 1,
     seeing only its title, URL and description. Hits at or above
     `:min_relevance` are kept, best first.
  4. **Scrape** - the best `:max_sources` kept hits are scraped
     concurrently; an unreadable page is replaced by the next kept hit, and
     a page another source already covers is skipped.
  5. **Summarize** - one model call writes the answer from the numbered
     sources, as text with `[n]` citations or as an object matching
     `:schema`.

The result is a `Perplexed.Answer` carrying the answer, the sources it
was written from, and every hit considered with why it was used or not.

## Untrusted content

Search results and page text are written by third parties. They reach the
model inside `<result>` and `<source>` tags, with a standing instruction to
treat that text as data, never as instructions. That lowers, but does not
remove, the risk of a page steering its own rank or the answer; treat
answers, and especially structured answers you act on, accordingly.

# `t`

```elixir
@type t() :: %Perplexed.Pipeline{
  answer: term(),
  candidates: term(),
  cited: term(),
  explicit_queries: term(),
  generate_object: term(),
  generate_text: term(),
  hits_per_query: term(),
  instructions: term(),
  max_queries: term(),
  max_sources: term(),
  min_relevance: term(),
  model: term(),
  model_options: term(),
  object_options: term(),
  page_chars: term(),
  provider: term(),
  queries: term(),
  query: term(),
  schema: term(),
  scrape_options: term(),
  search_options: term(),
  sources: term(),
  task_supervisor: term(),
  trace: term(),
  use_search_content: term(),
  validation_retries: term()
}
```

One run's resolved options and accumulated state.

# `run`

```elixir
@spec run(String.t(), keyword()) ::
  {:ok, Perplexed.Answer.t()} | {:error, Perplexed.error()}
```

Answers `query` from the web.

## Options

  * `:model` - a ReqLLM model spec, default `config :perplexed, model:`.
    Any provider ReqLLM supports works.
  * `:model_options` - ReqLLM options for every model call, overriding the
    built-in `max_tokens: 4000` and `receive_timeout:`;
    `:object_options` - extra options for structured-output calls.
  * `:schema` - a Zoi schema; the answer becomes a validated object.
  * `:instructions` - system instructions for the summary call, replacing
    the built-in ones (the untrusted-content note is always kept).
  * `:queries` - search queries to run instead of planning them.
  * `:max_queries` - planned queries, default 3.
  * `:provider`, `:search_options` - the search provider spec and its
    options; `:hits_per_query` defaults to 8.
  * `:max_sources` - pages to read and summarize, default 5.
  * `:min_relevance` - lowest ranking score kept, default 0.5.
  * `:scrape_options` - options for `Perplexed.Scrape.batch/2`, such as
    `:ladder`.
  * `:page_chars` - characters of each page given to the summary, default
    12000.
  * `:use_search_content` - read the page text a search provider returned
    with a hit (DeepSeek does) instead of scraping it, when that text
    passes `Perplexed.Verdict`; default `true`. Such a source's tier is
    `:search`.
  * `:validation_retries` - schema-mismatch retries, default
    1.
  * `:task_supervisor` - default `Perplexed.TaskSupervisor`.
  * `:generate_object`, `:generate_text` - stand-ins for
    `ReqLLM.generate_object/4` and `ReqLLM.generate_text/3`, for tests.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
