-
Notifications
You must be signed in to change notification settings - Fork 226
Move automodeling logic out of view #2680
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,171 @@ | ||
| import { ExternalApiUsage, MethodSignature } from "./external-api-usage"; | ||
| import { ModeledMethod } from "./modeled-method"; | ||
| import { extLogger } from "../common/logging/vscode"; | ||
| import { load as loadYaml } from "js-yaml"; | ||
| import { ProgressCallback, withProgress } from "../common/vscode/progress"; | ||
| import { createAutoModelV2Request, getCandidates } from "./auto-model-v2"; | ||
| import { runAutoModelQueries } from "./auto-model-codeml-queries"; | ||
| import { loadDataExtensionYaml } from "./yaml"; | ||
| import { ModelRequest, ModelResponse, autoModelV2 } from "./auto-model-api-v2"; | ||
| import { RequestError } from "@octokit/request-error"; | ||
| import { showAndLogExceptionWithTelemetry } from "../common/logging"; | ||
| import { redactableError } from "../common/errors"; | ||
| import { App } from "../common/app"; | ||
| import { CodeQLCliServer } from "../codeql-cli/cli"; | ||
| import { QueryRunner } from "../query-server"; | ||
| import { DatabaseItem } from "../databases/local-databases"; | ||
| import { Mode } from "./shared/mode"; | ||
|
|
||
| export class AutoModeler { | ||
| constructor( | ||
| private readonly app: App, | ||
| private readonly cliServer: CodeQLCliServer, | ||
| private readonly queryRunner: QueryRunner, | ||
| private readonly queryStorageDir: string, | ||
| private readonly databaseItem: DatabaseItem, | ||
| private readonly addModeledMethods: ( | ||
| modeledMethods: Record<string, ModeledMethod>, | ||
| ) => Promise<void>, | ||
| ) {} | ||
|
|
||
| public async startModeling( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this method to prepare for the future, because it doesnt seem to do much other than just call the other method?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, exactly. It'll be doing a bit more later on. |
||
| externalApiUsages: ExternalApiUsage[], | ||
| modeledMethods: Record<string, ModeledMethod>, | ||
| mode: Mode, | ||
| ): Promise<void> { | ||
| await this.modelDependency(externalApiUsages, modeledMethods, mode); | ||
| } | ||
|
|
||
| private async modelDependency( | ||
| externalApiUsages: ExternalApiUsage[], | ||
| modeledMethods: Record<string, ModeledMethod>, | ||
| mode: Mode, | ||
| ): Promise<void> { | ||
| await withProgress(async (progress) => { | ||
| const maxStep = 3000; | ||
|
|
||
| progress({ | ||
| step: 0, | ||
| maxStep, | ||
| message: "Retrieving usages", | ||
| }); | ||
|
|
||
| // Fetch the candidates to send to the model | ||
| const candidateMethods = getCandidates( | ||
| mode, | ||
| externalApiUsages, | ||
| modeledMethods, | ||
| ); | ||
|
|
||
| // If there are no candidates, there is nothing to model and we just return | ||
| if (candidateMethods.length === 0) { | ||
| void extLogger.log("No candidates to model. Stopping."); | ||
| return; | ||
| } | ||
|
|
||
| await this.modelCandidates(candidateMethods, mode, progress, maxStep); | ||
| }); | ||
| } | ||
|
|
||
| private async modelCandidates( | ||
| candidateMethods: MethodSignature[], | ||
| mode: Mode, | ||
| progress: ProgressCallback, | ||
| maxStep: number, | ||
| ): Promise<void> { | ||
| const usages = await runAutoModelQueries({ | ||
| mode, | ||
| candidateMethods, | ||
| cliServer: this.cliServer, | ||
| queryRunner: this.queryRunner, | ||
| queryStorageDir: this.queryStorageDir, | ||
| databaseItem: this.databaseItem, | ||
| progress: (update) => progress({ ...update, maxStep }), | ||
| }); | ||
| if (!usages) { | ||
| return; | ||
| } | ||
|
|
||
| progress({ | ||
| step: 1800, | ||
| maxStep, | ||
| message: "Creating request", | ||
| }); | ||
|
|
||
| const request = await createAutoModelV2Request(mode, usages); | ||
|
|
||
| progress({ | ||
| step: 2000, | ||
| maxStep, | ||
| message: "Sending request", | ||
| }); | ||
|
|
||
| const response = await this.callAutoModelApi(request); | ||
| if (!response) { | ||
| return; | ||
| } | ||
|
|
||
| progress({ | ||
| step: 2500, | ||
| maxStep, | ||
| message: "Parsing response", | ||
| }); | ||
|
|
||
| const models = loadYaml(response.models, { | ||
| filename: "auto-model.yml", | ||
| }); | ||
|
|
||
| const loadedMethods = loadDataExtensionYaml(models); | ||
| if (!loadedMethods) { | ||
| return; | ||
| } | ||
|
|
||
| // Any candidate that was part of the response is a negative result | ||
| // meaning that the canidate is not a sink for the kinds that the LLM is checking for. | ||
| // For now we model this as a sink neutral method, however this is subject | ||
| // to discussion. | ||
| for (const candidate of candidateMethods) { | ||
| if (!(candidate.signature in loadedMethods)) { | ||
| loadedMethods[candidate.signature] = { | ||
| type: "neutral", | ||
| kind: "sink", | ||
| input: "", | ||
| output: "", | ||
| provenance: "ai-generated", | ||
| signature: candidate.signature, | ||
| packageName: candidate.packageName, | ||
| typeName: candidate.typeName, | ||
| methodName: candidate.methodName, | ||
| methodParameters: candidate.methodParameters, | ||
| }; | ||
| } | ||
| } | ||
|
|
||
| progress({ | ||
| step: 2800, | ||
| maxStep, | ||
| message: "Applying results", | ||
| }); | ||
|
|
||
| await this.addModeledMethods(loadedMethods); | ||
| } | ||
|
|
||
| private async callAutoModelApi( | ||
| request: ModelRequest, | ||
| ): Promise<ModelResponse | null> { | ||
| try { | ||
| return await autoModelV2(this.app.credentials, request); | ||
| } catch (e) { | ||
| if (e instanceof RequestError && e.status === 429) { | ||
| void showAndLogExceptionWithTelemetry( | ||
| this.app.logger, | ||
| this.app.telemetry, | ||
| redactableError(e)`Rate limit hit, please try again soon.`, | ||
| ); | ||
| return null; | ||
| } else { | ||
| throw e; | ||
| } | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we add some comments to the methods in here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure. Did you have anything specific in mind that you wanted commented?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I might just do that in a separate PR when there's a bit more beef to it. But please let me know if you'd like anything specific commented :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe what some of the parameters should contain? We can also do it in a followup.