-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' into sbosio/ai-models-call
- Loading branch information
Showing
14 changed files
with
682 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import {Args} from '@oclif/core' | ||
import {flags} from '@heroku-cli/command' | ||
import destroyAddon from '../../../lib/ai/models/destroy_addon' | ||
import confirmCommand from '../../../lib/confirmCommand' | ||
import Command from '../../../lib/base' | ||
|
||
export default class Destroy extends Command { | ||
static description = 'destroy an existing AI model resource' | ||
|
||
static flags = { | ||
app: flags.app({required: true, description: 'app to run command against'}), | ||
confirm: flags.string({char: 'c'}), | ||
force: flags.boolean({char: 'f', description: 'allow destruction even if connected to other apps'}), | ||
remote: flags.remote({description: 'git remote of app to use'}), | ||
} | ||
|
||
static args = { | ||
modelResource: Args.string({required: true, description: 'The resource ID or alias of the model resource to destroy.'}), | ||
} | ||
|
||
static examples = [ | ||
'$ heroku ai:models:destroy claude-3-5-sonnet-acute-43973', | ||
] | ||
|
||
public async run(): Promise<void> { | ||
const {flags, args} = await this.parse(Destroy) | ||
const {app, confirm} = flags | ||
const {modelResource} = args | ||
const force = flags.force || process.env.HEROKU_FORCE === '1' | ||
|
||
await this.configureHerokuAIClient(modelResource, app) | ||
|
||
const aiAddon = this.addon | ||
|
||
await confirmCommand(app, confirm) | ||
await destroyAddon(this.config, aiAddon, force) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import color from '@heroku-cli/color' | ||
import {flags} from '@heroku-cli/command' | ||
import {Args, ux} from '@oclif/core' | ||
import * as Heroku from '@heroku-cli/schema' | ||
import Command from '../../../lib/base' | ||
|
||
export default class Detach extends Command { | ||
static description = 'Detach a model resource from an app.' | ||
static flags = { | ||
app: flags.app({description: 'The name of the Heroku app to detach the model resource from.', required: true}), | ||
remote: flags.remote(), | ||
} | ||
|
||
static args = { | ||
model_resource: Args.string({ | ||
description: 'The resource ID or alias of the model resource to detach', | ||
required: true, | ||
}), | ||
} | ||
|
||
static example = 'heroku ai:models:detach claude-3-5-sonnet-acute-41518 --app example-app' | ||
|
||
public async run(): Promise<void> { | ||
const {flags, args} = await this.parse(Detach) | ||
const {app} = flags | ||
const {model_resource: modelResource} = args | ||
|
||
await this.configureHerokuAIClient(modelResource, app) | ||
|
||
const aiAddon = this.addon | ||
|
||
ux.action.start(`Detaching ${color.cyan(aiAddon.name || '')} from ${color.magenta(app)}`) | ||
|
||
await this.heroku.delete(`/addon-attachments/${aiAddon.id}`) | ||
|
||
ux.action.stop() | ||
|
||
ux.action.start(`Unsetting ${color.cyan(aiAddon.name || '')} config vars and restarting ${color.magenta(app)}`) | ||
|
||
const {body: releases} = await this.heroku.get<Heroku.Release[]>(`/apps/${app}/releases`, { | ||
partial: true, headers: {Range: 'version ..; max=1, order=desc'}, | ||
}) | ||
|
||
ux.action.stop(`done, v${releases[0]?.version || ''}`) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
import color from '@heroku-cli/color' | ||
import {flags} from '@heroku-cli/command' | ||
import {Args, ux} from '@oclif/core' | ||
import Command from '../../../lib/base' | ||
import {ModelResource} from '../../../lib/ai/types' | ||
import appAddons from '../../../lib/ai/models/app_addons' | ||
import * as Heroku from '@heroku-cli/schema' | ||
|
||
export default class Info extends Command { | ||
static description = 'get the current status of all the AI model resources attached to your app or a specific resource' | ||
static examples = [ | ||
'heroku ai:models:info claude-3-5-sonnet-acute-04281 --app example-app', | ||
'heroku ai:models:info --app example-app', | ||
] | ||
|
||
static flags = { | ||
app: flags.app({required: true}), | ||
remote: flags.remote(), | ||
} | ||
|
||
static args = { | ||
modelResource: Args.string({description: 'The resource ID or alias of the model resource to check.'}), | ||
} | ||
|
||
public async run(): Promise<any> { | ||
const {args, flags} = await this.parse(Info) | ||
const {app} = flags | ||
const {modelResource} = args | ||
const synthesizedModels: Array<ModelResource> = [] | ||
let listOfProvisionedModels: Array<ModelResource> = [] | ||
|
||
const modelInfo = async () => { | ||
const modelInfoResponse = await this.herokuAI.get<ModelResource>(`/models/${this.apiModelId}`, { | ||
headers: {authorization: `Bearer ${this.apiKey}`}, | ||
}) | ||
.catch(error => { | ||
if (error.statusCode === 404) { | ||
ux.warn(`We can’t find a model resource called ${color.yellow(modelResource)}.\nRun ${color.cmd('heroku ai:models:info -a <app>')} to see a list of model resources.`) | ||
} else { | ||
throw error | ||
} | ||
}) | ||
|
||
return modelInfoResponse | ||
} | ||
|
||
const getModelDetails = async (collectedModels: Array<Heroku.AddOn> | string) => { | ||
if (typeof collectedModels === 'string') { | ||
const modelResource = collectedModels | ||
await this.configureHerokuAIClient(modelResource, app) | ||
|
||
const {body: currentModelResource} = await modelInfo() || {body: null} | ||
synthesizedModels.push(currentModelResource!) | ||
} else { | ||
for (const addonModel of collectedModels) { | ||
await this.configureHerokuAIClient(addonModel.modelResource, app) | ||
|
||
const {body: currentModelResource} = await modelInfo() || {body: null} | ||
synthesizedModels.push(currentModelResource!) | ||
} | ||
} | ||
|
||
return synthesizedModels | ||
} | ||
|
||
if (modelResource) { | ||
listOfProvisionedModels = await getModelDetails(modelResource) | ||
} else { | ||
const provisionedModelsInfo: Record<string, string | undefined>[] = [] | ||
const inferenceRegex = /inference/ | ||
const addonsResponse = await appAddons(this.config, app) | ||
|
||
for (const addonInfo of addonsResponse as Array<Heroku.AddOn>) { | ||
const addonType = addonInfo.addon_service?.name || '' | ||
const isModelAddon = inferenceRegex.test(addonType) | ||
|
||
if (isModelAddon) { | ||
provisionedModelsInfo.push({ | ||
addonName: addonInfo.addon_service?.name, | ||
modelResource: addonInfo.name, | ||
modelId: addonInfo.addon_service?.id, | ||
}) | ||
} | ||
} | ||
|
||
listOfProvisionedModels = await getModelDetails(provisionedModelsInfo) | ||
} | ||
|
||
this.displayModelResource(listOfProvisionedModels) | ||
} | ||
|
||
displayModelResource(modelResources: ModelResource[]) { | ||
for (const modelResource of modelResources) { | ||
ux.log() | ||
ux.styledHeader(modelResource.model_id) | ||
ux.styledObject({ | ||
'Base Model ID': modelResource.model_id, | ||
Ready: modelResource.ready, | ||
'Tokens In': modelResource.tokens_in, | ||
'Tokens Out': modelResource.tokens_out, | ||
'Avg Performance': modelResource.avg_performance, | ||
}) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import {Config} from '@oclif/core' | ||
import {APIClient} from '@heroku-cli/command' | ||
import * as Heroku from '@heroku-cli/schema' | ||
|
||
export default async function (config: Config, app: string) { | ||
const herokuClient = new APIClient(config) | ||
|
||
const {body: response} = await herokuClient.get<Heroku.AddOn>(`/apps/${app}/addons`, { | ||
headers: {'Accept-Expansion': 'plan'}, | ||
}).catch(error => { | ||
console.log('ERROR MESSAGE:', error.message) | ||
const error_ = error.body && error.body.message ? new Error(`The add-on was unable to be destroyed: ${error.body.message}.`) : new Error(`The add-on was unable to be destroyed: ${error}.`) | ||
throw error_ | ||
}) | ||
|
||
return response | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import color from '@heroku-cli/color' | ||
import {ux} from '@oclif/core' | ||
import {Config} from '@oclif/core' | ||
import {APIClient} from '@heroku-cli/command' | ||
import * as Heroku from '@heroku-cli/schema' | ||
|
||
export default async function (config: Config, addon: Heroku.AddOn, force = false) { | ||
const addonName = addon.name || '' | ||
const herokuClient = new APIClient(config) | ||
|
||
ux.action.start(`Destroying ${color.addon(addonName)} in the background.\nThe app will restart when complete...`) | ||
|
||
await herokuClient.delete<Heroku.AddOn>(`/apps/${addon.app?.id}/addons/${addon.id}`, { | ||
headers: {'Accept-Expansion': 'plan'}, | ||
body: {force}, | ||
}).catch(error => { | ||
ux.action.stop('') | ||
const error_ = error.body && error.body.message ? new Error(`The add-on was unable to be destroyed: ${error.body.message}.`) : new Error(`The add-on was unable to be destroyed: ${error}.`) | ||
throw error_ | ||
}) | ||
|
||
ux.action.stop() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.