A full-stack chat application for prospective renters with intelligent response handling and actionable next steps.
This is implemention of a Mini Leasing Assistant API that processes prospective renter inquiries and provides intelligent responses.
- Prospective renter sends free-text message (email/SMS)
- Microservice must:
- Decide which domain tool(s) to call for information
- Craft human-ready reply
- Return next-action for front-end execution
- LLM-Agent Logic: Lightweight agent using LLM (OpenAI/Claude/Gemini) to orchestrate domain tools
- Domain Tools: Three required tools for property information
- React Frontend: TypeScript chat workspace with message list and input
- Backend API: FastAPI endpoints for chat message processing
- Data Layer: Mock inventory with observability logging
check_availability(community_id, bedrooms)→ Unit availability informationcheck_pet_policy(community_id, pet_type)→ Pet policy and feesget_pricing(community_id, unit_id, move_in_date)→ Rent and specials
propose_tour: System suggests specific tour slotask_clarification: Question needs more detailshandoff_human: Cannot fulfill automatically
This application processes free-text messages (email or SMS) from prospective renters and provides intelligent responses with actionable next steps. The system decides which tools to call, crafts human-ready replies, and returns actions that the front-end can execute.
- Frontend: React TypeScript chat interface with message list, input bar, and dynamic action buttons
- Backend: FastAPI Python service with intelligent agent logic for processing renter inquiries
- MCP Server: Model Context Protocol server providing standardized tools for availability, pet policies, and pricing
- Communication: REST API with CORS-enabled endpoints between frontend/backend, direct function calls to MCP tools
- Real-time Chat Interface: Responsive chat workspace with message history
- Intelligent Response System: Context-aware responses based on inquiry type
- Action-based UI: Dynamic buttons for tour scheduling, clarification requests, and human handoff
- Tour Scheduling: Automated tour time proposals with confirmation workflow
- Human Handoff: Seamless escalation to human agents when needed
The system maintains conversation context across multiple messages using a client-based session system.
1. Frontend Session Identification:
- Browser automatically generates a unique client ID on first visit
- Stored in browser's localStorage for persistence across page refreshes
- Client ID is included in every API request to maintain conversation context
2. Backend Session Storage:
- Each client ID maps to a
ClientMemoryobject containing:- Full conversation history (user and assistant messages)
- Learned preferences extracted from conversation
- Lead information and community context
- Session data persists for the duration of the backend server process
- Memory includes preference extraction and confidence scoring
3. Intelligent Clarification System:
- System analyzes learned preferences before processing each message
- Asks clarifying questions when critical information is missing:
- Community: "Which community or property are you interested in?"
- Move-in Date: "When are you looking to move in?" (for availability/pricing queries)
- Budget/Requirements: Asked as conversation progresses
- Questions are asked in logical order (community first, then move-in date, then details)
- Won't re-ask for information already captured in session preferences
1. User: "is a 2‑bedroom still available and do you allow cats?"
→ System: No community in session → Ask for community
2. User: "Sunset Ridge"
→ System: Extracts community preference → Ask for move-in date
3. User: "July 2025"
→ System: Has community + move-in date → Check availability & pet policy
4. User: "What about pricing?"
→ System: Uses saved community + move-in preferences → Get pricing
- Contextual Conversations: No need to repeat information within a session
- Progressive Information Gathering: System builds complete picture over multiple exchanges
- Natural Interaction: Users can ask follow-up questions without re-providing context
- Efficient Processing: Avoids redundant clarification questions
The system implements a single REST endpoint:
POST /api/reply
Request:
{
"lead": {
"name": "Jane Doe",
"email": "jane@example.com"
},
"message": "Hi, is a 2-bedroom still available and do you allow cats?",
"preferences": {
"bedrooms": 2,
"move_in": "2025-07-01"
},
"community_id": "sunset-ridge"
}Response:
{
"reply": "Hi Jane! Unit 12B is available and cats are welcome (one-time $50 fee). Tours are open this Saturday 10 am–2 pm—does 11 am work?",
"action": "propose_tour",
"proposed_time": "2025-06-14T18:00:00Z"
}The system uses a sophisticated action classification system to determine the appropriate next step after processing each user message.
propose_tour: System suggests scheduling a property tour
- When Used: After answering 2+ substantial questions about availability, pricing, pets, amenities
- Criteria: User has provided community and move-in date, main concerns addressed
- Response: Includes specific proposed time (2 days ahead at 2 PM by default)
- Frontend: Displays "Schedule Tour for [datetime]" button
- User Action: Click to confirm tour, system shows confirmation message
ask_clarification: System needs more information to proceed
- When Used: Missing critical data (community, move-in date, budget) or ambiguous questions
- Criteria: User query requires information not yet captured in session preferences
- Response: Asks specific clarifying questions in logical order
- Frontend: Displays subtle prompt styling, user responds via text input
- Flow: Community → Move-in Date → Other details as needed
handoff_human: Escalate to human leasing specialist
- When Used: Complex situations, special requests, or system limitations
- Criteria: Queries about lease terms, exceptions, or issues beyond tool capabilities
- Response: Professional handoff message with context preservation
- Frontend: Displays "Connect with Human Agent" button
- User Action: Click to initiate human contact
Backend Action Detection (claude_agent.py):
def _extract_action_and_time(self, response_text: str) -> tuple[str, Optional[str]]:
# Pattern matching on LLM response content
# Tour proposal phrases
tour_phrases = ["schedule a tour", "see the unit", "take a look",
"visit in person", "show you around"]
# Clarification phrases
clarification_phrases = ["could you tell me", "which community",
"when are you looking", "what's your budget"]
# Handoff phrases
handoff_phrases = ["connect you with", "leasing specialist",
"human agent", "complex situation"]Conversation Context Analysis:
- Substantial Response Tracking: Counts non-clarification responses to determine tour readiness
- Preference Completeness: Analyzes learned preferences to identify missing information
- Timing Logic: Proposes tours when conversation has sufficient depth (≥2 substantial answers)
Frontend Action Rendering (ActionButton.tsx):
switch (response.action) {
case 'propose_tour':
return <button onClick={() => onActionClick('confirm_tour')}>
Schedule Tour for {formatTime(response.proposed_time)}
</button>
case 'ask_clarification':
return <div className="clarification-prompt">
Please provide more details
</div>
case 'handoff_human':
return <button onClick={() => onActionClick('connect_human')}>
Connect with Human Agent
</button>
}Action Flow Integration:
- LLM Processing: Claude generates response text with natural language cues
- Pattern Detection: Backend extracts action type from response content
- Data Enrichment: Adds proposed times, generates appropriate metadata
- Frontend Rendering: Displays contextual UI elements based on action type
- User Interaction: Handles clicks/responses and continues conversation flow
The system uses conversation context to make intelligent action decisions:
- Early Stage: Focuses on
ask_clarificationto gather essential information - Information Gathering: Asks for community, move-in date, preferences in logical sequence
- Engagement Phase: Provides detailed answers about availability, pricing, policies
- Conversion Stage: Transitions to
propose_tourwhen user is sufficiently informed - Exception Handling: Uses
handoff_humanfor edge cases and complex requests
This creates a natural conversation flow that guides users from initial inquiry to tour scheduling or appropriate escalation.
For detailed setup instructions, testing guidelines, and development workflows, see DEVELOPMENT.md.
Quick start:
make setup # Install dependencies
make dev # Start both serversThe backend uses Claude API for intelligent agent responses. To test the integration:
- Set your Claude API key as an environment variable:
export CLAUDE_API_KEY=your_api_key_here
Test the agent integration directly:
curl -X POST http://localhost:8000/api/reply \
-H "Content-Type: application/json" \
-d '{
"lead": {
"name": "Jane Doe",
"email": "jane@example.com"
},
"message": "Hi, is a 2-bedroom still available and do you allow cats?",
"preferences": {
"bedrooms": 2,
"move_in": "2025-07-01"
},
"community_id": "sunset-ridge"
}'You can also test using Python:
import requests
response = requests.post("http://localhost:8000/api/reply", json={
"lead": {
"name": "Jane Doe",
"email": "jane@example.com"
},
"message": "Hi, is a 2-bedroom still available and do you allow cats?",
"preferences": {
"bedrooms": 2,
"move_in": "2025-07-01"
},
"community_id": "sunset-ridge"
})
print(response.json())The API should return a JSON response with:
reply: Claude-generated response textaction: One of "propose_tour", "ask_clarification", or "handoff_human"proposed_time: ISO timestamp for tour proposals (if applicable)
✅ Integration Working: The Claude API integration has been successfully tested and is functional.
Verified Components:
- ✅ Claude API connection with valid credentials
- ✅ MCP client successfully connects to domain tools
- ✅ Inventory data loading (3 communities, 8 total units)
- ✅ Tool execution:
check_availability,check_pet_policy,get_pricing - ✅ Agent service orchestration and request processing
Sample Test Logs:
2025-06-25 10:43:56,047 - httpx - INFO - HTTP Request: POST https://api.anthropic.com/v1/messages "HTTP/1.1 200 OK"
2025-06-25 10:43:56,051 - tools.tools - INFO - LLM called check_availability: community_id=sunset-ridge, bedrooms=1
2025-06-25 10:43:56,051 - data.inventory - INFO - Found 1 available 1-bedroom units in sunset-ridge
2025-06-25 10:43:56,051 - app.services.mcp_client - INFO - MCP tool check_availability called successfully
Known Issues:
⚠️ Message Formatting Issue: There is a formatting issue in the final Claude API response causingmessages.2.content.0.type: Field requirederror. The tools execute successfully but the final response formatting needs to be fixed inclaude_agent.py:227where tool results are added to the conversation messages.
Troubleshooting:
- 401 Unauthorized: Check that
CLAUDE_API_KEYis set correctly - Credit Balance Error: Add credits to your Claude API account
- Connection Error: Ensure backend server is running on port 8000
This project uses a simplified MCP approach rather than the full MCP protocol:
Current Implementation (Direct Function Calls):
- MCP tools are imported directly as Python functions
- All code runs in a single backend process
- Simpler deployment and debugging
Full MCP Protocol Would Use:
- Separate MCP server process communicating via JSON-RPC
- Process isolation between backend and tools
- Standard MCP protocol for cross-language compatibility
The current approach provides the benefits of MCP (standardized tool definitions, proper abstractions) without the complexity of separate processes. This is ideal for single-application deployments where simplicity and performance are prioritized over protocol standardization.
The system uses FAISS vector similarity search to intelligently match user queries to our predefined data categories for both pet policies and community names.
Users ask about pets and communities using natural language that doesn't exactly match our data structure:
Pet Policy Matching:
- User: "Can I have a hamster?" → Our data has:
"small_pets" - User: "Do you allow puppies?" → Our data has:
"dog" - User: "What about bunnies?" → Our data has:
"small_pets"
Community Name Matching:
- User: "Do you have units at Sunset?" → Our data has:
"sunset-ridge" - User: "What about Oak Valley?" → Our data has:
"oak-valley-apartments" - User: "Any availability at Pine?" → Our data has:
"pine-meadows"
Vector Semantic Matching:
- FAISS Index: Build vector embeddings for all data categories:
- Pet types:
dog,cat,bird,fish,small_pets - Community names:
sunset-ridge,oak-valley-apartments,pine-meadows
- Pet types:
- Query Encoding: Convert user queries to vectors using
sentence-transformers - Similarity Search: Find the closest matching category with confidence scores
- Intelligent Fallback: Falls back to exact matching if vector search fails
Pet Policy Matches:
"hamster" → "small_pets" (confidence: 0.78)
"puppy" → "dog" (confidence: 0.85)
"kitten" → "cat" (confidence: 0.82)
"bunny" → "small_pets" (confidence: 0.76)
Community Name Matches:
"Sunset" → "sunset-ridge" (confidence: 0.82)
"Oak Valley" → "oak-valley-apartments" (confidence: 0.89)
"Pine" → "pine-meadows" (confidence: 0.75)
"Ridge" → "sunset-ridge" (confidence: 0.71)
- Confidence Threshold: 0.6 (configurable)
- Model:
all-MiniLM-L6-v2(fast, good quality) - Fallback: Exact string matching if vector search fails
- Caching: Search results cached for performance
This enables natural conversation while maintaining accurate policy and community lookups.
├── frontend/ # React TypeScript UI
│ ├── src/
│ │ ├── components/ # React components
│ │ ├── types/ # TypeScript type definitions
│ │ └── services/ # API service layer
├── backend/ # FastAPI Python service
│ ├── app/
│ │ ├── api/ # API routes
│ │ ├── models/ # Pydantic schemas
│ │ └── services/ # Business logic
├── mcp_server/ # Model Context Protocol server
│ ├── data/ # Inventory data and vector search
│ ├── tools/ # MCP tool implementations
│ └── __init__.py # MCP server initialization
├── Makefile # Development automation
└── DEVELOPMENT.md # Setup and testing guide