# Tutorial: Building a Deal Desk Agent with Search and Custom Code

Build an agent that helps a sales team decide whether a discount on a B2B deal can be approved. The agent uses two tools: a document search pipeline that finds the right pricing policy excerpts, and a custom code tool that applies your discount rules with exact thresholds.

***

- **Level**: Intermediate
- **Time to complete**: 20 minutes
- **Prerequisites**:
  - A workspace where you'll build the agent.
  - An LLM connection configured in your workspace. To learn how to set up an LLM connection, see [Add Integrations](/docs/how-to-guides/managing-access/add-integrations.mdx).
  - Basic knowledge of [Pipelines](/docs/concepts/about-pipelines/about-pipelines.mdx) and the [Agent](/docs/reference/pipeline-components/ai/Agent.mdx) component.
- **Goal**: After completing this tutorial, you will have a working agent that searches deal desk policy documents and checks discount approval with deterministic business rules.

***

## What You're Building

An agent that supports sales teams in deciding whether they can grant a discount on a deal based on the company's pricing policy. Account executives ask the deal desk questions like:

> "A healthcare prospect wants 28% off a $95,000 annual contract. Can I approve this myself?"

To answer correctly, the agent needs to know two things:

1. Policy context: What do the company's pricing guidelines say about healthcare caps, enterprise thresholds, and escalation?
2. Who makes the final decision: Is this discount within rep authority, or does it need a manager or VP?

An LLM can summarize policy text, but it should not guess discount limits or approval tiers. That is what custom code is for.

The agent will have two tools:

1. `search_deal_policy` — A document search pipeline that retrieves relevant deal desk and pricing policy snippets. It works on your own files.
2. `check_discount_approval` — A custom Python function that takes deal facts (ACV, discount percent, contract term, vertical) and returns a structured approval result. The function is configured with fixed thresholds (rep limit, manager limit, VP limit) that you set once in the tool card. The agent extracts the deal facts from the sales rep's message and passes them to the tool at runtime.

Search finds the policy language to cite. Code applies the rules the same way every time. The agent combines both into a clear answer for the sales rep.

***

## Upload Sample Files

Upload a small deal desk policy corpus. You can use your own documents, or use the sample files prepared for this tutorial.

1. Download the four files from the [`GDrive folder`](https://drive.google.com/drive/folders/1wer4p3YghXxkFyJK59VwFtqOrtEo9wZ5?usp=sharing).
2. In <ProductName />, switch to the workspace where you want to build the agent and go to *Files > Upload Files*.
3. Choose the files you downloaded and click **Upload**. Wait until the files are uploaded to the workspace.

**Result**: Your workspace contains four Atlas Analytics policy documents:

| File | What it represents |
| --- | --- |
| `deal-desk-discount-policy.md` | Rep, manager, and VP discount authority |
| `enterprise-pricing-guidelines.md` | List price bands and packaging tiers |
| `healthcare-vertical-rules.md` | Extra caps and approvals for healthcare deals |
| `approval-escalation-matrix.md` | Who must approve which discount scenarios |

## Create an Index

Index the files so the agent can search them.

1. Go to *Indexes > Create Index*.
2. Choose **Standard Index (English)** and click **Create Index**.
3. Leave the default index configuration and click **Enable**. Wait until indexing finishes.

**Result**: Your files are indexed and ready for search. The *Standard Index (English)* index has status *indexed*.

<ClickableImage src="/img/tutorials/agent-custom-code-index.png" alt="An index with status indexed on the Indexes page" />

## Create the Agent

1. In <ProductName />, open the workspace where your index lives and go to **Pipelines > Create Pipeline**.
2. On the Templates page, click **Create empty pipeline**.
3. Enter *deal-desk-agent* as the pipeline name and click **Create Pipeline**.
4. In Builder, click **Add** to open the component library and add the following components:

      - `Input`
      - `Agent`
      - `Output`

5. Connect `Input` to the `Agent` and `Agent` to `Output`.
6. Click **Model** on the `Agent` component and choose the model you want to use for the agent.

<ClickableImage src="/img/tutorials/tutorial-minimal-agent.gif" alt="An agent with a model selected" />

**Result**: You have created a minimal agent. Next you will add the two tools and the system prompt.

## Create the Deal Policy Search Pipeline

Create the first tool: a document search pipeline over your deal desk policies.

1. Leave Builder and go to **Pipeline Templates**.
2. Choose **Document Search** as the category, find **Semantic Document Search**, and click **Use Template**.
3. Enter *deal-policy-search* as the pipeline name and click **Create Pipeline**.
4. Click the `OpenSearchDocumentStore` component to open its configuration and in the Index field,choose the *Standard Index (English)* index.
5. Test the pipeline: click **Run Pipeline** next to the `Input` component and type `healthcare discount cap`. You should see retrieved policy snippets.
6. Click **Deploy** to make the pipeline available to the agent as a tool. Confirm you're deploying the latest version and click **Deploy**.

<details>
<summary>Pipeline YAML</summary>

This is the YAML configuration for the document search pipeline:

```yaml
components:
  query_embedder:
    type: deepset_cloud_custom_nodes.embedders.nvidia.text_embedder.DeepsetNvidiaTextEmbedder
    init_parameters:
      normalize_embeddings: true
      model: intfloat/e5-base-v2

  embedding_retriever:
    # Selects the most similar documents from the document store
    type: haystack_integrations.components.retrievers.opensearch.embedding_retriever.OpenSearchEmbeddingRetriever
    init_parameters:
      document_store:
        type: haystack_integrations.document_stores.opensearch.document_store.OpenSearchDocumentStore
        init_parameters:
          embedding_dim: 768
          index: Standard-Index-English
      top_k: 20 # The number of results to return

  ranker:
    type: deepset_cloud_custom_nodes.rankers.nvidia.ranker.DeepsetNvidiaRanker
    init_parameters:
      model: tomaarsen/Qwen3-Reranker-0.6B-seq-cls
      top_k: 20

connections:
- sender: query_embedder.embedding
  receiver: embedding_retriever.query_embedding
- sender: embedding_retriever.documents
  receiver: ranker.documents

inputs:
  query:
  - query_embedder.text
  - ranker.query
  filters:
  - embedding_retriever.filters

outputs:
  documents: ranker.documents
```

</details>

**Result**: You have a document search pipeline that returns relevant policy snippets and is ready to be used as a tool.

## Add the Deal Policy Search Tool to the Agent

Attach the search pipeline as a tool on your agent:

1. Leave Builder and on the Pipelines page click the *deal-desk-agent* pipeline to open it.
2. Click the `Agent` component to open its configuration.
3. In the *Tools* section, click **Add Tool**.
4. Choose *Pipeline* as the tool type.
5. On the Create Pipeline Tool window, choose *Existing pipeline* and select *deal-policy-search*.
6. Leave *Current draft* as the pipeline version.
7. Enter `search_deal_policy` as the tool name.
8. Copy the following text and paste it as the tool description:
   ```text
   Search Atlas Analytics deal desk policies for discount authority, pricing tiers,
   vertical rules, and approval escalation. Use when you need policy language to
   cite in a discount approval answer.
   ```
9. Click **Add Pipeline Tool**.

**Result**: The agent has a pipeline tool that can search deal desk policies.

:::info
When you add an existing pipeline as a tool, the platform handles input and output mapping for you. For details, see [Add a Pipeline as a Tool](/docs/how-to-guides/building-agents/configuring-agent.mdx#add-a-pipeline-as-a-tool).
:::

## Add the Discount Approval Code Tool

The second tool encodes the company's discount approval rules in Python. The code uses a class with `__init__` parameters for the fixed thresholds and a `@tool` function the agent calls with per-deal facts from the conversation.

:::tip Init parameters vs tool parameters
`__init__` parameters are configured once in the tool card UI — they're the business rules that rarely change (discount caps, ACV thresholds). `@tool` function arguments are what the agent passes at runtime from the sales rep's message (contract value, discount percent, term, vertical). To create a second pipeline with different thresholds for a different market, you clone it and change only the init parameters.
:::

1. On the `Agent` componentconfiguration, in the *Tools* section, click **Add Tool**.
2. Choose **Code** as the tool type.
3. Type `check_discount_approval` as the tool name.
4. Copy the following text and paste it as the tool description:
   ```text
   Check whether a requested discount needs rep, manager, or VP approval given ACV, discount percent, contract term, and vertical.
   ```
   :::note
   Keep the description on a single line. Some LLM providers, including Anthropic, reject tool descriptions that contain line breaks.
   :::
5. Click **Create Tool**.
6. In the code editor, paste the following code replacing the example code:

  ```python
from typing import Annotated

from haystack.tools import tool

class DiscountApprovalEngine:
    """Discount rules configured at deploy time through init parameters."""

    def __init__(
        self,
        max_rep_discount_percent: float = 15,
        manager_max_discount_percent: float = 25,
        vp_approval_acv_threshold_usd: float = 75000,
        healthcare_max_without_vp: float = 20,
        multi_year_bonus_percent: float = 3,
    ):
        self.max_rep_discount_percent = max_rep_discount_percent
        self.manager_max_discount_percent = manager_max_discount_percent
        self.vp_approval_acv_threshold_usd = vp_approval_acv_threshold_usd
        self.healthcare_max_without_vp = healthcare_max_without_vp
        self.multi_year_bonus_percent = multi_year_bonus_percent

    def check(
        self,
        annual_contract_value_usd: float,
        requested_discount_percent: float,
        contract_term_years: int,
        industry_vertical: str,
    ) -> dict:
        vertical = industry_vertical.strip().lower()
        healthcare_verticals = {"healthcare", "life_sciences", "life sciences", "pharma"}

        rep_limit = self.max_rep_discount_percent
        if contract_term_years >= 2:
            rep_limit += self.multi_year_bonus_percent

        if vertical in healthcare_verticals and requested_discount_percent > self.healthcare_max_without_vp:
            return {
                "rep_can_approve": False,
                "approval_required": "vp_sales",
                "rep_discount_limit_percent": rep_limit,
                "reason": (
                    f"Healthcare vertical discounts above {self.healthcare_max_without_vp}% "
                    "require VP Sales approval."
                ),
            }

        if (
            annual_contract_value_usd > self.vp_approval_acv_threshold_usd
            and requested_discount_percent > self.max_rep_discount_percent
        ):
            return {
                "rep_can_approve": False,
                "approval_required": "vp_sales",
                "rep_discount_limit_percent": rep_limit,
                "reason": (
                    f"Deals over ${self.vp_approval_acv_threshold_usd:,.0f} with a discount "
                    f"above {self.max_rep_discount_percent}% require VP Sales approval."
                ),
            }

        if requested_discount_percent <= rep_limit:
            return {
                "rep_can_approve": True,
                "approval_required": "none",
                "rep_discount_limit_percent": rep_limit,
                "reason": (
                    f"Requested discount is within the rep limit of {rep_limit}% "
                    f"({'includes multi-year bonus' if contract_term_years >= 2 else 'standard authority'})."
                ),
            }

        if requested_discount_percent <= self.manager_max_discount_percent:
            return {
                "rep_can_approve": False,
                "approval_required": "sales_manager",
                "rep_discount_limit_percent": rep_limit,
                "reason": (
                    f"Discount exceeds rep limit of {rep_limit}% but is within the "
                    f"{self.manager_max_discount_percent}% manager threshold."
                ),
            }

        return {
            "rep_can_approve": False,
            "approval_required": "vp_sales",
            "rep_discount_limit_percent": rep_limit,
            "reason": (
                f"Discount above {self.manager_max_discount_percent}% requires VP Sales approval."
            ),
        }

_engine = DiscountApprovalEngine()

@tool
def check_discount_approval(
    annual_contract_value_usd: Annotated[float, "Annual contract value in USD"],
    requested_discount_percent: Annotated[float, "Requested discount as a percentage, for example 28 for 28%"],
    contract_term_years: Annotated[int, "Contract length in years"],
    industry_vertical: Annotated[
        str,
        "Industry vertical, for example healthcare, financial_services, or standard",
    ],
):
    """Check discount approval level using Atlas Analytics deal desk rules."""
    acv = float(str(annual_contract_value_usd).replace(",", ""))
    discount = float(str(requested_discount_percent).replace("%", "").strip())
    term = int(str(contract_term_years).strip())
    return _engine.check(
        annual_contract_value_usd=acv,
        requested_discount_percent=discount,
        contract_term_years=term,
        industry_vertical=industry_vertical,
    )
```

7. Close the code editor. 

**Result**: The agent has a code tool that returns a structured approval result for any deal the sales rep describes.

## Set the System Prompt

Give the agent instructions so it knows when to use each tool.

1. Click the **Agent** component to open its configuration.
2. Copy the following text and paste it as the system prompt:

  ```text
You are an Atlas Analytics deal desk assistant helping sales reps evaluate discount requests.

You have access to two tools:

1. search_deal_policy: Search deal desk policies for discount authority, pricing tiers, vertical rules, and approval escalation.

2. check_discount_approval: Check whether a discount can be approved at rep level or needs escalation. Pass annual_contract_value_usd, requested_discount_percent, contract_term_years, and industry_vertical.

Workflow:
- Extract deal facts from the user's message (ACV, discount, contract term, vertical).
- If any fact is missing, ask one short follow-up question.
- Call check_discount_approval with the extracted facts.
- Call search_deal_policy to find policy excerpts that support your answer.
- Combine the approval result with policy citations in your reply.

Response format:
- Can the rep approve: yes or no
- Approval required: none, sales_manager, or vp_sales
- Rep discount limit
- Policy references from search_deal_policy
- Next steps for the sales rep
```

<details>
<summary>Full AgentYAML Configuration</summary>

This is the YAML configuration for the agent with tools:

```yaml
inputs:
  query: []
  filters: []
  files: []
  messages:
  - Agent.messages

components:
  Agent:
    type: haystack.components.agents.agent.Agent
    init_parameters:
      chat_generator:
        type: haystack_integrations.components.generators.anthropic.chat.chat_generator.AnthropicChatGenerator
        init_parameters:
          model: claude-sonnet-4-6
          ignore_tools_thinking_messages: true
          api_key:
            type: env_var
            strict: true
            env_vars:
            - ANTHROPIC_API_KEY
      tools:
      - type: haystack.tools.pipeline_tool.PipelineTool
        data:
          name: search_deal_policy
          description: >-
            Search Atlas Analytics deal desk policies for discount authority,
            pricing tiers,
               vertical rules, and approval escalation. Use when you need policy language to
               cite in a discount approval answer.
          input_mapping:
            query:
            - query_embedder.text
            - ranker.query
            filters:
            - embedding_retriever.filters
          output_mapping:
            ranker.documents: documents
          pipeline:
            components:
              query_embedder:
                type: deepset_cloud_custom_nodes.embedders.nvidia.text_embedder.DeepsetNvidiaTextEmbedder
                init_parameters:
                  normalize_embeddings: true
                  model: intfloat/e5-base-v2
              embedding_retriever:
                type: haystack_integrations.components.retrievers.opensearch.embedding_retriever.OpenSearchEmbeddingRetriever
                init_parameters:
                  document_store:
                    type: haystack_integrations.document_stores.opensearch.document_store.OpenSearchDocumentStore
                    init_parameters:
                      embedding_dim: 768
                      index: Standard-Index-English
                  top_k: 20
              ranker:
                type: deepset_cloud_custom_nodes.rankers.nvidia.ranker.DeepsetNvidiaRanker
                init_parameters:
                  model: tomaarsen/Qwen3-Reranker-0.6B-seq-cls
                  top_k: 20
            connections:
            - sender: query_embedder.embedding
              receiver: embedding_retriever.query_embedding
            - sender: embedding_retriever.documents
              receiver: ranker.documents
          is_pipeline_async: false
          inputs_from_state: {}
          outputs_to_string: {}
          outputs_to_state: {}
        _meta:
          name: search_deal_policy
          description: >-
            Search Atlas Analytics deal desk policies for discount authority,
            pricing tiers,
               vertical rules, and approval escalation. Use when you need policy language to
               cite in a discount approval answer.
          tool_id:
          pipeline_version_id: c9bdb695-fa2e-47c7-a25a-3bccc9701817
      - type: deepset_cloud_custom_nodes.tools.code_tool.CodeTool
        data:
          name: check_discount_approval
          description: Check whether a requested discount needs rep, manager, or VP
            approval given ACV, discount percent, contract term, and vertical.
          code: "from typing import Annotated\r

            \r

            from haystack.tools import tool\r

            \r

            \r

            class DiscountApprovalEngine:\r

            \    \"\"\"Discount rules configured at deploy time through init
            parameters.\"\"\"\r

            \r

            \    def __init__(\r

            \        self,\r

            \        max_rep_discount_percent: float = 15,\r

            \        manager_max_discount_percent: float = 25,\r

            \        vp_approval_acv_threshold_usd: float = 75000,\r

            \        healthcare_max_without_vp: float = 20,\r

            \        multi_year_bonus_percent: float = 3,\r

            \    ):\r

            \        self.max_rep_discount_percent = max_rep_discount_percent\r

            \        self.manager_max_discount_percent =
            manager_max_discount_percent\r

            \        self.vp_approval_acv_threshold_usd =
            vp_approval_acv_threshold_usd\r

            \        self.healthcare_max_without_vp =
            healthcare_max_without_vp\r

            \        self.multi_year_bonus_percent = multi_year_bonus_percent\r

            \r

            \    def check(\r

            \        self,\r

            \        annual_contract_value_usd: float,\r

            \        requested_discount_percent: float,\r

            \        contract_term_years: int,\r

            \        industry_vertical: str,\r

            \    ) -> dict:\r

            \        vertical = industry_vertical.strip().lower()\r

            \        healthcare_verticals = {\"healthcare\", \"life_sciences\",
            \"life sciences\", \"pharma\"}\r

            \r

            \        rep_limit = self.max_rep_discount_percent\r

            \        if contract_term_years >= 2:\r

            \            rep_limit += self.multi_year_bonus_percent\r

            \r

            \        if vertical in healthcare_verticals and
            requested_discount_percent > self.healthcare_max_without_vp:\r

            \            return {\r

            \                \"rep_can_approve\": False,\r

            \                \"approval_required\": \"vp_sales\",\r

            \                \"rep_discount_limit_percent\": rep_limit,\r

            \                \"reason\": (\r

            \                    f\"Healthcare vertical discounts above
            {self.healthcare_max_without_vp}% \"\r

            \                    \"require VP Sales approval.\"\r

            \                ),\r

            \            }\r

            \r

            \        if (\r

            \            annual_contract_value_usd >
            self.vp_approval_acv_threshold_usd\r

            \            and requested_discount_percent >
            self.max_rep_discount_percent\r

            \        ):\r

            \            return {\r

            \                \"rep_can_approve\": False,\r

            \                \"approval_required\": \"vp_sales\",\r

            \                \"rep_discount_limit_percent\": rep_limit,\r

            \                \"reason\": (\r

            \                    f\"Deals over
            ${self.vp_approval_acv_threshold_usd:,.0f} with a discount \"\r

            \                    f\"above {self.max_rep_discount_percent}%
            require VP Sales approval.\"\r

            \                ),\r

            \            }\r

            \r

            \        if requested_discount_percent <= rep_limit:\r

            \            return {\r

            \                \"rep_can_approve\": True,\r

            \                \"approval_required\": \"none\",\r

            \                \"rep_discount_limit_percent\": rep_limit,\r

            \                \"reason\": (\r

            \                    f\"Requested discount is within the rep limit
            of {rep_limit}% \"\r

            \                    f\"({'includes multi-year bonus' if
            contract_term_years >= 2 else 'standard authority'}).\"\r

            \                ),\r

            \            }\r

            \r

            \        if requested_discount_percent <=
            self.manager_max_discount_percent:\r

            \            return {\r

            \                \"rep_can_approve\": False,\r

            \                \"approval_required\": \"sales_manager\",\r

            \                \"rep_discount_limit_percent\": rep_limit,\r

            \                \"reason\": (\r

            \                    f\"Discount exceeds rep limit of {rep_limit}%
            but is within the \"\r

            \                    f\"{self.manager_max_discount_percent}% manager
            threshold.\"\r

            \                ),\r

            \            }\r

            \r

            \        return {\r

            \            \"rep_can_approve\": False,\r

            \            \"approval_required\": \"vp_sales\",\r

            \            \"rep_discount_limit_percent\": rep_limit,\r

            \            \"reason\": (\r

            \                f\"Discount above
            {self.manager_max_discount_percent}% requires VP Sales approval.\"\r

            \            ),\r

            \        }\r

            \r

            \r

            _engine = DiscountApprovalEngine()\r

            \r

            \r

            @tool\r

            def check_discount_approval(\r

            \    annual_contract_value_usd: Annotated[float, \"Annual contract
            value in USD\"],\r

            \    requested_discount_percent: Annotated[float, \"Requested
            discount as a percentage, for example 28 for 28%\"],\r

            \    contract_term_years: Annotated[int, \"Contract length in
            years\"],\r

            \    industry_vertical: Annotated[\r

            \        str,\r

            \        \"Industry vertical, for example healthcare,
            financial_services, or standard\",\r

            \    ],\r

            ):\r

            \    \"\"\"Check discount approval level using Atlas Analytics deal
            desk rules.\"\"\"\r

            \    acv = float(str(annual_contract_value_usd).replace(\",\",
            \"\"))\r

            \    discount = float(str(requested_discount_percent).replace(\"%\",
            \"\").strip())\r

            \    term = int(str(contract_term_years).strip())\r

            \    return _engine.check(\r

            \        annual_contract_value_usd=acv,\r

            \        requested_discount_percent=discount,\r

            \        contract_term_years=term,\r

            \        industry_vertical=industry_vertical,\r

            \    )"
        _meta:
          name: check_discount_approval
          description:
          tool_id:
      system_prompt: >-
        {% message role="system" %}

        You are an Atlas Analytics deal desk assistant helping sales reps
        evaluate discount requests.

        You have access to two tools:

        1. search_deal_policy: Search deal desk policies for discount authority,
        pricing tiers, vertical rules, and approval escalation.

        2. check_discount_approval: Check whether a discount can be approved at
        rep level or needs escalation. Pass annual_contract_value_usd,
        requested_discount_percent, contract_term_years, and industry_vertical.

        Workflow:

        - Extract deal facts from the user's message (ACV, discount, contract
        term, vertical).

        - If any fact is missing, ask one short follow-up question.

        - Call check_discount_approval with the extracted facts.

        - Call search_deal_policy to find policy excerpts that support your
        answer.

        - Combine the approval result with policy citations in your reply.

        Response format:

        - Can the rep approve: yes or no

        - Approval required: none, sales_manager, or vp_sales

        - Rep discount limit

        - Policy references from search_deal_policy

        - Next steps for the sales rep

        {% endmessage %}
      user_prompt:
      required_variables:
      exit_conditions:
      state_schema: {}
      max_agent_steps: 100
      streaming_callback:
      raise_on_tool_invocation_failure: false
      tool_invoker_kwargs:
      confirmation_strategies:

outputs:
  answers:
  documents:
  messages: Agent.messages
```

</details>

**Result**: The agent has a clear policy for when and how to use each tool.

## Test the Agent

1. In Builder, with the deal-desk-agent pipeline open, click **Run Pipeline** next to the **Input** component.
2. Paste a message like this:

  ```text
  A healthcare prospect wants 28% off a $95,000 annual contract on a 1-year deal.
  Can the rep approve this discount?
  ```

  The code tool should report *VP Sales approval required* (healthcare cap is 20%). The agent should cite the healthcare vertical rules and escalation matrix from `search_deal_policy`.

3. Try a deal the rep can approve:

  ```text
  A standard SaaS prospect wants 12% off a $40,000 annual contract on a 1-year deal.
  Can the rep approve this?
  ```

  The code tool should report *rep can approve* (12% is within the 15% rep limit).

**Result**: Congratulations!You have created an agent that can answer discount approval questions with a mix of policy citations and deterministic business rules. The agent uses two tools: `search_deal_policy` for citations and `check_discount_approval` for the definitive call.

***

## What's Next

- Add more vertical rules or regional pricing tiers to the code tool init parameters.
- Connect the agent to your CRM through an MCP server tool. For details, see [Use Your Pipeline as an MCP Tool](/docs/how-to-guides/productionizing-your-pipeline/expose-pipeline-as-mcp-tool.mdx).
- Use [Prompt Explorer](/docs/how-to-guides/designing-your-pipeline/work-with-llms/using-prompt-studio.mdx) to refine the system prompt.
- For more on agents and tools, see [Agent Tools](/docs/concepts/ai-agents/agent-tools.mdx).
