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
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
156 changes: 156 additions & 0 deletions agent-genie-app/agent_genie/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import os
import json
import requests
from flask import Flask, render_template, request, jsonify
from databricks.sdk.core import Config
from databricks.sdk import WorkspaceClient
from databricks.sdk.service.serving import ChatMessage, ChatMessageRole

# Initialize Flask app
app = Flask(__name__, static_folder='static', template_folder='static')

cfg = Config()
client = WorkspaceClient()

# Model serving endpoint name
ENDPOINT_NAME = "agents_ws_vfc_demo-sch_vfc_demo-fashion_merchandising_agent"

@app.route('/')
def index():
"""Serve the main HTML page"""
return render_template('index.html')

@app.route('/api/chat', methods=['POST'])
def chat():
"""Handle chat requests and forward to model serving endpoint using Databricks SDK"""
try:
# Get the message history from the request
data = request.get_json()
messages = data.get('messages', [])

if not messages:
return jsonify({'error': 'No messages provided'}), 400

# Convert messages to ChatMessage objects
chat_messages = []
for msg in messages:
role = msg.get('role', 'user')
content = msg.get('content', '')

# Map roles to ChatMessageRole enum
if role == 'user':
chat_role = ChatMessageRole.USER
elif role == 'assistant':
chat_role = ChatMessageRole.ASSISTANT
elif role == 'system':
chat_role = ChatMessageRole.SYSTEM
else:
chat_role = ChatMessageRole.USER # Default to user

chat_messages.append(ChatMessage(role=chat_role, content=content))

# Get user information for logging
user_email = request.headers.get('X-Forwarded-Email')
app.logger.info(f"Making request to model endpoint for user: {user_email}")

# Make request to model serving endpoint using Databricks SDK
response = client.serving_endpoints.query(
name=ENDPOINT_NAME,
messages=chat_messages
)

# Extract the response content
if response and hasattr(response, 'choices') and response.choices:
# Get the first choice
choice = response.choices[0]
if hasattr(choice, 'message') and choice.message:
result_content = choice.message.content
return jsonify({'content': result_content})
else:
return jsonify({'error': 'No message content in response'}), 500
else:
return jsonify({'error': 'No response choices received'}), 500

except Exception as e:
error_msg = str(e)
app.logger.error(f"Error calling model endpoint: {error_msg}")

# Handle specific error types
if "authentication" in error_msg.lower() or "unauthorized" in error_msg.lower():
return jsonify({
'error': 'Authentication failed. Your Databricks token may have expired.',
'details': 'Please refresh the page or log in again.'
}), 401
elif "permission" in error_msg.lower() or "forbidden" in error_msg.lower():
return jsonify({
'error': 'Access denied. You may not have permission to access this model endpoint.',
'details': 'Please contact your Databricks administrator.'
}), 403
elif "timeout" in error_msg.lower():
return jsonify({'error': 'Request timed out. Please try again.'}), 504
else:
return jsonify({
'error': f'Unexpected error: {error_msg}',
'details': 'Please try again or contact support if the issue persists.'
}), 500

@app.route('/health')
def health():
"""Health check endpoint"""
user_email = request.headers.get('X-Forwarded-Email')

try:
# Test the client connection
client.current_user.me()
client_healthy = True
except Exception as e:
client_healthy = False
app.logger.error(f"Client health check failed: {str(e)}")

return jsonify({
'status': 'healthy',
'client_authenticated': client_healthy,
'user_email': user_email if user_email else 'anonymous',
'endpoint_name': ENDPOINT_NAME
})

@app.route('/api/debug')
def debug():
"""Debug endpoint to check authentication and endpoint status"""
headers_info = {
'X-Forwarded-Access-Token': 'present' if request.headers.get('X-Forwarded-Access-Token') else 'missing',
'X-Forwarded-Email': request.headers.get('X-Forwarded-Email', 'not provided'),
'User-Agent': request.headers.get('User-Agent', 'not provided'),
'Authorization': 'present' if request.headers.get('Authorization') else 'missing'
}

try:
# Check if we can access the serving endpoint
endpoint_info = client.serving_endpoints.get(name=ENDPOINT_NAME)
endpoint_status = endpoint_info.state.value if endpoint_info.state else 'unknown'
except Exception as e:
endpoint_status = f'error: {str(e)}'

try:
# Check current user
current_user = client.current_user.me()
user_info = current_user.user_name if current_user else 'unknown'
except Exception as e:
user_info = f'error: {str(e)}'

return jsonify({
'message': 'Debug information for Databricks App',
'headers': headers_info,
'endpoint_name': ENDPOINT_NAME,
'endpoint_status': endpoint_status,
'current_user': user_info,
'sdk_config': {
'host': cfg.host if hasattr(cfg, 'host') else 'not set',
'auth_type': cfg.auth_type if hasattr(cfg, 'auth_type') else 'not set'
}
})

if __name__ == '__main__':
# Get port from environment variable or default to 8080
port = int(os.environ.get('PORT', 8080))
app.run(host='0.0.0.0', port=port, debug=False)
15 changes: 15 additions & 0 deletions agent-genie-app/agent_genie/app.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
command: [
"uvicorn",
"app:app",
"--host",
"127.0.0.1",
"--port",
"8000"
]
env:
- name: "SPACE_ID"
value: "01f03de6df76113387ccc36242e6c804"
- name: "SERVING_ENDPOINT_NAME"
value: "databricks-gpt-oss-120b"
- name: "TAVILY_API_KEY"
value: "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"
95 changes: 95 additions & 0 deletions agent-genie-app/agent_genie/databricks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
bundle:
name: agent-genie-app

variables:
project_name:
description: "Display name for the app"
default: "agent-genie"

# Env-only fallback; will also be persisted to a secret by the installer job if possible
tavily_api_key:
description: "Tavily API key (installer job will try to store in a secret scope)"
default: ""

# Where to persist the secret (customize if you already have a scope)
secret_scope:
description: "Workspace secret scope to store Tavily key"
default: "agent_genie_secrets"

secret_key:
description: "Secret key name for Tavily key inside the scope"
default: "TAVILY_API_KEY"

environment:
description: "Deployment environment (dev, staging, prod)"
default: "dev"

targets:
dev:
default: true
mode: development

resources:
apps:
agent_genie:
name: "${var.project_name}"
description: "FastAPI app with Genie + Serving Endpoint integration"
source_code_path: "."

command: [
"uvicorn",
"app:app",
"--host",
"127.0.0.1",
"--port",
"8000"
]

# Inject env for your app at runtime
env:
- name: "SPACE_ID"
value_from: "genie-space"
- name: "SERVING_ENDPOINT_NAME"
value_from: "serving-endpoint"
- name: "TAVILY_API_KEY"
value: "${var.tavily_api_key}" # app can use this directly; secret is optional hardening

# --- App Resources (end-user picks these at install) ---
app_resources:
- key: "serving-endpoint"
serving_endpoint_spec:
permission: "CAN_QUERY"
- key: "genie-space"
genie_space_spec:
permission: "CAN_RUN"

# --- User Authorized Scopes (Preview) ---
user_authorized_scopes:
- "sql"
- "dashboards.genie"
- "files.files"
- "serving.serving-endpoints"
- "vectorsearch.vector-search-indexes"
- "catalog.connections"

# --- Installer job to persist Tavily key into a secret and write optional config ---
jobs:
install_app:
name: "${var.project_name} - Install/Configure"
tasks:
- task_key: configure_app
notebook_task:
notebook_path: "./notebooks/setup_app" # create this notebook
base_parameters:
TAVILY_API_KEY: "${var.tavily_api_key}"
SECRET_SCOPE: "${var.secret_scope}"
SECRET_KEY: "${var.secret_key}"
# Add compute for your workspace (example placeholders):
# existing_cluster_id: "<your-cluster-id>"
# OR:
# job_clusters:
# - job_cluster_key: "install_cluster"
# new_cluster:
# spark_version: "14.3.x-scala2.12"
# node_type_id: "i3.xlarge"
# num_workers: 0
Loading
Loading