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

Command ask #45

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion .dev.vars.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ SUPABASE_URL=""
SUPABASE_KEY=""
VOYAGEAI_API_KEY=""
OPENROUTER_API_KEY=""
UBIQUITY_OS_APP_NAME=""
UBIQUITY_OS_APP_NAME=""
ANTHROPIC_API_KEY=
1 change: 1 addition & 0 deletions .github/workflows/compute.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,6 @@ jobs:
SUPABASE_KEY: ${{ secrets.SUPABASE_KEY }}
VOYAGEAI_API_KEY: ${{ secrets.VOYAGEAI_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
UBIQUITY_OS_APP_NAME: ${{ secrets.UBIQUITY_OS_APP_NAME }}
2 changes: 2 additions & 0 deletions .github/workflows/worker-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,15 @@ jobs:
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
secrets: |
OPENAI_API_KEY
ANTHROPIC_API_KEY
VOYAGEAI_API_KEY
UBIQUITY_OS_APP_NAME
SUPABASE_URL
SUPABASE_KEY
${{ secrets.KERNEL_PUBLIC_KEY && secrets.KERNEL_PUBLIC_KEY != '' && 'KERNEL_PUBLIC_KEY' || '' }}
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY}}
VOYAGEAI_API_KEY: ${{ secrets.VOYAGEAI_API_KEY }}
UBIQUITY_OS_APP_NAME: ${{ secrets.UBIQUITY_OS_APP_NAME }}
SUPABASE_URL: ${{ secrets.SUPABASE_URL }}
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,11 @@ plugins:
- uses:
- plugin: http://localhost:4000
with:
model: ""
openAiModel: ""
openAiBaseUrl: ""
anthropicAiModel: ""
anthropicAiBaseUrl: ""

```

`.dev.vars` (for local testing):
Expand All @@ -31,6 +34,7 @@ To use the Openrouter API for fetching chat history, set the `OPENROUTER_API_KEY

```sh
OPENAI_API_KEY=your_openai_api_key
ANTHROPIC_API_KEY=your_anthropic_api_key
SUPABASE_URL=your_supabase_url
SUPABASE_KEY=your_supabase_key
VOYAGEAI_API_KEY=your_voyageai_api_key
Expand Down
Binary file modified bun.lockb
Binary file not shown.
11 changes: 9 additions & 2 deletions manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"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"],
"skipBotEvents": true,
"commands": {
"ask": {
Expand All @@ -21,13 +21,20 @@
"configuration": {
"type": "object",
"properties": {
"model": {
"openAiModel": {
"default": "o1-mini",
"type": "string"
},
"openAiBaseUrl": {
"type": "string"
},
"anthropicAiModel": {
"default": "claude-3.5-sonnet",
"type": "string"
},
"anthropicAiBaseUrl": {
"type": "string"
},
"similarityThreshold": {
"default": 0.9,
"type": "number"
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
"open-source"
],
"dependencies": {
"@anthropic-ai/sdk": "^0.32.1",
"@octokit/graphql-schema": "^15.25.0",
"@sinclair/typebox": "0.34.3",
"@supabase/supabase-js": "^2.45.4",
"@ubiquity-os/plugin-sdk": "^1.1.0",
Expand Down
12 changes: 12 additions & 0 deletions src/adapters/claude/helpers/claude.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import Anthropic from "@anthropic-ai/sdk";
import { Context } from "../../../types/context";

export class SuperAnthropic {
protected client: Anthropic;
protected context: Context;

constructor(client: Anthropic, context: Context) {
this.context = context;
this.client = client;
}
}
148 changes: 148 additions & 0 deletions src/adapters/claude/helpers/completions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/* eslint-disable sonarjs/no-duplicate-string */

import Anthropic from "@anthropic-ai/sdk";
import { Context } from "../../../types";
import { CompletionsModelHelper, ModelApplications } from "../../../types/llm";
import { encode } from "gpt-tokenizer";
import { ContentBlock } from "@anthropic-ai/sdk/resources";
import { SuperAnthropic } from "./claude";

export interface CompletionsType {
answer: string;
groundTruths: string[];
tokenUsage: {
input: number;
output: number;
total: number;
};
}

// Type guard for content block
interface TextBlock {
type: "text";
text: string;
}

function isTextBlock(content: ContentBlock): content is TextBlock {
return content?.type === "text" && typeof content?.text === "string";
}

export class AnthropicCompletion extends SuperAnthropic {
protected context: Context;
ishowvel marked this conversation as resolved.
Show resolved Hide resolved

constructor(client: Anthropic, context: Context) {
super(client, context);
this.context = context;
}

getModelMaxTokenLimit(model: string): number {
const tokenLimits = new Map<string, number>([["claude-3.5-sonnet", 200000]]);

return tokenLimits.get(model) || 200000;
}

getModelMaxOutputLimit(model: string): number {
const tokenLimits = new Map<string, number>([["claude-3.5-sonnet", 4096]]);

return tokenLimits.get(model) || 4096;
}

async createCompletion(
model: string = "claude-3.5-sonnet",
localContext: string[],
groundTruths: string[],
botName: string,
maxTokens: number
): Promise<CompletionsType> {
const query =
"Perform code review using the diff and spec and output a JSON format with key: 'confidenceThreshold' (0-1). A 0 indicates that the code review failed and 1 mean its passed";

const sysMsg = [
"You Must obey the following ground truths: ",
JSON.stringify(groundTruths) + "\n",
"You are tasked with assisting as a GitHub bot by generating a confidence threshold from 0-1 on whether you think the pull difference completes the issue specification/body based on provided chat history and similar responses, focusing on using available knowledge within the provided corpus, which may contain code, documentation, or incomplete information. Your role is to interpret and use this knowledge effectively to answer user questions.\n\n# Steps\n\n1. **Understand Context**: Analyze the chat history and any similar responses to grasp the issue requirements and pull request intent.\n2. **Extract Key Information**: Identify crucial details from the corpus, even if incomplete, focusing on specifications and their alignment with the pull diff.\n3. **Evaluate Completeness**: Assess how well the pull diff fulfills the issue specifications, using logical assumptions if needed to fill gaps.\n4. **Generate Confidence**: Provide a confidence score (0-1) indicating how likely the pull diff satisfies the issue specification.\n5. **Output Response**: Return only a JSON object in this format: `{confidenceThreshold: <value>}`. For example, if the pull diff adds `x.py` to fulfill a request to add `x.py`, output `{confidenceThreshold: 1}`. Include no explanation or additional text.",
`Your name is: ${botName}`,
"\n",
"Main Context (Provide additional precedence in terms of information): ",
localContext.join("\n"),
].join("\n");

this.context.logger.info(`System message: ${sysMsg}`);

const res = await this.client.messages.create({
model: model,
system: sysMsg,
messages: [
{
role: "user",
content: query,
},
],
max_tokens: maxTokens,
temperature: 0,
});

// Use type guard to safely handle the response
const content = res.content[0];
ishowvel marked this conversation as resolved.
Show resolved Hide resolved
if (!isTextBlock(content)) {
throw this.context.logger.error("Unexpected response format: Expected text block");
}

const answer = content.text;

const inputTokens = res.usage.input_tokens;
const outputTokens = res.usage.output_tokens;

this.context.logger.info(`Number of tokens used: ${inputTokens + outputTokens}`);

return {
answer,
groundTruths,
tokenUsage: {
input: inputTokens,
output: outputTokens,
total: inputTokens + outputTokens,
},
};
}

async createGroundTruthCompletion<TApp extends ModelApplications>(
context: Context,
groundTruthSource: string,
systemMsg: string,
model: CompletionsModelHelper<TApp>
): Promise<string | null> {
const {
env: { ANTHROPIC_API_KEY },
config: { anthropicAiBaseUrl },
} = context;

const client = new Anthropic({
apiKey: ANTHROPIC_API_KEY,
baseURL: anthropicAiBaseUrl,
});

const res = await client.messages.create({
model: model,
system: systemMsg,
max_tokens: this.getModelMaxTokenLimit(model),
messages: [
{
role: "user",
content: groundTruthSource,
},
],
});

const content = res.content[0];
ishowvel marked this conversation as resolved.
Show resolved Hide resolved
if (!isTextBlock(content)) {
throw this.context.logger.error("Unexpected response format: Expected text block");
}

return content.text;
}

async findTokenLength(text: string = "", additionalContext: string[] = [], localContext: string[] = [], groundTruths: string[] = []): Promise<number> {
return encode(text + additionalContext.join("\n") + localContext.join("\n") + groundTruths.join("\n"), { disallowedSpecial: new Set() }).length;
}
}
9 changes: 8 additions & 1 deletion src/adapters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@ import { SuperOpenAi } from "./openai/helpers/openai";
import OpenAI from "openai";
import { Completions } from "./openai/helpers/completions";
import { Rerankers } from "./voyage/helpers/rerankers";
import { AnthropicCompletion } from "./claude/helpers/completions";
import Anthropic from "@anthropic-ai/sdk";
import { SuperAnthropic } from "./claude/helpers/claude";

export function createAdapters(supabaseClient: SupabaseClient, voyage: VoyageAIClient, openai: OpenAI, context: Context) {
export function createAdapters(supabaseClient: SupabaseClient, voyage: VoyageAIClient, openai: OpenAI, anthropic: Anthropic, context: Context) {
return {
supabase: {
comment: new Comment(supabaseClient, context),
Expand All @@ -27,5 +30,9 @@ export function createAdapters(supabaseClient: SupabaseClient, voyage: VoyageAIC
completions: new Completions(openai, context),
super: new SuperOpenAi(openai, context),
},
anthropic: {
completions: new AnthropicCompletion(anthropic, context),
super: new SuperAnthropic(anthropic, context),
},
};
}
12 changes: 9 additions & 3 deletions src/handlers/add-comment.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import { Context } from "../types/context";

import { getIssueNumberFromPayload } from "../helpers/issue-fetching";
/**
* Add a comment to an issue
* @param context - The context object containing environment and configuration details
* @param message - The message to add as a comment
*/
export async function addCommentToIssue(context: Context, message: string) {

export async function addCommentToIssue(context: Context, message: string, issueNum?: number) {
const { payload } = context;
const issueNumber = payload.issue.number;
const issueNumber = getIssueNumberFromPayload(context.payload, {
issueNum,
context: context,
owner: payload.repository.owner.login,
repo: payload.repository.name,
});
try {
await context.octokit.rest.issues.createComment({
owner: payload.repository.owner.login,
Expand Down
Loading
Loading