import modal import openai from fastapi import Depends, FastAPI, status from fastapi.middleware.cors import CORSMiddleware from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer from modal import Image, Stub from openai import OpenAI from pydantic import BaseModel, Field from pydantic_settings import BaseSettings, SettingsConfigDict stub = Stub() auth_scheme = HTTPBearer() image = Image.debian_slim().pip_install_from_pyproject("pyproject.toml") class Settings(BaseSettings): openai_api_key: str erato_token: str model_config = SettingsConfigDict(env_file=".env") settings = Settings() openai.organization = "org-xJUtqr0WhHZ39GSAdXBeH8mt" openai.api_key = settings.openai_api_key app = FastAPI() app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["GET", "POST"], allow_headers=["*"], ) openai_client = OpenAI(organization=openai.organization, api_key=openai.api_key) DEFAULT_TEMP = 1.0 class CompletionParams(BaseModel): context: str = "" cursor: int | None = None first_word: bool = Field(False, alias="firstWord") max_tokens: int = Field(500, alias="maxTokens") temperature: float = DEFAULT_TEMP tax: float | None = None stub = Stub(name="erato") @app.post("/api/completions") def make_completions( params: CompletionParams, token: HTTPAuthorizationCredentials = Depends(auth_scheme) ): if token.credentials != settings.erato_token: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect token", headers={"WWW-Authenticate": "Bearer"}, ) context = params.context cursor = params.cursor if params.cursor is not None else len(context) first_word = params.first_word max_tokens = params.max_tokens temperature = params.temperature print("max tokens:", max_tokens) SYSTEM_PROMPT = """You're a critically acclaimed songwriter writing a song in collaboration with a musician. You'll be given part of a song and asked to complete a line. The line you'll be asked to complete will be marked with '[LYRICS GO HERE]'. Respond with the text you'd like to replace '[LYRICS GO HERE]'. Only respond with the line, do not copy the whole song or add any preamble. If you wish to start a new word, begin your response with a space.""" num_responses = 10 if first_word else 3 user_prompt = context[:cursor] + "[LYRICS GO HERE]" + context[cursor:] completion = openai_client.chat.completions.create( model="gpt-4", messages=[ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": user_prompt}, ], n=num_responses, max_tokens=max_tokens, temperature=temperature, ) raw_choices = [(c.message.content) for c in completion.choices] print("raw choices:") for rc in raw_choices: print(rc) choices = [_prep_choice(rc) for rc in raw_choices] if first_word: choices = [_prep_choice(c.split()[0]) for c in choices] return {"choices": choices} def _prep_choice(choice: str): if choice.startswith('"'): choice = choice[1:] if choice.endswith('"'): choice = choice[:-1] if not choice.startswith(" "): choice = " " + choice return choice @stub.function( image=image, secrets=[ modal.Secret.from_name("openai-secret"), modal.Secret.from_name("erato-token"), ], ) @modal.asgi_app() def fastapi_app(): return app