A sophisticated multi-utility AI chatbot powered by LangGraph, featuring advanced RAG, intelligent tool calling, MCP integration, and agentic workflow orchestration.
- Overview
- Architecture
- Key Features
- Core Concepts
- Tech Stack
- Project Structure
- Installation
- Usage
- Advanced Features
- LangSmith Observability
- Contributing
Aria is an intelligent conversational agent that combines the power of:
- LangGraph: For orchestrating complex AI workflows
- LangChain: For tool management and LLM interactions
- Google Gemini 2.5: State-of-the-art language understanding
- Agentic RAG: Intelligent document retrieval with reasoning
- MCP Protocol: Extensible tool integration via Model Context Protocol
- SQLite: Persistent multi-threaded conversation storage
This chatbot goes beyond simple retrieval—it understands when to use which tool, combines information from multiple sources, and maintains context across dozens of independent conversations.
┌─────────────────────────────────────────────────────────┐
│ Streamlit Frontend │
│ (Chat UI, PDF Management, Thread Handling) │
└────────────────┬────────────────────────────────────────┘
│
┌────────────────▼────────────────────────────────────────┐
│ LangGraph State Machine │
│ ┌──────────────────────────────────────────────────┐ │
│ │ START → chat_node → tools_condition │ │
│ │ ↑ ↓ │ │
│ │ └─── tool_node ←────┘ │ │
│ └──────────────────────────────────────────────────┘ │
└────────────────┬────────────────────────────────────────┘
│
┌────────────┼────────────┐
│ │ │
┌───▼──┐ ┌─────▼──┐ ┌──────▼──────┐
│ LLM │ │ Tools │ │ Storage │
│ │ │ │ │ (SQLite) │
└──────┘ └────────┘ └─────────────┘
- User Input → Streamlit frontend
- Thread Routing → SQLite retrieves conversation history
- LLM Processing → Gemini determines which tools to use
- Tool Execution → Parallel/sequential tool invocation
- Response Assembly → Stream response back to user
- Storage → Persist conversation to database
Traditional RAG:
Query → Retrieve → Generate
Aria's Agentic RAG:
Query → Analyze → Decide Retrieval Strategy → Retrieve → Reason → Generate
What makes it "Agentic":
- Intelligent Routing: LLM decides whether RAG is needed or if other tools are better
- Multi-step Reasoning: Can combine document insights with web search or calculations
- Adaptive Chunking: 1000-character chunks with 200-char overlap for semantic cohesion
- Semantic Search: Uses Google's embeddings (models/embedding-001) for similarity matching
How it works:
- User uploads a PDF → Text extraction & splitting
- Documents are embedded using Google's embedding model
- FAISS index created for fast similarity search
- When queried, LLM receives top-4 relevant chunks
- LLM decides: use RAG info + search + calculate + track expenses?
7 Integrated Tools:
| Tool | Purpose | Provider | Type |
|---|---|---|---|
| DuckDuckGo Search | Real-time web information | DuckDuckGo | External API |
| Stock Prices | Financial data retrieval | Alpha Vantage | External API |
| Calculator | Math operations | Native | Built-in |
| RAG Tool | Document Q&A | FAISS + Embeddings | Internal |
| Add Expense | Track spending | MCP Server | Remote Protocol |
| List Expenses | View transaction history | MCP Server | Remote Protocol |
| Summarize | Generate expense summaries | MCP Server | Remote Protocol |
Tool Calling Flow:
LLM Decision:
├─ If ("search" in intent) → DuckDuckGo_Search()
├─ If ("stock" in intent) → Get_Stock_Price()
├─ If ("math" in intent) → Calculator()
├─ If ("document" in intent) → RAG_Tool()
├─ If ("expense" in intent) → MCP_Expense_Tools()
└─ If ("none" in intent) → Direct_Response()Key Technologies:
- LangChain StructuredTool: Type-safe tool definitions
- Tool Binding: LLM knows function signatures, parameters, return types
- Conditional Edges: LangGraph routes based on LLM output
- Async Wrapping: MCP async tools converted to sync for compatibility
What is MCP? MCP is a standardized protocol for connecting AI models with external tools and data sources. It's like an "API standard" for AI assistants.
Aria's MCP Setup:
Aria Chatbot ←→ MCP Client ←→ Expense Tracker Server
(stdio transport)
Local vs Remote:
- ❌ Remote Setup Failed: 401 Unauthorized from remote endpoint
- ✅ Local Setup Success: Python subprocess stdio transport
Architecture:
# Local MCP Expense Tracker
MCP Service:
├─ add_expense(amount, date, category)
├─ list_expenses(filters)
└─ summarize(period)
Transport: Stdio (stdin/stdout pipes)
Async Handling: StructuredTool wrapper for sync execution
Threading: Separate event loop for async operationsBenefits:
- Extensibility: Add any MCP server without modifying core code
- Isolation: Tools run in separate processes
- Standardization: Same API whether local or remote
What is LangSmith?
LangSmith is a unified platform for debugging, testing, and monitoring AI applications. It provides complete visibility into your LLM calls, tool usage, latency, and failures.
Aria's LangSmith Integration:
Every chat interaction is automatically traced and logged to LangSmith for real-time monitoring and debugging.
What Gets Traced:
Chat Turn Trace:
├─ User Input
│ ├─ Message content
│ ├─ Thread ID
│ └─ Message count
├─ Processing
│ ├─ LLM invocation (Gemini 2.5)
│ ├─ Tool decisions & calls
│ └─ Tool execution results
└─ Output
├─ AI response
├─ Response length
├─ Tools used
└─ Execution timing
Key Metrics Captured:
| Metric | Purpose | Use Case |
|---|---|---|
| Response Time | Track LLM latency | Identify slow responses |
| Tools Used | Monitor tool routing | Understand agent behavior |
| Token Count | Measure input/output size | Cost analysis |
| Error Tracking | Log failures | Debug issues |
| Thread ID | Link to conversation | Trace user sessions |
| Timestamp | Record exact time | Performance trending |
LangSmith Dashboard Views:
-
Traces Tab: Complete execution flow for each chat turn
- Visual hierarchy of LLM calls, tool calls, and responses
- Input/output for each node
- Timing information
-
Metrics Tab: Aggregated performance data
- Average response time per conversation
- Tool usage frequency
- Error rates and types
-
Debugging: Deep-dive into specific traces
- See exact prompts sent to LLM
- Tool execution details
- Error messages and stack traces
Setup:
# Add to .env file
LANGCHAIN_TRACING_V2=true
LANGCHAIN_PROJECT=Advance LangGraph project
LANGCHAIN_API_KEY=<your-langsmith-api-key>
LANGCHAIN_ENDPOINT=https://api.smith.langchain.comGet your API key from: https://smith.langchain.com/settings/org/api-keys
How It Works:
# Automatic tracing happens in chatbot_stream_with_tracing()
@traceable(name="Chat Turn", tags=["chat", "stream"])
def traced_stream():
# All code here is automatically traced
for chunk in chatbot.stream(input_dict, config=config):
yield chunkEvery interaction flows through LangSmith's tracer, creating a complete audit trail of:
- What the user asked
- How the agent reasoned about tools
- Which tools were called and why
- What data was retrieved/computed
- How the final response was generated
Use Cases:
- Real-time Monitoring: Watch trace execution as users chat
- Performance Analysis: Identify bottlenecks (slow tools, token usage)
- Debugging: Replay failed traces to understand what went wrong
- Cost Tracking: Monitor LLM token usage and API calls
- Quality Assurance: Audit agent behavior patterns
- User Behavior: Understand which features users interact with
Example Scenario:
User: "Based on my uploaded document, find instances where costs exceeded $10,000"
LangSmith Trace Shows:
- LLM received: user query + system prompt + document chunks (RAG)
- LLM decision: "Need to use RAG tool to search document"
- Tool executed: rag_tool("costs exceeded 10000", thread_id=...)
- Tool returned: 3 matching sections from document
- LLM regenerated: Final response with filtered results
- Trace metadata: response_time=2.3s, tools_used=["rag_tool"], token_count=1240
Features:
✅ Automatic tracing - No code changes needed, works out of the box ✅ Tool tracking - See which tools called and their execution time ✅ Error capture - Failures are automatically logged with context ✅ Session continuity - Thread IDs link traces to conversations ✅ No data leakage - API calls are secure, private to your org
Dashboard Sample:
Project: Advance LangGraph project
├─ Today
│ ├─ Chat Turn (2:45 PM) - Response: "The document shows..." | Tools: rag_tool | Time: 2.3s
│ ├─ Chat Turn (2:41 PM) - Response: "AAPL is trading at..." | Tools: stock_price | Time: 1.2s
│ ├─ Chat Turn (2:38 PM) - Response: "47 * 32 = 1504" | Tools: calculator | Time: 0.5s
│ └─ Chat Turn (2:35 PM) - Response: "Here's what I found..." | Tools: duckduckgo_search | Time: 3.8s
└─ Last 7 days
└─ (Historical data...)
For detailed configuration and troubleshooting, see LANGSMITH_FIX.md.
What are Threads?
Threads enable independent conversation contexts. Each thread has:
- Unique UUID identifier
- Isolated conversation history
- Separate document index (FAISS)
- Dedicated metadata storage
Per-Thread Storage:
Database (SQLite):
├─ Thread A (UUID: 22258138...)
│ ├─ Message 1: "Hi, analyze my budget"
│ ├─ Message 2: "Create expense tracking"
│ └─ Metadata: {docs_indexed: 1, chunks: 245}
│
├─ Thread B (UUID: 8f9a1d2c...)
│ ├─ Message 1: "Search latest AI news"
│ └─ Message 2: "What about LangGraph?"
│
└─ Thread C (UUID: 7c4e5b9a...)
├─ Message 1: "Calculate 500 * 2.5"
└─ ...
Implementation:
- SQLite Checkpointer:
langgraph_checkpoint_sqlitepersists state - Thread ID Config: Passed to every invocation via
configurable["thread_id"] - Per-Thread Retrievers:
_THREAD_RETRIEVERSdict maintains separate FAISS indexes - Conversation Isolation: Messages in Thread A don't affect Thread B
Thread Operations:
# Create new thread
thread_id = generate_thread_id()
# Load existing thread
state = chatbot.get_state(config={"configurable": {"thread_id": thread_id}})
# Continue conversation
for msg in chatbot.stream({"messages": [HumanMessage(...)]},
config={"configurable": {"thread_id": thread_id}}):
process(msg)SQLite with LangGraph Checkpointer
Schema (Auto-managed by langgraph-checkpoint-sqlite):
threads:
├─ thread_id (primary key)
├─ checkpoint_data (serialized state)
└─ timestamp
Type structure (internal):
├─ messages: List[Union[HumanMessage, AIMessage, ToolMessage]]
└─ metadata: Dict[str, Any]
Thread-Local Storage:
# Memory storage (session-based)
_THREAD_RETRIEVERS: Dict[str, Any] # FAISS indices per thread
_THREAD_METADATA: Dict[str, dict] # Document info per thread
# Persistent storage (database)
SQLiteSaver checkpointer # Message historyPersistence Flow:
1. User sends message
2. LLM generates response + tool calls
3. Tools execute
4. State updated: messages + tool outputs
5. SQLiteSaver checkpoints to database
6. Next request loads from checkpoint
Why SQLite?
- ✅ Simple, no external DB needed
- ✅ Thread-safe (aiosqlite for async)
- ✅ Persistent across restarts
- ✅ Perfect for single-machine deployment
- ✅ Easy migration to PostgreSQL later
What it is: A directed acyclic graph (DAG) that defines AI workflow execution.
Aria's Graph:
graph = StateGraph(ChatState)
graph.add_node("chat_node", chat_node) # LLM invocation
graph.add_node("tools", tool_node) # Tool execution
graph.add_edge(START, "chat_node") # Entry point
graph.add_conditional_edges( # Smart routing
"chat_node",
tools_condition, # Decides: call tools or finish?
{
"continue": "tools",
END: END
}
)
graph.add_edge("tools", "chat_node") # Loop backExecution Trace:
START
↓
chat_node → [LLM decides: need tools?]
↓
tools_condition → {
No tools → END (return response)
Tools needed → tools
}
↓
tools → Execute all tools in parallel
↓
chat_node → [LLM processes tool outputs]
↓
(loop until no more tools needed)
Problem: How do we merge tool outputs back into the message history?
Solution: Reducer function
messages: Annotated[list[BaseMessage], add_messages]What it does:
Old messages: [User, Assistant, ToolMessage]
New messages: [ToolMessage, ToolMessage]
Result: [User, Assistant, ToolMessage, ToolMessage, ...]
(Automatically merges instead of replacing)
Why Stream?
- ✅ Real-time token-by-token display
- ✅ Better UX (doesn't feel frozen)
- ✅ Faster apparent response time
- ✅ Can cancel long responses
Aria's Streaming:
for message_chunk, _ in chatbot.stream(
{"messages": [HumanMessage(content=user_input)]},
config=CONFIG,
stream_mode="messages"
):
if isinstance(message_chunk, AIMessage):
yield message_chunk.content # Stream to Streamlit- LangGraph 1.1.6: AI workflow orchestration
- LangChain 1.2.15: LLM abstractions & tools
- Streamlit 1.48: Web UI framework
- SQLite + aiosqlite: Persistent storage
- Google Generative AI: Gemini 2.5 Flash Lite LLM + embedding model
- FAISS: Vector similarity search (Facebook AI Similarity Search)
- Sentence Transformers: Embeddings pipeline
- LangSmith: AI application debugging, testing, and monitoring
- LangSmith Traces: Real-time execution tracking
- LangSmith Metrics: Performance and cost analytics
- PyPDF: PDF text extraction
- LangChain Text Splitters: Semantic chunking (1000 chars, 200 overlap)
- HuggingFace Hub: Model downloading
- DuckDuckGo Search (ddgs 9.13.0): Web search
- Alpha Vantage API: Stock price data
- MCP Protocol (fastmcp 3.2.0): Standard tool interface
- Model Context Protocol (mcp 1.27.0): Protocol support
- Python 3.12.2: Language version
- Virtual Environment (venv): Dependency isolation
Aria/
├── frontend.py # Streamlit UI (300+ lines)
│ ├── Sidebar: Thread management, PDF upload
│ ├── Main: Chat interface, features showcase
│ └── CSS: Custom styling
│
├── langgraph_backend.py # Core orchestration (700+ lines)
│ ├── Environment Setup: LangSmith configuration
│ ├── LangSmith Integration: Trace creation & metrics
│ ├── Event Loop Setup: Thread-safe async handling
│ ├── PDF Processing: Ingestion → Chunking → Embedding
│ ├── Tool Definitions: 7 integrated tools
│ ├── MCP Client: Local expense tracker
│ ├── Chat Node: LLM invocation + tool binding
│ ├── Graph Construction: StateGraph + edges
│ └── Helper Functions: Retrievers, metadata tracking
│
├── chatbot.db # SQLite database
│ └─ Persisted conversation history across threads
│
├── LANGSMITH_FIX.md # LangSmith detailed guide
├── README.md # Documentation (you are here!)
├── LICENSE # MIT License
└── requirements.txt # Dependencies
- Python 3.12+
- pip or conda
- 2GB+ free disk space (for embeddings models)
- Clone repository:
git clone <repo-url>
cd aria-chatbot- Create virtual environment:
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\Activate.ps1- Install dependencies:
pip install -r requirements.txtRequired packages:
langchain==1.2.15
langchain-core==1.2.27
langchain-community==0.4.1
langgraph==1.1.6
langgraph-checkpoint-sqlite==2.0.11
langsmith>=0.1.0
google-generativelanguage==0.6.18
langchain-google-genai==2.1.9
streamlit==1.48.0
PyPDF==6.9.2
faiss-cpu==1.8.0
sentence-transformers==5.3.0
ddgs==9.13.0
aiosqlite==0.21.0
- Set environment variables:
export GOOGLE_API_KEY="your-gemini-api-key-here"
export LANGCHAIN_TRACING_V2=true
export LANGCHAIN_PROJECT="Advance LangGraph project"
export LANGCHAIN_API_KEY="your-langsmith-api-key-here"- Run the application:
streamlit run frontend.pyApp will open at: http://localhost:8505
- Type a message in the chat input
- Aria analyzes which tools to use
- Tools execute (search, calculate, retrieve docs, etc.)
- Response streams in real-time
- Click "Document Management" in sidebar
- Drag & drop PDF or click "Browse files"
- Watch as Aria chunks and indexes the document
- Ask questions about the document content
- Click "Thread ID" to see current thread
- Click "🔄" button to start new chat
- View past conversations in "Conversation History"
- Click on any past chat to resume it
- Ask: "Add $50 on groceries on March 30, 2026"
- Ask: "What did I spend on"
- Ask: "Summarize my expenses this month"
- Ask: "Search for latest news on AI"
- Ask: "What's the current price of AAPL?"
- Ask: "Calculate 123 * 456"
Example: Complex Query
User Query: "Based on my documents, find instances where the methodology aligns with recent AI trends"
Aria's Decision Process:
1. Parse Query
↓
2. Route Decision:
- Needs RAG? Yes (document analysis)
- Needs Web Search? Yes (recent trends)
- Needs Calculation? No
- Needs Expense Tracking? No
↓
3. Execute in Parallel:
- RAG: Retrieve methodology sections
- Search: Get latest AI trends
- Wait for both results
↓
4. Reasoning Phase:
- LLM receives: doc chunks + search results
- LLM analyzes: alignment between them
- LLM generates: coherent answer with sources
↓
5. Stream Response:
"Based on your documents + recent trends..."
Sequential vs Parallel:
# If tool A depends on tool B output: Sequential
tool_b_result = tool_b(params)
tool_a_result = tool_a(tool_b_result)
# If tools are independent: Parallel
results = await asyncio.gather(
tool_a(params_a),
tool_b(params_b),
tool_c(params_c)
)Why MCP matters for Aria:
- Extensibility: Add expense tracking, weather API, DB query tool, etc.
- Isolation: Tools crash independently, don't crash main app
- Security: Tools run in sandboxed processes
- Standardization: Same interface for local + remote tools
Example: Adding a New MCP Tool
# Create new MCP server (external process)
class WeatherMCPServer:
def get_weather(self, city: str) -> str:
return f"Weather in {city}: 72°F, Clear"
# Register in Aria
mcp_client.add_tool(
name="weather",
description="Get current weather",
function=get_weather
)
# Now users can: "What's the weather in NYC?"| Operation | Time | Notes |
|---|---|---|
| LLM Response | 2-5s | Streaming token-by-token |
| PDF Indexing (100 pages) | 3-8s | One-time, then instant retrieval |
| RAG Retrieval | 50-200ms | FAISS vector search |
| Tool Execution | 500ms-2s | Depends on tool type |
| Database Query | <10ms | SQLite in-process |
Solution:
pip install -r requirements.txt
source venv/bin/activateSolution: (Non-critical warning, doesn't affect functionality)
# This is from transformers library optional dependencies
# Safe to ignore - only needed for vision modelsSolution:
# Set environment variable
export GOOGLE_API_KEY="your-key"
# Or add to .env file
GOOGLE_API_KEY=your-keySolution:
streamlit run frontend.py --server.port 8506LangSmith is integrated into Aria as the primary observability platform. It provides:
- Real-time tracing of every chat interaction
- Performance metrics and latency analysis
- Tool usage analytics and routing decisions
- Error tracking and debugging capabilities
- Cost estimation via token counting
Step 1: Sign up at https://smith.langchain.com
Step 2: Create API Key
Settings → API Keys → Create New Key
Step 3: Add to .env
LANGCHAIN_TRACING_V2=true
LANGCHAIN_PROJECT=Advance LangGraph project
LANGCHAIN_API_KEY=<paste-your-api-key-here>
LANGCHAIN_ENDPOINT=https://api.smith.langchain.comStep 4: Restart the app
streamlit run frontend.pyStep 5: Check connection In the Streamlit app, you should see: ✅ "LangSmith Tracing Active" in the main content area
View Real-time Traces:
- Open LangSmith dashboard: https://smith.langchain.com/projects
- Select project: "Advance LangGraph project"
- Click "Traces" tab
- You'll see traces appearing as users chat
Trace Anatomy:
Each trace contains:
Trace ID: unique-identifier
├─ Name: "Chat Turn"
├─ Input: {user_message, thread_id}
├─ Duration: 2.3 seconds
├─ Status: Success ✓
└─ Output: {response, tools_used, length}
Access Metrics:
- LangSmith Dashboard → Project → "Metrics" tab
- See aggregated data:
- Total interactions
- Average response time
- Tool usage distribution
- Error rates
Sample Metrics:
Today's Activity:
├─ Total Chat Turns: 42
├─ Avg Response Time: 2.1s
├─ Tools Used: {rag_tool: 15, search: 12, calculator: 8, stock_price: 7}
├─ Error Rate: 0.5% (1 failure)
└─ Total Tokens: ~18,500
When Something Goes Wrong:
- LangSmith shows error details:
Status: Error ❌
Error Message: "RAG tool - No document found"
Error Type: ValueError
Stack Trace: (full Python trace)
-
Click on trace to see:
- Exact prompts sent to LLM
- Tool parameters
- Error location in code
-
Understand why it failed:
- User didn't upload document
- Document wasn't indexed
- Query malformed
- etc.
1. Performance Optimization
Q: "Which feature is slowest?"
A: Look at "Tool Execution" trace
→ RAG retrievals take 800ms on average
→ Consider caching or chunking optimization
2. Feature Adoption
Q: "Which tools do users rely on most?"
A: Check metrics tab
→ RAG tool: 60% of interactions
→ Calculator: 20%
→ Web search: 15%
→ Expense tracking: 5%
3. Cost Analysis
Q: "How much are we spending on LLM calls?"
A: Tokens per interaction × Model pricing
→ 1000 interactions × 1200 tokens avg
→ Cost estimate: ~$0.50/day with Gemini
4. Quality Assurance
Q: "Is the agent routing correctly?"
A: Look at tool decision patterns
→ Document questions → RAG tool 95% ✓
→ Math questions → Calculator 90% ✓
→ News questions → Web search 85% ✓
Issue: "No traces appearing"
Check:
- API key is valid (test in LangSmith UI)
.envfile has correct keyLANGCHAIN_TRACING_V2=trueis set- Restart Streamlit app after adding key
- Check browser console for errors
Issue: "Connection refused"
Check:
- Internet connectivity
- Firewall not blocking smith.langchain.com
- VPN not interfering
- API endpoint is correct
Issue: "Partial traces"
Possible causes:
- Tool execution taking too long (timeout)
- Large token outputs
- Streaming being interrupted
Solutions:
- Check backend logs for exceptions
- Verify tool implementations
- Increase timeout if needed
You can log custom metrics to LangSmith:
# In langgraph_backend.py
log_to_langsmith("custom_metric", {
"value": 42,
"category": "feature_usage"
}, tags={
"user_id": thread_id,
"feature": "my_feature"
})These appear in LangSmith as custom data points.
Add tracing to new functions:
from langsmith import traceable
@traceable(name="My Feature", tags=["custom", "feature"])
def my_new_feature():
# This function is now traced
result = do_something()
return result- LangSmith Docs: https://docs.smith.langchain.com
- API Reference: https://docs.smith.langchain.com/reference
- Pricing: https://smith.langchain.com/pricing
- Support: support@langsmith.com
For detailed LangSmith architecture and maintenance guide, see LANGSMITH_FIX.md.
- ✅ Native integration with LangChain/LangGraph (zero setup)
- ✅ Real-time trace visualization (see what LLM does)
- ✅ Tool routing analysis (understand agent decisions)
- ✅ Error capture and debugging
- ✅ Cost estimation via token counting
- ✅ Team collaboration features
- ✅ Free tier available for development
- ✅ Simpler deployment (no external DB)
- ✅ Sufficient for single-machine use
- ✅ Easy to migrate later
- ✅ Built-in with LangGraph
- ✅ Open source, no vendor lock-in
- ✅ Free, no API costs
- ✅ Fast local similarity search
- ✅ Perfect for demo/MVP
- ✅ More reliable (no network issues)
- ✅ Lower latency
- ✅ No 401 auth issues
- ✅ Data stays local
- ✅ LangSmith Integration - Complete observability (IMPLEMENTED)
- Web scraping tool for real-time data
- Multi-document comparison across all uploaded PDFs
- Conversation export (JSON, PDF)
- User authentication & multi-user support
- Custom model fine-tuning
- Advanced caching for repeated queries
- Vector store cloud sync to Pinecone
- Webhook integrations (Slack, Discord)
- LangSmith custom dashboards for team analytics
Contributions are welcome! Areas:
- New tool integrations (finance APIs, databases, etc.)
- UI/UX improvements
- Performance optimizations
- Documentation enhancements
- Bug fixes
MIT License - See LICENSE file
Built with:
- LangGraph & LangChain by LangSmith
- Streamlit by Streamlit Inc
- Google Generative AI
- Faiss by Facebook AI Research
Made with ❤️ by AI Enthusiasts