mirror of
https://github.com/iflytek/skillhub.git
synced 2026-08-27 11:14:59 +00:00
* add issue triage automation mvp * Document issue automation design in Chinese * Fix legacy compat slug tests
139 lines
3.5 KiB
TypeScript
139 lines
3.5 KiB
TypeScript
import { GitHubIssue } from "./github.ts";
|
|
import { IssueLlmConfig } from "./issue-llm-types.ts";
|
|
import { TriageResult } from "./issue-triage-types.ts";
|
|
|
|
const DEFAULT_TIMEOUT_MS = 30000;
|
|
const DEFAULT_MAX_ATTEMPTS = 2;
|
|
const DEFAULT_RETRY_BACKOFF_MS = 1500;
|
|
const DEFAULT_TEMPERATURE = 0.1;
|
|
const DEFAULT_MAX_COMMENTS = 4;
|
|
const DEFAULT_MAX_COMMENT_CHARS = 900;
|
|
const DEFAULT_MAX_BODY_CHARS = 6000;
|
|
|
|
export function readIssueLlmConfig(): IssueLlmConfig | null {
|
|
const mode = normalizeMode(Deno.env.get("ISSUE_TRIAGE_LLM_MODE"));
|
|
|
|
if (mode === "off") {
|
|
return null;
|
|
}
|
|
|
|
const baseUrl = normalizeUrl(Deno.env.get("ISSUE_TRIAGE_LLM_BASE_URL"));
|
|
const apiKey = Deno.env.get("ISSUE_TRIAGE_LLM_API_KEY")?.trim() ?? "";
|
|
const model = Deno.env.get("ISSUE_TRIAGE_LLM_MODEL")?.trim() ?? "";
|
|
|
|
if (!baseUrl || !apiKey || !model) {
|
|
console.warn(
|
|
"LLM triage is configured in a non-off mode but base URL, model, or API key is missing. Falling back to rules-only.",
|
|
);
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
mode,
|
|
provider: "openai-compatible",
|
|
baseUrl,
|
|
apiKey,
|
|
model,
|
|
timeoutMs: parseInteger(
|
|
Deno.env.get("ISSUE_TRIAGE_LLM_TIMEOUT_MS"),
|
|
DEFAULT_TIMEOUT_MS,
|
|
),
|
|
maxAttempts: Math.max(
|
|
1,
|
|
parseInteger(
|
|
Deno.env.get("ISSUE_TRIAGE_LLM_MAX_ATTEMPTS"),
|
|
DEFAULT_MAX_ATTEMPTS,
|
|
),
|
|
),
|
|
retryBackoffMs: Math.max(
|
|
0,
|
|
parseInteger(
|
|
Deno.env.get("ISSUE_TRIAGE_LLM_RETRY_BACKOFF_MS"),
|
|
DEFAULT_RETRY_BACKOFF_MS,
|
|
),
|
|
),
|
|
temperature: parseFloatSetting(
|
|
Deno.env.get("ISSUE_TRIAGE_LLM_TEMPERATURE"),
|
|
DEFAULT_TEMPERATURE,
|
|
),
|
|
maxComments: parseInteger(
|
|
Deno.env.get("ISSUE_TRIAGE_LLM_MAX_COMMENTS"),
|
|
DEFAULT_MAX_COMMENTS,
|
|
),
|
|
maxCommentChars: parseInteger(
|
|
Deno.env.get("ISSUE_TRIAGE_LLM_MAX_COMMENT_CHARS"),
|
|
DEFAULT_MAX_COMMENT_CHARS,
|
|
),
|
|
maxBodyChars: parseInteger(
|
|
Deno.env.get("ISSUE_TRIAGE_LLM_MAX_BODY_CHARS"),
|
|
DEFAULT_MAX_BODY_CHARS,
|
|
),
|
|
};
|
|
}
|
|
|
|
export function shouldUseLlm(issue: GitHubIssue, result: TriageResult) {
|
|
const reasons: string[] = [];
|
|
|
|
if (result.route === "needs-info") {
|
|
reasons.push("route-needs-info");
|
|
}
|
|
|
|
if (result.route === "core") {
|
|
reasons.push("route-core");
|
|
}
|
|
|
|
if (result.priority >= 3 && result.priority <= 4.2) {
|
|
reasons.push("priority-near-threshold");
|
|
}
|
|
|
|
if (result.confidence <= 3) {
|
|
reasons.push("confidence-low");
|
|
}
|
|
|
|
if (issue.comments >= 4) {
|
|
reasons.push("discussion-heavy");
|
|
}
|
|
|
|
if ((issue.body ?? "").length >= 1200) {
|
|
reasons.push("body-long");
|
|
}
|
|
|
|
if (result.issueKind === "feature" || result.issueKind === "reward") {
|
|
reasons.push("non-bug-judgment");
|
|
}
|
|
|
|
return {
|
|
use: reasons.length > 0,
|
|
reasons,
|
|
};
|
|
}
|
|
|
|
function normalizeMode(raw: string | undefined | null) {
|
|
const value = raw?.trim().toLowerCase();
|
|
|
|
if (value === "shadow" || value === "assist") {
|
|
return value;
|
|
}
|
|
|
|
return "off";
|
|
}
|
|
|
|
function normalizeUrl(value: string | undefined | null) {
|
|
const trimmed = value?.trim();
|
|
|
|
if (!trimmed) {
|
|
return "";
|
|
}
|
|
|
|
return trimmed.endsWith("/") ? trimmed.slice(0, -1) : trimmed;
|
|
}
|
|
|
|
function parseInteger(raw: string | undefined, fallback: number) {
|
|
const parsed = Number.parseInt(raw ?? "", 10);
|
|
return Number.isNaN(parsed) ? fallback : parsed;
|
|
}
|
|
|
|
function parseFloatSetting(raw: string | undefined, fallback: number) {
|
|
const parsed = Number.parseFloat(raw ?? "");
|
|
return Number.isNaN(parsed) ? fallback : parsed;
|
|
}
|