Skip to content

Create a reusable need-analysis agent #208

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
359 changes: 359 additions & 0 deletions notebooks/need-analysis-agent.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,359 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Install the required dependencies"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Install the required dependencies\n",
"! pip install haystack-ai>=2.14.0\n",
"! pip install docstring-parser\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"Next, we configure our variables. We need to set the `OPENAI_API_KEY` for [`OpenAIChatGenerator`](https://docs.haystack.deepset.ai/docs/openaichatgenerator)."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"from getpass import getpass\n",
"import os\n",
"\n",
"if not os.environ.get(\"OPENAI_API_KEY\"):\n",
" os.environ[\"OPENAI_API_KEY\"] = getpass(\"Enter your OpenAI API key:\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Create the tools to get user input and pass it on to next component."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"from typing import Annotated\n",
"from haystack.tools import tool, ComponentTool\n",
"from haystack.components.agents import Agent\n",
"from haystack.components.generators.chat import OpenAIChatGenerator\n",
"from haystack.dataclasses import ChatMessage\n",
"from haystack.components.generators.utils import print_streaming_chunk\n",
"\n",
"@tool\n",
"def human_in_loop_tool(\n",
" question: Annotated[\n",
" str,\n",
" \"A clarifying question to prompt the user for more information\"\n",
" ]\n",
") -> str:\n",
" '''Ask the user a clarifying question and return their response via input().'''\n",
" user_message = input(f\"[Agent needs your input] {question}\\n> \")\n",
" return user_message\n",
"\n",
"@tool\n",
"def hand_off_to_next_tool(\n",
" info_package: Annotated[\n",
" dict,\n",
" \"The information package to be passed to the downstream agent or terminal\"\n",
" ]\n",
") -> dict:\n",
" \"\"\"\n",
" Stub: returns the info_package so that your orchestrator\n",
" (or whichever process is listening) can pick it up.\n",
" \"\"\"\n",
" return info_package\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Create and Configure the Need Analysis Agent\n",
"Create a Haystack Agent and configure its behavior with a system prompt and tools. Then we convert it into a `ComponentTool` to be used by another agent."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"need_analysis_agent = Agent(\n",
" chat_generator=OpenAIChatGenerator(model=\"o4-mini\"),\n",
" tools=[human_in_loop_tool, hand_off_to_next_tool],\n",
" streaming_callback=print_streaming_chunk,\n",
" system_prompt=(\n",
" \"You are a Needs‐Analysis Research Agent. Your sole job is to elicit from the user all the facts, context, constraints, \"\n",
" \"and preferences necessary to solve their query. Treat each new user query as a requirement gathering exercise. Ask only one question in a single message to prevent confusion.\\n\\n\"\n",
" \"1. **Clarify the Goal**: Begin by restating the user’s request in your own words. Ask follow-ups to ensure you understand their high-level objective.\\n\"\n",
" \"2. **Uncover Context**: Ask about background, motivations, timeline, budget/resources, stakeholders, technical constraints or any existing work.\\n\"\n",
" \"3. **Elicit Preferences**: Drill down into style preferences, priorities (e.g. cost vs. quality vs. speed), formats, platforms, level of detail, etc.\\n\"\n",
" \"4. **Validate and Summarize**: Periodically summarize what you’ve heard and ask the user to confirm or correct. This ensures completeness.\\n\"\n",
" \"5. **Depth and Edge Cases**: Probe for exceptions, unusual scenarios, or potential trade-offs the user may not have considered.\\n\\n\"\n",
" \"**Exit Condition**: Call the `hand_off_to_next_tool` and exit when you have gathered a clear, structured “information package” (objective + context + constraints + preferences) \"\n",
" \"to enable a downstream agent to produce a full solution.\"\n",
" ),\n",
" exit_conditions=[\"hand_off_to_next_tool\"]\n",
")\n",
"\n",
"need_analysis_agent.warm_up()\n",
"\n",
"need_analysis_tool = ComponentTool(\n",
" component=need_analysis_agent,\n",
" description=\"Use this tool to gather information about the user's needs and preferences\",\n",
" name=\"need_analysis_tool\"\n",
")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"For this demo, we use a `consulting_agent` that calls the `need_analysis_tool` to gather user requirements and preferences, then uses that information to generate a comprehensive recommendation. There is only tool for now to keep it simple, later we can add more tools."
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"consulting_agent = Agent(\n",
" chat_generator=OpenAIChatGenerator(model=\"o4-mini\", streaming_callback=print_streaming_chunk),\n",
" system_prompt=\"\"\"\n",
" You are a car consultant agent that generates a car recommendation based on the user's needs and preferences.\n",
" \"\"\",\n",
" tools=[need_analysis_tool],\n",
" exit_conditions=[\"text\"]\n",
")\n",
"\n",
"consulting_agent.warm_up()\n",
"\n",
"\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## See the Agent in Action"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\n",
"[TOOL CALL]\n",
"Tool: need_analysis_tool \n",
"Arguments: {\"messages\":[{\"role\":\"user\",\"content\":[{\"text\":\"I want to buy a Range Rover\"}]}]}\n",
"\n",
"\n",
"\n",
"[TOOL CALL]\n",
"Tool: human_in_loop_tool \n",
"Arguments: {\"question\":\"It sounds like you're looking to purchase a Range Rover. Could you tell me what your main objectives are for this vehicle—for example, how you plan to use it, what features you consider essential, and any budget or timeline constraints?\"}\n",
"\n",
"\n",
"\n",
"[TOOL RESULT]\n",
"I need the car for daily commute and occasional trips\n",
"\n",
"\n",
"\n",
"[TOOL CALL]\n",
"Tool: human_in_loop_tool \n",
"Arguments: {\"question\":\"Understood. To help narrow down the right Range Rover model and configuration, what is your budget range for the purchase and do you have a specific timeframe or deadline by which you need the vehicle?\"}\n",
"\n",
"\n",
"\n",
"[TOOL RESULT]\n",
"budegt is 100k, I want to buy the car next month\n",
"\n",
"\n",
"\n",
"[TOOL CALL]\n",
"Tool: human_in_loop_tool \n",
"Arguments: {\"question\":\"Thanks for clarifying. Next, do you prefer a brand-new Range Rover or would you consider a certified pre-owned model? Also, what are your must-have features (e.g., seating capacity, engine type, infotainment options, towing capability, off-road package)?\"}\n",
"\n",
"\n",
"\n",
"[TOOL RESULT]\n",
"Brand new, hybrid engine with a panoramic roof\n",
"\n",
"\n",
"\n",
"[TOOL CALL]\n",
"Tool: human_in_loop_tool \n",
"Arguments: {\"question\":\"To pinpoint the right Range Rover variant, which body style and seating capacity do you prefer? For example, are you looking at the full-size Range Rover or the smaller Sport/Velar/Evoque, and do you need seating for five or seven passengers?\"}\n",
"\n",
"\n",
"\n",
"[TOOL RESULT]\n",
"Sport or Evoque, for 5 people\n",
"\n",
"\n",
"\n",
"[TOOL CALL]\n",
"Tool: human_in_loop_tool \n",
"Arguments: {\"question\":\"Great—one last area to cover: do you have any specific performance targets (for example horsepower, towing capacity, or off-road capability) and aesthetic preferences (such as exterior color and interior materials)? Additionally, are there any advanced driver-assistance or infotainment features that are must-haves for you?\"}\n",
"\n",
"\n",
"\n",
"[TOOL RESULT]\n",
"High horsepower, color black\n",
"\n",
"\n",
"\n",
"[TOOL CALL]\n",
"Tool: human_in_loop_tool \n",
"Arguments: {\"question\":\"Thank you. Finally, could you specify your interior material and color preferences (e.g., leather or suede, light or dark trim) and any driver-assistance or infotainment systems you consider essential (such as adaptive cruise, lane-keep assist, premium sound, rear-seat entertainment)? This will help complete your configuration.\"}\n",
"\n",
"\n",
"\n",
"[TOOL RESULT]\n",
"Leather seats and adaptive cruise\n",
"\n",
"\n",
"\n",
"[TOOL CALL]\n",
"Tool: hand_off_to_next_tool \n",
"Arguments: {}\n",
"\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"Failed to invoke Tool `hand_off_to_next_tool` with parameters {}. Error: hand_off_to_next_tool() missing 1 required positional argument: 'info_package'\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\n",
"\n",
"\n",
"[TOOL CALL]\n",
"Tool: hand_off_to_next_tool \n",
"Arguments: {\"info_package\":{\"objective\":\"Purchase a new Range Rover for daily commuting and occasional trips\",\"context\":{\"use_case\":\"Daily commute plus occasional trips\"},\"constraints\":{\"budget\":100000,\"purchase_timeline\":\"Within next month\",\"model_condition\":\"Brand new\"},\"preferences\":{\"model_options\":[\"Sport\",\"Evoque\"],\"seating_capacity\":5,\"powertrain\":\"Hybrid engine\",\"roof\":\"Panoramic\",\"performance\":\"High horsepower\",\"exterior_color\":\"Black\",\"interior_material\":\"Leather\",\"driver_assistance\":[\"Adaptive cruise control\"]}}}\n",
"\n",
"\n",
"\n",
"[TOOL RESULT]\n",
"{'objective': 'Purchase a new Range Rover for daily commuting and occasional trips', 'context': {'use_case': 'Daily commute plus occasional trips'}, 'constraints': {'budget': 100000, 'purchase_timeline': 'Within next month', 'model_condition': 'Brand new'}, 'preferences': {'model_options': ['Sport', 'Evoque'], 'seating_capacity': 5, 'powertrain': 'Hybrid engine', 'roof': 'Panoramic', 'performance': 'High horsepower', 'exterior_color': 'Black', 'interior_material': 'Leather', 'driver_assistance': ['Adaptive cruise control']}}\n",
"\n",
"Based on everything you’ve told me, here are two turnkey, brand-new Range Rover configurations—one Sport and one Evoque—that meet your criteria. Both use the PHEV (plug-in hybrid) powertrain, include a panoramic roof, leather interior, adaptive cruise control and deliver strong performance. Each will run you right around or just under your $100 K budget (MSRP + destination) and should be available to order now for delivery within a month.\n",
"\n",
"1) 2024 Range Rover Sport P440e Autobiography \n",
" • Powertrain & Performance \n",
" – 3.0-liter inline-6 mild hybrid + 105 kW electric motor → total 438 hp & 700 Nm \n",
" – 0–60 mph in 5.2 s \n",
" – Electric-only range ~30 mi; up to 450 mi combined \n",
" • Exterior & Interior \n",
" – Black exterior with contrast gloss-black grille and badging \n",
" – Panoramic fixed-glass sunroof \n",
" – Ebony perforated Windsor leather with Ebony headliner \n",
" • Key Features \n",
" – Adaptive Cruise with Steering Assist \n",
" – Terrain Response 2 off-road suite \n",
" – Upgraded Meridian 3D Surround Sound (1,400 W) \n",
" – Heated/ventilated front seats, 4-zone climate \n",
" – Towing prep (3,500 kg capacity) \n",
" • Cost Estimate \n",
" – Base MSRP: ~$92,000 \n",
" – Options & destination: +$5–7,000 \n",
" – Total: ~$98–99,000 \n",
"\n",
"2) 2024 Range Rover Evoque P300e R-Dynamic HSE \n",
" • Powertrain & Performance \n",
" – 1.5-liter three-cylinder PHEV → total 305 hp & 540 Nm \n",
" – 0–60 mph in ~6.3 s \n",
" – Electric range ~34 mi; up to 410 mi combined \n",
" • Exterior & Interior \n",
" – Black exterior paint with gloss-black roof pillars \n",
" – Panoramic sliding roof \n",
" – Ebony leather seats with Ebony accent stitching \n",
" • Key Features \n",
" – Adaptive Cruise with Queue Assist \n",
" – All-surface Progress Control (cruise for low-speed off-road) \n",
" – Meridian™ 3D Surround Sound (600 W) \n",
" – Heated steering wheel, power-folding mirrors \n",
" – Tow hitch prep (2,500 kg capacity) \n",
" • Cost Estimate \n",
" – Base MSRP: ~$65,000 \n",
" – Options & destination: +$7–8,000 \n",
" – Total: ~$72–73,000 \n",
"\n",
"Recommendation Summary \n",
"– If you want maximum power, interior space and towing capability, go Sport P440e Autobiography. You’ll be near the top of your budget but get best-in-class luxury and performance. \n",
"– If you prioritize efficiency, city maneuverability and a lower sticker, the Evoque P300e R-Dynamic HSE is an excellent fit—and comes in well under budget, leaving headroom for accessories (e.g. tow package, extended warranty). \n",
"\n",
"Next Steps \n",
"1. Contact your local Land Rover retailer to confirm factory-order slots and exact delivery timing (most have PHEV allocation now). \n",
"2. Arrange a test-drive of each model to feel the performance and usability firsthand. \n",
"3. Discuss lease vs. purchase financing—your dealer can break out payment scenarios based on incentives (federal/state PHEV credits). \n",
"\n",
"Let me know if you’d like help connecting with a dealer or running specific numbers on monthly payments!\n",
"\n"
]
}
],
"source": [
"message=[ChatMessage.from_user(\"I want to buy a Range Rover \")]\n",
"ans = consulting_agent.run(messages=message)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.3"
}
},
"nbformat": 4,
"nbformat_minor": 2
}