Skip to content

Commit

Permalink
🔧 refactor: improve environment variable support and bot configuration
Browse files Browse the repository at this point in the history
Added sample API keys and tokens to .env-sample for enhanced security.
Fixed version information in README.md.
Improved asynchronous handling of bot configurations in MultiInstance.ts using Promise.all, and added error messages for when no bots are configured.
Also, fixed the function to hide tokens to return a new object without altering the original configuration.
  • Loading branch information
takuya-o committed Dec 31, 2024
1 parent a02221d commit d300de7
Show file tree
Hide file tree
Showing 5 changed files with 174 additions and 143 deletions.
19 changes: 18 additions & 1 deletion .env-sample
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
#CONFIG_FILE=./config.yaml
# CONFIG_FILE=./config.yaml

# Use environment variables to set API keys and tokens to manage them securely without storing them directly in the container as files.
# OPENAI__MATTERMOST_TOKEN=your-mattermost-token
# OPENAI_API_KEY=your-openai-api-key

# AZURE_MATTERMOST_TOKEN=your-mattermost-token-for-azure
# AZURE_OPENAI_API_KEY=your-azure-openai-api-key
# AZURE_OPENAI_API_IMAGE_KEY=your-azure-openai-vision-key
# AZURE_OPENAI_API_VISION_KEY=your-azure-openai-image-key

# COHERE_MATTERMOST_TOKEN=your-mattermost-token-for-cohere
# COHERE_API_KEY=your-cohere-api-key

# GOOGLE_MATTERMOST_TOKEN=your-mattermost-token-for-google
# GOOGLE_API_KEY=your-google-api-key


# Set the debug level and format
# DEBUG_LEVEL=INFO
# DEBUG_JSON=true
# DEBUG_COLORS=true
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
> **Note** 👀
> - Configuration methods have changed in version 3.
> - The version 3.0.1 have backward compatibility for enviromnent variables configurations on single instance.
> - After version 3.1 have backward compatibility for enviromnent variables configurations on single instance.
> - Recommend manually rewrite environment variables into `config.yaml` for multi instance.
## Enhanced from the [original yGuy/chatgpt-mattermost-bot](https://github.com/yGuy/chatgpt-mattermost-bot)
Expand Down
5 changes: 3 additions & 2 deletions config-sample.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ PLUGINS: image-plugin
OPENAI_MAX_TOKENS: 2000
OPENAI_TEMPERATURE: 1
MAX_PROMPT_TOKENS: 2000
# Bot instructions
BOT_INSTRUCTION: "You are a helpful assistant. Whenever users asks you for help you will provide them with succinct answers formatted using Markdown. You know the user's name as it is provided within the meta data of the messages."

bots:
- name: '@OpenAI'
Expand Down Expand Up @@ -68,5 +70,4 @@ bots:
temperature: 1
maxPromptTokens: 123904
plugins: ''
# Bot instructions
BOT_INSTRUCTION: "You are a helpful assistant. Whenever users asks you for help you will provide them with succinct answers formatted using Markdown. You know the user's name as it is provided within the meta data of the messages."

131 changes: 70 additions & 61 deletions dist/MultiInstance.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2052,74 +2052,83 @@ var botServices = {};
async function main() {
const config2 = getConfig();
config2.bots = config2.bots || [{}];
config2.bots.forEach(async (botConfig) => {
const name = botConfig.name ?? process.env["MATTERMOST_BOTNAME"];
if (botServices[name]) {
botLog.error(`Duplicate bot name detected: ${name}. Ignoring this bot configuration.`, hideTokens(botConfig));
return;
}
if (!name) {
botLog.error("No name. Ignore provider config", hideTokens(botConfig));
return;
}
botConfig.name = name;
if (!botConfig.type) {
if (process.env["AZURE_OPENAI_API_KEY"] || botConfig.apiVersion || botConfig.instanceName || botConfig.deploymentName) {
botConfig.type = "azure";
} else if (process.env["OPENAI_API_KEY"]) {
botConfig.type = "openai";
} else if (process.env["GOOGLE_API_KEY"]) {
botConfig.type = "google";
} else if (process.env["COHERE_API_KEY"]) {
botConfig.type = "cohere";
} else if (process.env["ANTHROPIC_API_KEY"]) {
botConfig.type = "anthropic";
} else {
botLog.error(`${name} No type. Ignore provider config`, hideTokens(botConfig));
await Promise.all(
config2.bots.map(async (botConfig) => {
const name = botConfig.name ?? process.env["MATTERMOST_BOTNAME"];
if (botServices[name]) {
botLog.error(`Duplicate bot name detected: ${name}. Ignoring this bot configuration.`, hideTokens(botConfig));
return;
}
botLog.warn(`${name} No type. Guessing type as ${botConfig.type}.`, hideTokens(botConfig));
}
botLog.log(`${name} Connected to Mattermost.`);
const mattermostToken = botConfig.mattermostToken ?? process.env[`${name.toUpperCase()}_MATTERMOST_TOKEN`] ?? process.env[`${botConfig.type.toUpperCase()}_MATTERMOST_TOKEN`] ?? process.env["MATTERMOST_TOKEN"];
const mattermostClient = new MattermostClient(
botConfig.mattermostUrl ?? config2.MATTERMOST_URL ?? process.env["MATTERMOST_URL"],
mattermostToken
);
botLog.log(`${name} Start LLM wrapper.`);
let openAIWrapper;
try {
openAIWrapper = new OpenAIWrapper(botConfig, mattermostClient);
} catch (e) {
botLog.error(`${name} Failed to create OpenAIWrapper. Ignore it.`, e);
return;
}
botLog.log(`${name} Start BotService.`);
const meId = (await mattermostClient.getClient().getMe()).id;
const botService = new BotService2(
mattermostClient,
meId,
name,
openAIWrapper,
botConfig.plugins ?? config2.PLUGINS ?? process.env["PLUGINS"] ?? "image-plugin graph-plugin"
);
mattermostClient.getWsClient().addMessageListener((e) => botService.onClientMessage(e));
botLog.trace(`${name} Listening to MM messages...`);
botServices[name] = botService;
});
if (!name) {
botLog.error("No name. Ignore provider config", hideTokens(botConfig));
return;
}
botConfig.name = name;
if (!botConfig.type) {
if (process.env["AZURE_OPENAI_API_KEY"] || botConfig.apiVersion || botConfig.instanceName || botConfig.deploymentName) {
botConfig.type = "azure";
} else if (process.env["OPENAI_API_KEY"]) {
botConfig.type = "openai";
} else if (process.env["GOOGLE_API_KEY"]) {
botConfig.type = "google";
} else if (process.env["COHERE_API_KEY"]) {
botConfig.type = "cohere";
} else if (process.env["ANTHROPIC_API_KEY"]) {
botConfig.type = "anthropic";
} else {
botLog.error(`${name} No type. Ignore provider config`, hideTokens(botConfig));
return;
}
botLog.warn(`${name} No type. Guessing type as ${botConfig.type}.`, hideTokens(botConfig));
}
botLog.log(`${name} Connected to Mattermost.`);
const mattermostToken = botConfig.mattermostToken ?? process.env[`${name.toUpperCase()}_MATTERMOST_TOKEN`] ?? process.env[`${botConfig.type.toUpperCase()}_MATTERMOST_TOKEN`] ?? process.env["MATTERMOST_TOKEN"];
const mattermostClient = new MattermostClient(
botConfig.mattermostUrl ?? config2.MATTERMOST_URL ?? process.env["MATTERMOST_URL"],
mattermostToken
);
botLog.log(`${name} Start LLM wrapper.`);
let openAIWrapper;
try {
openAIWrapper = new OpenAIWrapper(botConfig, mattermostClient);
} catch (e) {
botLog.error(`${name} Failed to create OpenAIWrapper. Ignore it.`, e);
return;
}
botLog.log(`${name} Start BotService.`);
const meId = (await mattermostClient.getClient().getMe()).id;
const botService = new BotService2(
mattermostClient,
meId,
name,
openAIWrapper,
botConfig.plugins ?? config2.PLUGINS ?? process.env["PLUGINS"] ?? "image-plugin graph-plugin"
);
mattermostClient.getWsClient().addMessageListener((e) => botService.onClientMessage(e));
botLog.trace(`${name} Listening to MM messages...`);
botServices[name] = botService;
})
);
if (Object.keys(botServices).length === 0) {
botLog.error("No bot is configured. Exiting...");
process.exit(-1);
}
botLog.log("All bots started.", Object.keys(botServices));
function hideTokens(botConfig) {
if (botConfig.mattermostToken) {
botConfig.mattermostToken = "***";
const result = { ...botConfig };
if (result.mattermostToken) {
result.mattermostToken = "***";
}
if (botConfig.apiKey) {
botConfig.apiKey = "***";
if (result.apiKey) {
result.apiKey = "***";
}
if (botConfig.imageKey) {
botConfig.imageKey = "***";
if (result.imageKey) {
result.imageKey = "***";
}
if (botConfig.visionKey) {
botConfig.visionKey = "***";
if (result.visionKey) {
result.visionKey = "***";
}
return result;
}
}
main().catch((reason) => {
Expand Down
160 changes: 82 additions & 78 deletions src/MultiInstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,91 +12,95 @@ const botServices: Record<string, BotService> = {}
async function main(): Promise<void> {
const config = getConfig()
config.bots = config.bots || [{}] // 旧バージョンの環境変数での設定を期待する
config.bots.forEach(async (botConfig: ProviderConfig) => {
const name = botConfig.name ?? process.env['MATTERMOST_BOTNAME']
if (botServices[name]) {
botLog.error(`Duplicate bot name detected: ${name}. Ignoring this bot configuration.`, hideTokens(botConfig))
return
}
if (!name) {
botLog.error('No name. Ignore provider config', hideTokens(botConfig))
return
}
botConfig.name = name
if (!botConfig.type) {
// typeが無いので、どのAPI_KEYが環境変数で定義されているかで推測する
if (
process.env['AZURE_OPENAI_API_KEY'] ||
botConfig.apiVersion ||
botConfig.instanceName ||
botConfig.deploymentName
) {
botConfig.type = 'azure'
} else if (process.env['OPENAI_API_KEY']) {
botConfig.type = 'openai'
} else if (process.env['GOOGLE_API_KEY']) {
botConfig.type = 'google'
} else if (process.env['COHERE_API_KEY']) {
botConfig.type = 'cohere'
} else if (process.env['ANTHROPIC_API_KEY']) {
botConfig.type = 'anthropic'
} else {
botLog.error(`${name} No type. Ignore provider config`, hideTokens(botConfig))
await Promise.all(
config.bots.map(async (botConfig: ProviderConfig) => {
const name = botConfig.name ?? process.env['MATTERMOST_BOTNAME']
if (botServices[name]) {
botLog.error(`Duplicate bot name detected: ${name}. Ignoring this bot configuration.`, hideTokens(botConfig))
return
}
botLog.warn(`${name} No type. Guessing type as ${botConfig.type}.`, hideTokens(botConfig))
}
botLog.log(`${name} Connected to Mattermost.`)
// AZURE_MATTERMOST_TOKEN, GOOGLE_MATTERMOST_TOKEN... を新設
const mattermostToken =
botConfig.mattermostToken ??
process.env[`${name.toUpperCase()}_MATTERMOST_TOKEN`] ??
process.env[`${botConfig.type.toUpperCase()}_MATTERMOST_TOKEN`] ??
process.env['MATTERMOST_TOKEN']
const mattermostClient = new MattermostClient(
botConfig.mattermostUrl ?? config.MATTERMOST_URL ?? process.env['MATTERMOST_URL'],
mattermostToken,
)
botLog.log(`${name} Start LLM wrapper.`)
let openAIWrapper: OpenAIWrapper
try {
openAIWrapper = new OpenAIWrapper(botConfig, mattermostClient)
} catch (e) {
botLog.error(`${name} Failed to create OpenAIWrapper. Ignore it.`, e)
return
}
botLog.log(`${name} Start BotService.`)
const meId = (await mattermostClient.getClient().getMe()).id
const botService = new BotService(
mattermostClient,
meId,
name,
openAIWrapper,
botConfig.plugins ?? config.PLUGINS ?? process.env['PLUGINS'] ?? 'image-plugin graph-plugin',
)
mattermostClient.getWsClient().addMessageListener(e => botService.onClientMessage(e))
botLog.trace(`${name} Listening to MM messages...`)
botServices[name] = botService
})
// if ( Object.keys(botServices).length === 0 ) {
// botLog.error('No bot is configured. Exiting...')
// process.exit(-1)
// }
// botLog.log('All bots started.')
if (!name) {
botLog.error('No name. Ignore provider config', hideTokens(botConfig))
return
}
botConfig.name = name
if (!botConfig.type) {
// typeが無いので、どのAPI_KEYが環境変数で定義されているかで推測する
if (
process.env['AZURE_OPENAI_API_KEY'] ||
botConfig.apiVersion ||
botConfig.instanceName ||
botConfig.deploymentName
) {
botConfig.type = 'azure'
} else if (process.env['OPENAI_API_KEY']) {
botConfig.type = 'openai'
} else if (process.env['GOOGLE_API_KEY']) {
botConfig.type = 'google'
} else if (process.env['COHERE_API_KEY']) {
botConfig.type = 'cohere'
} else if (process.env['ANTHROPIC_API_KEY']) {
botConfig.type = 'anthropic'
} else {
botLog.error(`${name} No type. Ignore provider config`, hideTokens(botConfig))
return
}
botLog.warn(`${name} No type. Guessing type as ${botConfig.type}.`, hideTokens(botConfig))
}
botLog.log(`${name} Connected to Mattermost.`)
// AZURE_MATTERMOST_TOKEN, GOOGLE_MATTERMOST_TOKEN... を新設
const mattermostToken =
botConfig.mattermostToken ??
process.env[`${name.toUpperCase()}_MATTERMOST_TOKEN`] ??
process.env[`${botConfig.type.toUpperCase()}_MATTERMOST_TOKEN`] ??
process.env['MATTERMOST_TOKEN']
const mattermostClient = new MattermostClient(
botConfig.mattermostUrl ?? config.MATTERMOST_URL ?? process.env['MATTERMOST_URL'],
mattermostToken,
)
botLog.log(`${name} Start LLM wrapper.`)
let openAIWrapper: OpenAIWrapper
try {
openAIWrapper = new OpenAIWrapper(botConfig, mattermostClient)
} catch (e) {
botLog.error(`${name} Failed to create OpenAIWrapper. Ignore it.`, e)
return
}
botLog.log(`${name} Start BotService.`)
const meId = (await mattermostClient.getClient().getMe()).id
const botService = new BotService(
mattermostClient,
meId,
name,
openAIWrapper,
botConfig.plugins ?? config.PLUGINS ?? process.env['PLUGINS'] ?? 'image-plugin graph-plugin',
)
mattermostClient.getWsClient().addMessageListener(e => botService.onClientMessage(e))
botLog.trace(`${name} Listening to MM messages...`)
botServices[name] = botService
}),
)
if (Object.keys(botServices).length === 0) {
botLog.error('No bot is configured. Exiting...')
process.exit(-1)
}
botLog.log('All bots started.', Object.keys(botServices))

function hideTokens(botConfig: ProviderConfig) {
if (botConfig.mattermostToken) {
botConfig.mattermostToken = '***'
function hideTokens(botConfig: ProviderConfig): ProviderConfig {
const result = { ...botConfig }
if (result.mattermostToken) {
result.mattermostToken = '***'
}
if (botConfig.apiKey) {
botConfig.apiKey = '***'
if (result.apiKey) {
result.apiKey = '***'
}
if (botConfig.imageKey) {
botConfig.imageKey = '***'
if (result.imageKey) {
result.imageKey = '***'
}
if (botConfig.visionKey) {
botConfig.visionKey = '***'
if (result.visionKey) {
result.visionKey = '***'
}
return result
}
}

Expand Down

0 comments on commit d300de7

Please sign in to comment.