> ## Documentation Index
> Fetch the complete documentation index at: https://docs.seekr.com/llms.txt
> Use this file to discover all available pages before exploring further.

# New hire onboarding agent with citations

> Build a FileSearch agent that answers onboarding questions from your own documents, with confidence ratings and cited sources.

export const DownloadNotebook = ({href, filename}) => <div className="not-prose" style={{
  marginTop: "4px",
  marginBottom: "16px"
}}>
    <a href={href} download={filename} className="inline-flex items-center gap-2 border border-[#00dad3] bg-[#00dad3]/10 text-[#007774] dark:text-[#00dad3] no-underline" style={{
  padding: "6px 14px",
  borderRadius: "8px",
  fontSize: "14px",
  fontWeight: "600"
}}>
      <Icon icon="download" size={16} />
      Download Jupyter notebook
    </a>
  </div>;

export const RecipeMeta = ({tags = [], level, time, version}) => {
  const pill = {
    display: "inline-block",
    padding: "2px 10px",
    borderRadius: "6px",
    fontSize: "12px",
    fontWeight: "600",
    marginRight: "6px",
    marginBottom: "6px"
  };
  const hasMeta = level || time || version;
  return <div className="not-prose" style={{
    marginTop: "4px",
    marginBottom: "12px"
  }}>
      {tags.length > 0 && <div style={{
    marginBottom: hasMeta ? "8px" : "0"
  }}>
          {tags.map(t => <span key={t} className="border border-[#00dad3] bg-[#00dad3]/10 text-[#007774] dark:text-[#00dad3]" style={pill}>
              {t}
            </span>)}
        </div>}
      {hasMeta && <div className="flex flex-wrap items-center gap-x-4 gap-y-1 text-sm text-black/60 dark:text-white/60">
          {level && <span className="inline-flex items-center gap-1.5">
              <Icon icon="gauge" size={14} />
              {level}
            </span>}
          {time && <span className="inline-flex items-center gap-1.5">
              <Icon icon="clock" size={14} />
              {time}
            </span>}
          {version && <span className="inline-flex items-center gap-1.5">
              <Icon icon="cube" size={14} />
              Verified with seekrai {version}
            </span>}
        </div>}
    </div>;
};

<RecipeMeta tags={["Agents", "FileSearch", "Citations"]} level="Intermediate" time="~20 min" version="0.29.0" />

<DownloadNotebook href="/notebooks/new-hire-onboarding-agent-with-citations.json" filename="new-hire-onboarding-agent-with-citations.ipynb" />

This recipe builds a document question-answering agent with SeekrFlow's Agent
framework and the FileSearch tool. You index a set of documents into a vector
database, attach that database to an agent through a FileSearch tool, and prompt
the agent to answer only from what it retrieves, rate its confidence, and cite
its sources.

The example uses a new hire onboarding scenario, but the pattern works for any
document set. Gather 3 to 5 high-quality documents relevant to your use case
before you start.

## What you'll build

An agent that:

1. Indexes your documents into a vector database.
2. Searches across those documents to answer questions.
3. Rates its confidence and explains the rating.
4. Cites the specific sources behind each answer.

## Prerequisites

* A SeekrFlow API key, set as the `SEEKR_API_KEY` environment variable
* Documents in PDF, DOCX, or Markdown format
* Python 3.8 or later
* The SeekrFlow SDK: `pip install seekrai`

<Warning>
  This recipe creates billable resources. When you are done, remove them with the
  [cleanup step](#clean-up).
</Warning>

## Build it

<Steps>
  <Step title="Set up the client">
    Create `onboarding_agent.py` and start with the imports, configuration, and
    client. The paths in `FILE_PATHS` should point at your own documents.

    <CodeGroup>
      ```python onboarding_agent.py theme={null}
      import os
      import re
      import time

      from seekrai import SeekrFlow
      from seekrai.types import CreateAgentRequest, FileSearch, FileSearchEnv

      API_KEY = os.environ["SEEKR_API_KEY"]
      API_URL = "https://flow.seekr.com/v1/"
      MODEL_ID = "meta-llama/Llama-3.1-8B-Instruct"
      EMBEDDING_MODEL = "intfloat/e5-mistral-7b-instruct"

      FILE_PATHS = [
          "company-guidebook.pdf",
          "company-holidays.pdf",
          "company-payroll-schedule.pdf",
      ]

      client = SeekrFlow(api_key=API_KEY, base_url=API_URL)
      ```
    </CodeGroup>
  </Step>

  <Step title="Create a vector database">
    Create an empty vector database. Documents you ingest are embedded with the
    model you name here and stored for retrieval.

    <CodeGroup>
      ```python onboarding_agent.py theme={null}
      vector_db = client.vector_database.create(
          name="BennyBotDB",
          model=EMBEDDING_MODEL,
          description="Employee guidelines and benefits for onboarding QA.",
      )
      database_id = vector_db.id
      print(f"Created vector database: {vector_db.name} ({database_id})")
      ```
    </CodeGroup>
  </Step>

  <Step title="Upload your documents">
    Upload each document to SeekrFlow's AI-Ready Data Engine with `purpose="alignment"`
    and collect the file IDs.

    <CodeGroup>
      ```python onboarding_agent.py theme={null}
      file_ids = []
      for file_path in FILE_PATHS:
          print(f"Uploading {file_path}...")
          upload_response = client.files.upload(file_path, purpose="alignment")
          file_ids.append(upload_response.id)
          print(f"  Uploaded: {upload_response.id}")
      ```
    </CodeGroup>
  </Step>

  <Step title="Ingest the documents">
    Start an ingestion job to chunk, embed, and store the uploaded files, then poll
    until it completes. Accuracy-optimized ingestion can take a few minutes.

    <CodeGroup>
      ```python onboarding_agent.py theme={null}
      ingestion_job = client.vector_database.create_ingestion_job(
          database_id=database_id,
          files=file_ids,
          method="accuracy-optimized",
          chunking_method="markdown",
          token_count=512,
          overlap_tokens=50,
      )
      job_id = ingestion_job.id
      print(f"Created ingestion job: {job_id}")

      deadline = time.time() + 600
      while time.time() < deadline:
          job = client.vector_database.retrieve_ingestion_job(database_id, job_id)
          print(f"Ingestion status: {job.status}")
          if job.status == "completed":
              print("Vector database ready.")
              break
          if job.status == "failed":
              raise RuntimeError(f"Ingestion failed: {job.error_message}")
          time.sleep(5)
      else:
          raise TimeoutError("Ingestion did not complete in time.")
      ```
    </CodeGroup>
  </Step>

  <Step title="Create the agent">
    Create an agent and attach a FileSearch tool pointed at your vector database. The
    instructions tell it to answer only from search results, rate its confidence,
    and cite sources. Then poll until the agent is `Active`.

    <CodeGroup>
      ```python onboarding_agent.py theme={null}
      instructions = """You are an expert onboarding assistant that answers only from document search results.

      For each question:
      1. Search the documents with the file_search tool.
      2. Use information from multiple sources when available.
      3. When you find relevant information, rate your confidence from 1 (guess) to 10 (certain).
      4. After your answer, on a new line starting with "Confidence: [X/10]", briefly explain the rating.
      5. Cite your sources, including specific document names.

      If the answer is not in the search results, say you could not find it and do not give a confidence score."""

      agent = client.agents.create(
          CreateAgentRequest(
              name="BennyBot",
              instructions=instructions,
              model_id=MODEL_ID,
              tools=[
                  FileSearch(
                      tool_env=FileSearchEnv(
                          file_search_index=database_id,
                          document_tool_desc="Search onboarding documents to answer employee questions accurately.",
                          top_k=6,
                          score_threshold=0.5,
                      )
                  )
              ],
          )
      )
      agent_id = agent.id
      print(f"Agent created: {agent_id}")

      deadline = time.time() + 300
      while time.time() < deadline:
          status = client.agents.retrieve(agent_id=agent_id).status.value
          print(f"Agent status: {status}")
          if status == "Active":
              print("Agent is active.")
              break
          if status == "Failed":
              raise RuntimeError("Agent failed to deploy.")
          time.sleep(10)
      else:
          raise TimeoutError("Agent did not become active in time.")
      ```
    </CodeGroup>
  </Step>

  <Step title="Ask a question">
    Create a thread, send a question, wait for the run to finish, and read the
    agent's reply. The `parse_response` helper splits the answer, the confidence
    rating, and any cited sources out of the reply.

    <CodeGroup>
      ```python onboarding_agent.py theme={null}
      def parse_response(response: str) -> dict:
          """Split an agent reply into answer, confidence, explanation, and sources."""
          if not response:
              return {"answer": "No response received.", "confidence": "0/10", "explanation": "", "sources": []}

          answer, confidence, explanation, sources = response, "Not provided", "", []

          if "Confidence:" in response:
              answer, _, confidence_part = response.partition("Confidence:")
              answer = answer.strip()
              confidence_part = confidence_part.strip()
              rating_match = re.search(r"(\d+)(/10)?", confidence_part)
              if rating_match:
                  confidence = f"{rating_match.group(1)}/10"
                  explanation = re.sub(r"^\d+(/10)?", "", confidence_part).strip()

          for pattern in (r"Source[s]?:(.+?)(?=\n\n|\Z)", r"According to (.+?)(?=\n\n|\Z)"):
              match = re.search(pattern, response, re.IGNORECASE | re.DOTALL)
              if match:
                  sources += [s.strip() for s in re.split(r",|\n", match.group(1)) if s.strip()]

          return {"answer": answer, "confidence": confidence, "explanation": explanation, "sources": sources}


      thread = client.agents.threads.create()
      client.agents.threads.create_message(
          thread_id=thread.id, role="user", content="How many paid holidays do employees get?"
      )
      run_response = client.agents.runs.run(agent_id=agent_id, thread_id=thread.id, stream=False)

      deadline = time.time() + 300
      while time.time() < deadline:
          run = client.agents.runs.retrieve(run_response.run_id, thread.id)
          if run.status.value == "completed":
              break
          if run.status.value in ("failed", "canceled"):
              raise RuntimeError(f"Run ended with status: {run.status.value}")
          time.sleep(2)
      else:
          raise TimeoutError("Run did not complete in time.")

      messages = client.agents.threads.list_messages(thread.id, limit=10, order="desc")
      reply = next((m.content for m in messages if m.role == "assistant"), None)

      result = parse_response(reply if isinstance(reply, str) else str(reply))
      print("\n" + "=" * 50)
      print(f"Answer: {result['answer']}\n")
      print(f"Confidence: {result['confidence']}")
      if result["explanation"]:
          print(f"Explanation: {result['explanation']}")
      if result["sources"]:
          print("\nSources:")
          for i, source in enumerate(result["sources"], 1):
              print(f"{i}. {source}")
      ```
    </CodeGroup>
  </Step>

  <Step title="Run the script">
    Run the finished script:

    ```bash theme={null}
    python onboarding_agent.py
    ```

    The agent searches your documents, answers from what it retrieves, and appends a
    confidence rating and its sources.
  </Step>
</Steps>

<h2 id="clean-up">
  Clean up resources (optional)
</h2>

Remove the resources this recipe created when you are done.

<CodeGroup>
  ```python onboarding_agent.py theme={null}
  client.agents.demote(agent_id)
  client.agents.delete(agent_id)
  client.vector_database.delete(database_id)
  for file_id in file_ids:
      client.files.delete(file_id)
  print("Deleted the agent, vector database, and files.")
  ```
</CodeGroup>

## Next steps

* **Tune retrieval.** Adjust `top_k` and `score_threshold` on the FileSearch tool
  to trade recall against precision for your document set.
* **Swap in your own documents.** Point `FILE_PATHS` at any PDF, DOCX, or Markdown
  files to build an assistant for a different domain.
* **Add metadata.** Attach metadata at ingestion so you can filter retrieval by
  fields like document type or date.
