Skip to content
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

Feat/pull precheck #11

Draft
wants to merge 61 commits into
base: development
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
61799d0
chore: use default wrangler dev port, init handler
Keyrxng Oct 23, 2024
86e7852
chore: remove .d.ts from manually written type file
Keyrxng Oct 23, 2024
9001723
chore: add new supported events, type handler
Keyrxng Oct 23, 2024
53d9a96
chore: gitignore temp payloads
Keyrxng Oct 23, 2024
af762e6
Merge branch 'development' into feat/pull-precheck
Keyrxng Oct 23, 2024
65a3a36
chore: submitCodeReview handler
Keyrxng Oct 24, 2024
f02dafa
chore: gql for task relations
Keyrxng Oct 24, 2024
f81eca0
chore: create sys msg fn, use array joins, update type name
Keyrxng Oct 24, 2024
89cbd3f
chore: improve sys msg readability, move to own file
Keyrxng Oct 24, 2024
eae71c5
chore: default sys msg and type import
Keyrxng Oct 24, 2024
b28670b
chore: llm-query-output handler
Keyrxng Oct 24, 2024
2d26534
chore: formatting
Keyrxng Oct 24, 2024
2c0325c
feat: basis for pull precheck
Keyrxng Oct 24, 2024
e6586a4
feat: dynamic ground truths
Keyrxng Oct 24, 2024
42934ff
chore: get issue no from payload util
Keyrxng Oct 24, 2024
63ab3e4
chore: precheck handler complete
Keyrxng Oct 24, 2024
638acbf
chore: gql updates, format, target: ESNEXT for regex groups
Keyrxng Oct 24, 2024
00534fa
chore: eslint, remove console.log, type fix
Keyrxng Oct 24, 2024
18f467f
chore: fix tests, type context for comment.created fns
Keyrxng Oct 24, 2024
e24add8
chore: pass single object param
Keyrxng Oct 24, 2024
788403d
chore: cleanup pull-precheck handler
Keyrxng Oct 24, 2024
48956a8
chore: correct Logs
Keyrxng Oct 24, 2024
50eae39
chore: move helper
Keyrxng Oct 24, 2024
b5b418d
chore: owner - repo - issueNo url util
Keyrxng Oct 24, 2024
8922866
chore: one review per day check
Keyrxng Oct 24, 2024
7501862
chore: convert to draft
Keyrxng Oct 24, 2024
b1d5d70
chore: context fallback for missing task spec
Keyrxng Oct 24, 2024
bbefbad
chore: get task spec
Keyrxng Oct 24, 2024
baa8ade
chore: has collaborator converted
Keyrxng Oct 24, 2024
d5359e8
chore: move hardcoded MAX_TOKENS into constants.ts
Keyrxng Oct 24, 2024
258c077
chore: tool handling
Keyrxng Oct 24, 2024
0f8ef70
chore: relocate helper fns
Keyrxng Oct 24, 2024
82cbb1e
chore: use my original agent logic - untested
Keyrxng Oct 24, 2024
e0f2d51
chore: adapters/openai/types and llm tools
Keyrxng Oct 24, 2024
5852dc4
chore: convertPrToDraft refactored for llm tooling
Keyrxng Oct 24, 2024
35e0429
chore: allow null body, format
Keyrxng Oct 31, 2024
228f486
chore: remove github-diff-tool
Keyrxng Oct 31, 2024
841b9a5
chore: return body hash matching, simplify diff fetch
Keyrxng Oct 31, 2024
dafdd09
chore: update logs to capture full final ctx
Keyrxng Oct 31, 2024
98c6839
chore: list normal comment on PR, remove readme from comments
Keyrxng Oct 31, 2024
d727f79
chore: readme block section, fetch only for current issue repo
Keyrxng Oct 31, 2024
0a63c34
chore: askGpt > askLlm
Keyrxng Oct 31, 2024
e2f6fad
chore: return empty array not a throw
Keyrxng Oct 31, 2024
9784ec5
chore: format, ctx window formatting fixes
Keyrxng Oct 31, 2024
e20a2c5
chore: fix tests
Keyrxng Oct 31, 2024
f1e5ba7
chore: sysMsg formatting fix
Keyrxng Oct 31, 2024
e817454
chore: hardcode model token limits
Keyrxng Oct 31, 2024
26f1709
chore: hardcode no language/deps responses
Keyrxng Oct 31, 2024
c5d4beb
chore: readability
Keyrxng Oct 31, 2024
d4b5a90
chore: ignore pr template html hashMatch
Keyrxng Oct 31, 2024
34b7ab8
chore: token handling
Keyrxng Oct 31, 2024
b877526
chore: diff fetch err handling
Keyrxng Oct 31, 2024
97868df
chore: pr parsing
Keyrxng Oct 31, 2024
398e993
chore: type, tests, cspell
Keyrxng Oct 31, 2024
0324dc9
chore: remove jsdoc comments, add helpful comments
Keyrxng Oct 31, 2024
f4332f9
chore: remove unused gql fetch
Keyrxng Oct 31, 2024
a6ffb03
chore: remove exclusion by file ext
Keyrxng Oct 31, 2024
a5221c7
chore: type null, filter null
Keyrxng Oct 31, 2024
2718477
chore: remove unused type
Keyrxng Nov 1, 2024
bf67520
Merge branch 'fix/missing-context' into feat/pull-precheck
Keyrxng Nov 3, 2024
9104945
chore: push old and merge missing ctx fixes
Keyrxng Nov 3, 2024
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
7 changes: 6 additions & 1 deletion .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,15 @@
"nemo",
"Reranking",
"mistralai",
"Precheck",
"Typeguard",
"typeguards",
"OPENROUTER_API_KEY",
"Openrouter"
"Openrouter",
"flac",
"dylib",
"mobileprovision",
"icns"
],
"dictionaries": ["typescript", "node", "software-terms"],
"import": ["@cspell/dict-typescript/cspell-ext.json", "@cspell/dict-node/cspell-ext.json", "@cspell/dict-software-terms"],
Expand Down
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,6 @@ junit.xml
cypress/screenshots
script.ts
.wrangler
test-dashboard.md
test-dashboard.md
payloads.json
test-dashboard.md
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "command-ask",
"description": "A highly context aware organization integrated chatbot",
"ubiquity:listeners": ["issue_comment.created"]
"ubiquity:listeners": ["issue_comment.created", "pull_request.opened", "pull_request.ready_for_review"]
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,14 @@
],
"dependencies": {
"@mswjs/data": "^0.16.2",
"@octokit/graphql-schema": "^15.25.0",
"@octokit/rest": "20.1.1",
"@octokit/webhooks": "13.2.7",
"@sinclair/typebox": "0.32.33",
"@supabase/supabase-js": "^2.45.4",
"@ubiquity-os/ubiquity-os-kernel": "^2.4.0",
"@ubiquity-os/ubiquity-os-logger": "^1.3.2",
"dotenv": "^16.4.5",
"github-diff-tool": "^1.0.6",
"gpt-tokenizer": "^2.5.1",
"openai": "^4.63.0",
"typebox-validators": "0.3.5",
Expand Down
3 changes: 3 additions & 0 deletions src/adapters/openai/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// this should probably be passed in via the config

export const MAX_COMPLETION_TOKENS = 7000;
35 changes: 35 additions & 0 deletions src/adapters/openai/helpers/append-to-base-chat-history.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { createSystemMessage } from "./create-system-msg";
import { ChatHistory, CreationParams, ToolCallResponse } from "../types";

export function appendToConversation(params: CreationParams, toolCallsToAppend: ToolCallResponse[] = []): ChatHistory {
const { systemMessage, query, additionalContext, localContext, groundTruths, botName } = params;
const baseChat: ChatHistory = [
{
role: "system",
content: [
{
type: "text",
text: createSystemMessage(systemMessage, additionalContext, localContext, groundTruths, botName),
},
],
},
{
role: "user",
content: [
{
type: "text",
text: query,
},
],
},
];

if (toolCallsToAppend.length > 0) {
toolCallsToAppend.forEach((toolCallResponse) => {
baseChat.push(toolCallResponse.response);
baseChat.push(toolCallResponse.tool_call_response);
});
}

return baseChat;
}
171 changes: 171 additions & 0 deletions src/adapters/openai/helpers/call-handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import OpenAI from "openai";
import { LLM_FUNCTIONS, LLM_TOOLS } from "./llm-tools";
import { Context } from "../../../types";
import { getIssueNumberFromPayload } from "../../../helpers/get-issue-no-from-payload";
import { logger } from "../../../helpers/errors";
import { ChatHistory, ResponseFromLlm } from "../types";
import { getAnswerAndTokenUsage } from "./get-answer-and-token-usage";

export async function handleChat(context: Context, chatHistory: ChatHistory) {
const response = await singleResponse(context, chatHistory);
return await handleResponse(context, response, chatHistory);
}

async function singleResponse(context: Context, chatHistory: ChatHistory) {
const {
config: { model },
env: { OPENAI_API_KEY },
} = context;
const openAi = new OpenAI({
apiKey: OPENAI_API_KEY,
});

return await openAi.chat.completions.create({
messages: chatHistory,
model,
max_tokens: 7000,
temperature: 0,
tools: LLM_TOOLS,
tool_choice: "auto",
});
}

async function handleResponse(
context: Context,
response: OpenAI.Chat.Completions.ChatCompletion,
chatHistory: ChatHistory
): Promise<ResponseFromLlm & { chatHistory: ChatHistory }> {
let chainCount = 0;
let toolIndex = 0;
let funcName = response.choices[0].message.tool_calls?.[0].function?.name;
let funcParams = response.choices[0].message.tool_calls?.[0].function?.arguments;
const toolCalls = response.choices[0].message.tool_calls?.length;

const answerAndUsage = getAnswerAndTokenUsage(response);

if (!toolCalls) {
return {
...answerAndUsage,
chatHistory,
};
}

while (toolCalls > 0) {
chainCount++;
console.log(`Chain count: ${chainCount}`);
console.log(`Response ${chainCount}: ${response.choices[0].message.content}`);
const toolCallFn = agentCommands.find((command) => command.name === funcName);

let argObj: Record<string, unknown>;
if (funcParams) {
argObj = JSON.parse(funcParams);
} else {
argObj = {};
}

try {
if (toolCallFn && toolCallFn.func) {
const issueNumber = getIssueNumberFromPayload(context.payload);
const args = toolCallFn?.expectedArgs.map((arg: string) => argObj[arg]) || [];
const result = await toolCallFn?.func(...args, {
owner: context.payload.repository.owner.login,
repo: context.payload.repository.name,
octokit: context.octokit,
pull_number: issueNumber,
});

chatHistory.push({
role: "tool",
content: result,
tool_call_id: response.choices[0].message.tool_calls?.[toolIndex]?.id || "",
});
}
} catch (err) {
console.log("====================================");
console.log("err:", err);
console.log("====================================");
}
toolIndex++;

if (!response.choices[0].message.tool_calls?.[toolIndex]) {
break;
}

funcName = response.choices[0].message.tool_calls?.[toolIndex]?.function.name;
funcParams = response.choices[0].message.tool_calls?.[toolIndex]?.function.arguments;
}

response = await singleResponse(context, chatHistory);

const lastResponse = getAnswerAndTokenUsage(response);

if (!lastResponse.answer) {
throw logger.error("No response found in handleResponse", {
response,
chatHistory,
chainCount,
toolCalls,
toolIndex,
});
}
const {
tokenUsage: { outputDetails: lastOutputDetails },
} = lastResponse;
const {
tokenUsage: { outputDetails: firstOutputDetails },
} = answerAndUsage;

let totalReasoningTokens = 0;

if (lastOutputDetails && lastOutputDetails.reasoning_tokens) {
totalReasoningTokens += lastOutputDetails.reasoning_tokens;
}

if (firstOutputDetails && firstOutputDetails.reasoning_tokens) {
totalReasoningTokens += firstOutputDetails.reasoning_tokens;
}

return {
answer: lastResponse.answer,
chatHistory,
tokenUsage: {
input: answerAndUsage.tokenUsage.input + lastResponse.tokenUsage.input,
output: answerAndUsage.tokenUsage.output + lastResponse.tokenUsage.output,
total: answerAndUsage.tokenUsage.total + lastResponse.tokenUsage.total,
outputDetails: {
reasoning_tokens: totalReasoningTokens,
},
},
};
}

function isValidTool(name: string) {
return LLM_TOOLS.some((tool) => tool.function.name === `${name}Tool`);
}

type AgentCommand = {
name: string;
// eslint-disable-next-line @typescript-eslint/ban-types
func: Function;
expectedArgs: string[];
};

/**
* Handles function calling/response chaining for our models.
*/
const agentCommands: AgentCommand[] = LLM_TOOLS.map((tool) => {
// tools should be named like: fnNameTool > fnName (convertPullToDraftTool > convertPullToDraft)
// where fnNameTool is the api consumed by the LLM and fnName is the actual function
const fnName = tool.function.name.replace("Tool", "");

if (!isValidTool(fnName)) {
throw new Error(`Invalid tool called: ${fnName}`);
}

return {
name: tool.function.name,
// eslint-disable-next-line @typescript-eslint/ban-types
func: LLM_FUNCTIONS.find((fn) => fn.name === fnName) as Function,
expectedArgs: JSON.parse(JSON.stringify(tool.function.parameters?.required)) as string[],
};
});
Loading
Loading