A FastAPI-based backend that accepts a company name, gathers financial data and news, and returns an Invest or Pass decision with detailed reasoning powered by an LLM (OpenAI or Groq).
Client → POST /auth/signup ──→ JWT token
Client → POST /auth/login ──→ JWT token
Client → POST /research (JWT Bearer)
│
├── WebSearchTool (DuckDuckGo) → news & web data
├── FinancialDataTool (yfinance) → price, P/E, market cap, etc.
│
└── LLM (OpenAI / Groq) → structured JSON decision
│
└── Response: {decision, reasoning, supporting_data}
The system uses a linear pipeline:
- Auth — user signs up or logs in to receive a JWT token.
- Data gathering — two tools run in parallel (web search + yfinance).
- LLM analysis — collected data is injected into a prompt that instructs a chat model (OpenAI GPT-3.5-turbo or Groq Llama 3 70B) to act as an investment analyst and produce a structured JSON output.
- Validation — the JSON is parsed and validated against a Pydantic model before being returned.
- Python 3.10+
- An API key for OpenAI (gpt-3.5-turbo) or Groq (llama3-70b-8192 — free tier available)
- Internet access for DuckDuckGo search and yfinance
# 1. Clone / copy the project
cd ai-investment-agent
# 2. Create virtual environment
python -m venv venv
source venv/bin/activate
# 3. Install dependencies
pip install -r requirements.txt
# 4. Configure environment
cp .env.example .env
# Edit .env with your API keys and change JWT_SECRET
# 5. Run the server
uvicorn main:app --host 0.0.0.0 --port 8000| Variable | Default | Description |
|---|---|---|
LLM_PROVIDER |
openai |
"openai" or "groq" |
OPENAI_API_KEY |
— | Your OpenAI API key |
GROQ_API_KEY |
— | Your Groq API key |
LLM_MODEL_NAME |
gpt-3.5-turbo |
Model name (see provider docs) |
MAX_SEARCH_RESULTS |
5 |
Max DuckDuckGo search results |
LOG_LEVEL |
INFO |
INFO or DEBUG |
JWT_SECRET |
— | Secret key for signing JWT tokens (change to a random string) |
JWT_ALGORITHM |
HS256 |
JWT signing algorithm |
JWT_EXPIRE_MINUTES |
1440 |
Token expiry in minutes (24h) |
GET /health
No auth required.
curl http://localhost:8000/health{"status": "ok"}POST /auth/signup
No auth required. Creates a new user and returns a JWT token.
curl -X POST http://localhost:8000/auth/signup \
-H "Content-Type: application/json" \
-d '{"email": "user@example.com", "password": "your-password"}'201 Created
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "bearer"
}409 Conflict — email already registered
{"detail": "Email already registered"}422 Unprocessable — validation error (e.g., invalid email)
{
"detail": [
{
"type": "value_error",
"loc": ["body", "email"],
"msg": "value is not a valid email address"
}
]
}POST /auth/login
No auth required. Authenticates and returns a JWT token.
curl -X POST http://localhost:8000/auth/login \
-H "Content-Type: application/json" \
-d '{"email": "user@example.com", "password": "your-password"}'200 OK
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "bearer"
}401 Unauthorized — wrong email or password
{"detail": "Invalid email or password"}POST /research
Requires Authorization: Bearer <token> header.
curl -X POST http://localhost:8000/research \
-H "Content-Type: application/json" \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
-d '{"company_name": "Apple"}'200 OK
{
"decision": "Pass",
"reasoning": "Apple remains a fundamentally strong company...",
"supporting_data": {
"financial_metrics": {
"current_price": 283.78,
"market_cap": 4167977926656,
"pe_ratio": 34.36,
"fifty_two_week_high": 317.4,
"fifty_two_week_low": 199.26,
"revenue_growth": 0.166,
"short_ratio": 2.88,
"dividend_yield": 0.38
},
"search_summary": "Title: Apple Inc. (AAPL) Stock Price, News..."
},
"error": null
}401 Unauthorized — missing or invalid token
{"detail": "Not authenticated"}- Signup —
POST /auth/signupwith email + password → returns JWT token. - Login —
POST /auth/loginwith email + password → returns JWT token (same format). - Use — pass the token in the
Authorization: Bearer <token>header for/research. - Expiry — tokens expire after
JWT_EXPIRE_MINUTES(default 24h). Login again to get a new one. - Storage — user credentials are stored hashed in
users.db(SQLite).
├── .env.example # Environment variable template
├── requirements.txt # Python dependencies
├── README.md # This file
├── main.py # FastAPI app, CORS, auth + research endpoints
├── core/
│ ├── __init__.py
│ ├── config.py # Pydantic Settings from env vars
│ ├── database.py # Async SQLite — user table, CRUD helpers
│ ├── auth.py # JWT create/verify, password hashing, auth dependency
│ ├── agent.py # LangChain chain: tools → LLM → JSON
│ ├── tools.py # @tool: web_search_tool, financial_data_tool
│ ├── models.py # Pydantic models (request/responses + auth schemas)
│ └── utils.py # Logging setup
└── tests/
├── __init__.py
└── test_dummy.py # Placeholder
| Decision | Rationale |
|---|---|
| DuckDuckGo (free) instead of Google/Bing API | No API key needed, though results are less comprehensive. |
| yfinance (free) instead of Bloomberg/Alpha Vantage | Simple, zero-cost stock data. May be rate-limited for heavy use. |
| SQLite instead of PostgreSQL | Zero setup, file-based — ideal for a VPS. Swap to Postgres for scale. |
| Simple linear pipeline instead of a ReAct agent loop | Easier to debug and maintain; avoids extra LLM calls. |
| Partial data mode | If a tool fails, the LLM still produces a decision with available data rather than returning a 500 error. |
- Add caching (e.g., Redis) for repeated company lookups.
- Integrate SEC filings (Edgar API) for fundamental analysis.
- Use a ReAct agent for multi-step reasoning (e.g., look up ticker, then fetch data).
- Add sentiment analysis on news headlines using a local model.
- Add rate limiting for production deployment.
- Deploy with Docker and a reverse proxy (nginx / Caddy).