• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hicommonwealth / commonwealth / 12913607989

22 Jan 2025 05:21PM UTC coverage: 47.273% (-0.4%) from 47.698%
12913607989

Pull #10529

github

web-flow
Merge 3e0cc764b into 2dceaabc4
Pull Request #10529: Add contest bot webhook

1359 of 3231 branches covered (42.06%)

Branch coverage included in aggregate %.

7 of 61 new or added lines in 6 files covered. (11.48%)

39 existing lines in 10 files now uncovered.

2697 of 5349 relevant lines covered (50.42%)

36.14 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

9.76
/libs/model/src/services/openai/parseBotCommand.ts
1
import { OpenAI } from 'openai';
2
import {
3
  ChatCompletionMessage,
4
  ChatCompletionTool,
5
} from 'openai/resources/index.mjs';
6
import { config } from '../../config';
7

8
export type ContestMetadataResponse = {
9
  contestName: string;
10
  payoutStructure: number[];
11
  voterShare: number;
12
  image_url: string;
13
  tokenAddress: string;
14
};
15

16
const system_prompt: ChatCompletionMessage = {
29✔
17
  role: 'assistant',
18
  content: `
19
    You are a data extraction system to understand intents of the following style of message: \n
20
    "hey @contestbot create a contest with the token 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913,
21
    with 20% of prize amount allocated to voters and the rest going to two winners.
22
    Winner #1 should have 75% while winner #2 has 25%.
23
    The contest title is “Submit your best artwork for our token”.
24
    Use the following image https://test.com/test.png" \n
25
    This message should result in the following parameters: \n
26
    {
27
      "contestName": "Submit your best artwork for our token",
28
      "payoutStructure": [75, 25],
29
      "voterShare": 20,
30
      "image_url": "https://test.com/test.png",
31
      "tokenAddress": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
32
    }
33
    \n
34
    The payoutStructure refers to the percentage given to each winner and its values must always add up to 100.
35
    Any mention of dividing equally refers to the payoutStructure.
36
    Any mention of shares, allocation or distribution to voters should be assigned to the voterShare
37
    which is independent of payoutStructure.
38
    `,
39
};
40

41
const tools: ChatCompletionTool[] = [
29✔
42
  {
43
    type: 'function',
44
    function: {
45
      name: 'parse_data',
46
      parameters: {
47
        type: 'object',
48
        properties: {
49
          contestName: { type: 'string' },
50
          payoutStructure: { type: 'array', items: { type: 'number' } },
51
          voterShare: { type: 'number' },
52
          image_url: { type: 'string' },
53
          tokenAddress: { type: 'string' },
54
        },
55
      },
56
    },
57
  },
58
];
59

60
// Custom error type that returns a human-readable error intended for end users
61
export class ParseBotCommandError extends Error {
62
  static ERRORS = {
29✔
63
    NoResponse: 'Failed to create contest. Verify your prompt or try again.',
64
    InvalidParams:
65
      'Failed to create contest. Specify all contest parameters: winners, prize distribution to voters, title, image and token address.',
66
  } as const;
67

68
  constructor(message: keyof typeof ParseBotCommandError.ERRORS) {
NEW
69
    super(message);
×
NEW
70
    this.name = this.constructor.name;
×
71

NEW
72
    if (Error.captureStackTrace) {
×
NEW
73
      Error.captureStackTrace(this, this.constructor);
×
74
    }
75
  }
76

77
  getPrettyError(): string {
NEW
78
    return (
×
79
      ParseBotCommandError.ERRORS[
×
80
        this.message as keyof typeof ParseBotCommandError.ERRORS
81
      ] || 'An unknown error occurred.'
82
    );
83
  }
84
}
85

86
export const parseBotCommand = async (
29✔
87
  command: string,
88
): Promise<ContestMetadataResponse> => {
89
  const openai = new OpenAI({
×
90
    organization: config.OPENAI.ORGANIZATION,
91
    apiKey: config.OPENAI.API_KEY,
92
  });
93

94
  const response = await openai.chat.completions.create({
×
95
    model: 'gpt-4-turbo',
96
    messages: [
97
      system_prompt,
98
      {
99
        role: 'user',
100
        content: command,
101
      },
102
    ],
103
    tools,
104
  });
105

NEW
106
  let data = null;
×
NEW
107
  try {
×
NEW
108
    data = JSON.parse(
×
109
      response.choices[0].message.tool_calls![0].function.arguments,
110
    );
111
  } catch (err) {
NEW
112
    throw new ParseBotCommandError('NoResponse');
×
113
  }
114

115
  // if payout structure has any remainder under 100, give it to first winner
NEW
116
  if (!data.payoutStructure.length) {
×
NEW
117
    throw new ParseBotCommandError('InvalidParams');
×
118
  }
NEW
119
  const payoutStructure: Array<number> = data.payoutStructure.map((n: number) =>
×
NEW
120
    Math.floor(n),
×
121
  );
NEW
122
  const sum = payoutStructure.reduce((p, acc) => acc + p, 0);
×
NEW
123
  if (sum < 100) {
×
NEW
124
    const remainder = 100 - sum;
×
NEW
125
    payoutStructure[0] += remainder;
×
126
  }
127

NEW
128
  if (!data.contestName || !data.image_url || !data.tokenAddress) {
×
NEW
129
    throw new ParseBotCommandError('InvalidParams');
×
130
  }
131

132
  return {
×
133
    contestName: data.contestName,
134
    payoutStructure,
135
    voterShare: data.voterShare || 0,
×
136
    image_url: data.image_url,
137
    tokenAddress: data.tokenAddress,
138
  };
139
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc