Skip to content
Open
Show file tree
Hide file tree
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
67 changes: 67 additions & 0 deletions agent/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# =============================================================================
# Docker Ignore - CareerSim Agent
# =============================================================================

# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
*.egg-info/
*.egg
.eggs/
dist/
build/
*.manifest
*.spec

# Virtual environments
.venv/
venv/
ENV/
env/

# IDE
.idea/
.vscode/
*.swp
*.swo
*~

# Testing
.pytest_cache/
.coverage
htmlcov/
.tox/
.nox/

# Logs
logs/
*.log

# Local environment
.env
.env.local
.env.*.local

# HuggingFace cache (will be mounted as volume)
.cache/

# Git
.git/
.gitignore

# Docker
Dockerfile
docker-compose*.yml
.docker/

# Documentation
*.md
!README.md
docs/

# Misc
.DS_Store
Thumbs.db
83 changes: 83 additions & 0 deletions agent/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# =============================================================================
# CareerSim Agent Configuration
# =============================================================================
# Copy this file to .env and fill in your values:
# cp .env.example .env
# =============================================================================

# -----------------------------------------------------------------------------
# OpenAI API Configuration (REQUIRED)
# -----------------------------------------------------------------------------

# Your OpenAI API key (required for AI responses)
OPENAI_API_KEY=your-openai-api-key

# Base URL for OpenAI-compatible API
# Use https://api.openai.com/v1 for OpenAI direct
# Use https://openrouter.ai/api/v1 for OpenRouter
OPENAI_BASE_URL=https://openrouter.ai/api/v1

# Model to use for conversation generation
# OpenAI: gpt-4o, gpt-4o-mini, gpt-4-turbo, gpt-3.5-turbo
# OpenRouter: openai/gpt-4o, anthropic/claude-3.5-sonnet, etc.
OPENAI_MODEL=openai/gpt-5.2

# Provider name (openai, anthropic, google, etc.)
OPENAI_PROVIDER=openai

# Maximum tokens in AI response
OPENAI_MAX_TOKENS=250000

# Temperature for response generation (0.0 = deterministic, 1.0 = creative)
OPENAI_TEMPERATURE=0.7

# Top-p (nucleus sampling) - alternative to temperature
OPENAI_TOP_P=1.0

# Frequency penalty (0.0 - 2.0) - reduces repetition of token sequences
OPENAI_FREQUENCY_PENALTY=0

# Presence penalty (0.0 - 2.0) - encourages new topics
OPENAI_PRESENCE_PENALTY=0

# -----------------------------------------------------------------------------
# Evaluation Model Configuration (Optional)
# Used for goal evaluation - can use a different/cheaper model
# -----------------------------------------------------------------------------

# Model for evaluation tasks (defaults to main model if not set)
OPENAI_EVAL_MODEL=google/gemini-2.5-flash

# Provider for evaluation model
OPENAI_EVAL_PROVIDER=google

OPENAI_EVAL_MAX_TOKENS=250000

# Lower temperature for more consistent evaluations
OPENAI_EVAL_TEMPERATURE=0.3
OPENAI_EVAL_TOP_P=1.0

# Penalties for evaluation model
OPENAI_EVAL_FREQUENCY_PENALTY=0.3
OPENAI_EVAL_PRESENCE_PENALTY=0.3

# -----------------------------------------------------------------------------
# Gradio UI Configuration
# -----------------------------------------------------------------------------

# Port for the Gradio web interface
GRADIO_SERVER_PORT=7860

# Set to true to create a public share link (useful for remote access)
GRADIO_SHARE=false

# -----------------------------------------------------------------------------
# Application Settings
# -----------------------------------------------------------------------------

# Logging level: DEBUG, INFO, WARNING, ERROR
LOG_LEVEL=DEBUG

# Skip preloading HuggingFace models at startup (faster startup, slower first request)
# Set to 1/true to skip, 0/false to preload
SKIP_PRELOAD=false
85 changes: 85 additions & 0 deletions agent/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# =============================================================================
# CareerSim Agent - Core Microservice
# =============================================================================
# Multi-stage Dockerfile for the LangGraph conversation agent with Gradio API
# Connect from backend using gradio_client
# =============================================================================

# -----------------------------------------------------------------------------
# Stage 1: Builder - Install dependencies
# -----------------------------------------------------------------------------
FROM python:3.11-slim AS builder

WORKDIR /app

# Install build dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
&& rm -rf /var/lib/apt/lists/*

# Install uv for faster dependency installation
RUN pip install --no-cache-dir uv

# Copy dependency files
COPY pyproject.toml ./

# Create virtual environment and install dependencies
RUN uv venv /app/.venv
ENV PATH="/app/.venv/bin:$PATH"
RUN uv pip install --no-cache .
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docker builder stage fails without source code

High Severity

The builder stage copies only pyproject.toml then runs uv pip install --no-cache ., which invokes the hatchling build backend. Hatchling requires the src/careersim_agent directory (specified via packages in pyproject.toml) to build the wheel, but source code isn't copied until the runtime stage. This causes the Docker build to fail with a hatchling error about missing package files. The intent was to pre-install dependencies for layer caching, but uv pip install . tries to build the whole project, not just its dependencies.

Fix in Cursor Fix in Web


# -----------------------------------------------------------------------------
# Stage 2: Runtime - Final image
# -----------------------------------------------------------------------------
FROM python:3.11-slim AS runtime

WORKDIR /app

# Install runtime dependencies (for torch/transformers)
RUN apt-get update && apt-get install -y --no-install-recommends \
libgomp1 \
&& rm -rf /var/lib/apt/lists/*

# Create non-root user for security
RUN useradd --create-home --shell /bin/bash appuser

# Copy virtual environment from builder
COPY --from=builder /app/.venv /app/.venv

# Set environment variables
ENV PATH="/app/.venv/bin:$PATH"
ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1

# HuggingFace cache directory (will be mounted as volume)
ENV HF_HOME=/app/.cache/huggingface
ENV TRANSFORMERS_CACHE=/app/.cache/huggingface

# Gradio configuration for API access
ENV GRADIO_SERVER_NAME=0.0.0.0
ENV GRADIO_SERVER_PORT=7860

# Copy application code
COPY --chown=appuser:appuser src/ ./src/
COPY --chown=appuser:appuser data/ ./data/
COPY --chown=appuser:appuser pyproject.toml ./

# Install package in editable mode
RUN pip install --no-cache-dir -e .

# Create cache directory with correct permissions
RUN mkdir -p /app/.cache/huggingface && chown -R appuser:appuser /app/.cache
RUN mkdir -p /app/logs && chown -R appuser:appuser /app/logs

# Switch to non-root user
USER appuser

# Expose Gradio port
EXPOSE 7860

# Health check - Gradio provides a /info endpoint
HEALTHCHECK --interval=30s --timeout=10s --retries=5 --start-period=120s \
CMD python -c "import httpx; r = httpx.get('http://localhost:7860/', timeout=5.0); exit(0 if r.status_code == 200 else 1)"

# Default command - run the agent
CMD ["python", "-m", "careersim_agent.main"]
158 changes: 158 additions & 0 deletions agent/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# CareerSim Agent

A standalone Python LangGraph agent with a Gradio developer UI for career simulation conversations.

## Features

- **LangGraph-based conversation flow** with persona-driven AI responses
- **Local HuggingFace transformers** for sentiment and emotion analysis
- **Goal evaluation** using zero-shot classification
- **Gradio developer console** with:
- Real-time state inspection
- Node execution tracing
- Goal progress dashboard
- Manual trigger buttons (inactivity, followup)
- **API endpoints** for programmatic access via `gradio_client`

## Quick Start

### Local Development

```bash
# Create and activate virtual environment
python3 -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate

# Install dependencies
pip install -e .

# Or using uv (faster)
uv venv
source .venv/bin/activate
uv sync

# Copy and configure environment
cp .env.example .env
# Edit .env and add your OPENAI_API_KEY

# Run the agent
python -m careersim_agent.main
```

The Gradio UI will open at http://localhost:7860

> **Note**: First run will download HuggingFace models (~500MB). Set `SKIP_PRELOAD=1` to defer this.

### Docker (as Core Microservice)

Run the agent as part of the cluster:

```bash
# From the project root
docker-compose -f docker-compose.local.yml up core

# Or with the full stack
docker-compose -f docker-compose.local.yml up
```

The service will be available at:
- **Internal (for backend)**: `http://core:7860`
- **External (for dev)**: `http://localhost:7860`

## API Usage (gradio_client)

Connect from the backend using `gradio_client`:

```python
from gradio_client import Client

# Connect to the core service
client = Client("http://core:7860") # or http://localhost:7860

# List available simulations
result = client.predict(api_name="/api_list_simulations")
print(result["simulations"])

# Start a session
result = client.predict(
simulation_slug="behavioral-interview-brenda",
api_name="/api_start_session"
)
session_id = result["session_id"]
print(f"Started session: {session_id}")
print(f"Initial message: {result['messages']}")

# Send a message
result = client.predict(
session_id=session_id,
message="Hello, I'm ready for my interview.",
api_name="/api_send_message"
)
print(f"AI response: {result['messages'][-1]}")
print(f"Goal progress: {result['goal_progress']}")

# Trigger proactive message (inactivity, followup)
result = client.predict(
session_id=session_id,
trigger_type="followup",
api_name="/api_trigger_proactive"
)

# Get full session state
result = client.predict(
session_id=session_id,
api_name="/api_get_session_state"
)

# End session
result = client.predict(
session_id=session_id,
api_name="/api_end_session"
)
```

### API Endpoints

| Endpoint | Description |
|----------|-------------|
| `/api_list_simulations` | List all available simulations |
| `/api_start_session` | Start a new conversation session |
| `/api_send_message` | Send a user message and get AI response |
| `/api_trigger_proactive` | Trigger proactive message (start/inactivity/followup) |
| `/api_get_session_state` | Get full session state |
| `/api_end_session` | End and cleanup a session |

## Project Structure

```
agent/
├── data/
│ ├── personas.json # Persona definitions
│ └── simulations.json # Simulation + goals
├── src/careersim_agent/
│ ├── main.py # Entry point
│ ├── config.py # Settings
│ ├── graph/ # LangGraph components
│ │ ├── state.py # State schema
│ │ ├── builder.py # Graph construction
│ │ └── nodes/ # Node implementations
│ ├── prompts/ # Prompt templates
│ ├── services/ # Data loader, transformers
│ └── ui/ # Gradio interface
└── tests/
```

## Configuration

Edit `data/personas.json` and `data/simulations.json` to customize personas and scenarios.

## Development

This is a lab/experimental environment designed for rapid iteration.
It can evolve into a production microservice.

Key simplifications vs production:
- JSON files instead of database
- In-memory state (no checkpointing)
- Manual triggers instead of background schedulers
- Local transformers instead of external service
Loading