Skip to content

Commit c96b3af

Browse files
authored
chore(example): add ollama model for example (#56)
+ This PR will replace for #54 + Use model factory to create multiple models such as openai, ollama, ...
1 parent 82f2865 commit c96b3af

File tree

8 files changed

+2118
-2852
lines changed

8 files changed

+2118
-2852
lines changed

examples/telegram-bot/.env.example

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# OpenAI API Key
2-
OPENAI_API_KEY=
2+
#OPENAI_API_KEY=
33

44
# Polkadot RPC Endpoint
55
WS_ENDPOINT=
@@ -10,4 +10,6 @@ PRIVATE_KEY=
1010
# Telegram Bot Token
1111
TELEGRAM_BOT_TOKEN=
1212

13+
#
14+
1315

examples/telegram-bot/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@
2323
"telegraf": "^4.16.3",
2424
"telegraf-safe-md-reply": "^1.0.0",
2525
"@langchain/core": "^0.3.40",
26-
"@langchain/openai": "^0.3.17"
26+
"zod": "^3.22.0",
27+
"@langchain/ollama":"^0.2.2",
28+
"@langchain/openai":"^0.5.12"
2729
},
2830
"devDependencies": {
2931
"@types/jest": "^29.5.14",
Lines changed: 33 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,62 @@
1-
import { Telegraf } from 'telegraf';
2-
import { ChatOpenAI } from '@langchain/openai';
3-
import { Tool } from '@langchain/core/tools';
4-
import { setupHandlers } from './handlers';
5-
import { PolkadotAgentKit } from '@polkadot-agent-kit/sdk';
6-
import { getChainByName, KnownChainId, getAllSupportedChains } from '@polkadot-agent-kit/common';
7-
1+
import { Telegraf } from "telegraf";
2+
import { setupHandlers } from "./handlers";
3+
import { PolkadotAgentKit } from "@polkadot-agent-kit/sdk";
4+
import {
5+
ChatModelFactory,
6+
ChatModelOptions,
7+
ChatModelWithTools,
8+
} from "./models";
89

910
interface BotConfig {
1011
botToken: string;
1112
openAiApiKey?: string;
1213
privateKey?: string;
13-
// delegatePrivateKey?: string;
14-
// chains: { url: string; name: string; apiKey: string; type: 'RelayChain' | 'ParaChain'; paraId?: number }[];
1514
}
1615

1716
export class TelegramBot {
1817
private bot: Telegraf;
1918
private agent: PolkadotAgentKit;
20-
private llm: ChatOpenAI;
19+
private llm: ChatModelWithTools;
20+
21+
private initializeLLM(openAiApiKey?: string): ChatModelWithTools {
22+
const options: ChatModelOptions = {
23+
provider: openAiApiKey ? ("openai" as const) : ("ollama" as const),
24+
modelName: openAiApiKey ? "gpt-4o-mini" : "qwen3:latest",
25+
temperature: 0.7,
26+
verbose: false,
27+
};
28+
return ChatModelFactory.create(options);
29+
}
2130

2231
constructor(config: BotConfig) {
23-
const {
24-
botToken,
25-
openAiApiKey,
26-
privateKey,
27-
// delegatePrivateKey,
28-
// chains,
29-
} = config;
32+
const { botToken, openAiApiKey, privateKey } = config;
3033

3134
if (!botToken) {
32-
throw new Error('TELEGRAM_BOT_TOKEN must be provided!');
35+
throw new Error("TELEGRAM_BOT_TOKEN must be provided!");
3336
}
3437

3538
this.bot = new Telegraf(botToken);
3639

37-
this.agent = new PolkadotAgentKit(privateKey as string, {keyType: 'Sr25519'});
38-
39-
this.llm = new ChatOpenAI({
40-
modelName: 'gpt-4',
41-
temperature: 0.7,
42-
openAIApiKey: openAiApiKey,
43-
streaming: true,
40+
this.agent = new PolkadotAgentKit(privateKey as string, {
41+
keyType: "Sr25519",
4442
});
43+
44+
this.llm = this.initializeLLM(openAiApiKey);
4545
}
4646

4747
async initialize() {
4848
console.log("Initializing bot...");
49-
49+
5050
try {
5151
// Initialize APIs first
5252
await this.agent.initializeApi();
53-
54-
// Set up tools
53+
54+
// Set up tools
5555
// Get balance of agent account
5656
const checkBalance = this.agent.getNativeBalanceTool();
5757
// Transfer native tokens to a recipient address on a specific chain.
5858
const transferNative = this.agent.transferNativeTool();
59-
59+
6060
setupHandlers(this.bot, this.llm, {
6161
checkBalance: checkBalance,
6262
transferNative: transferNative,
@@ -69,17 +69,13 @@ export class TelegramBot {
6969
}
7070
}
7171

72-
73-
74-
7572
public async start(): Promise<void> {
7673
try {
7774
await this.initialize();
7875
await this.bot.launch();
79-
console.log('Bot is running!');
80-
76+
console.log("Bot is running!");
8177
} catch (error) {
82-
console.error('Failed to start bot:', error);
78+
console.error("Failed to start bot:", error);
8379
throw error;
8480
}
8581
}
@@ -89,7 +85,7 @@ export class TelegramBot {
8985
await this.agent.disconnect();
9086
this.bot.stop();
9187
} catch (error) {
92-
console.error('Error during shutdown:', error);
88+
console.error("Error during shutdown:", error);
9389
}
9490
}
95-
}
91+
}

examples/telegram-bot/src/handlers.ts

Lines changed: 25 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { Telegraf } from 'telegraf';
2-
import { HumanMessage, SystemMessage } from '@langchain/core/messages';
3-
import { ChatOpenAI } from '@langchain/openai';
4-
import { DynamicStructuredTool, Tool } from '@langchain/core/tools';
1+
import { Telegraf } from "telegraf";
2+
import { HumanMessage, SystemMessage } from "@langchain/core/messages";
3+
import { DynamicStructuredTool } from "@langchain/core/tools";
4+
import { ChatModelWithTools } from "./models";
55

66
const SYSTEM_PROMPT = `I am a Telegram bot powered by PolkadotAgentKit. I can assist you with:
77
- Transferring native tokens on specific chain (e.g., "transfer 1 WND to 5CSox4ZSN4SGLKUG9NYPtfVK9sByXLtxP4hmoF4UgkM4jgDJ on westend_asset_hub")
@@ -24,29 +24,26 @@ Please provide instructions, and I will assist you!`;
2424

2525
export function setupHandlers(
2626
bot: Telegraf,
27-
llm: ChatOpenAI,
27+
llm: ChatModelWithTools,
2828
toolsByName: Record<string, DynamicStructuredTool>,
2929
): void {
30-
3130
bot.start((ctx) => {
3231
ctx.reply(
33-
'Welcome to Polkadot Bot!\n' +
34-
'I can assist you with:\n' +
35-
'- Transferring native tokens (e.g., "transfer 1 token to westend_asset_hub to 5CSox4ZSN4SGLKUG9NYPtfVK9sByXLtxP4hmoF4UgkM4jgDJ")\n' +
36-
'- Checking balance (e.g., "check balance on west/polkadot/kusama")\n' +
37-
'- Checking proxies (e.g., "check proxies on westend" or "check proxies")\n' +
38-
'Try asking something!',
32+
"Welcome to Polkadot Bot!\n" +
33+
"I can assist you with:\n" +
34+
'- Transferring native tokens (e.g., "transfer 1 token to westend_asset_hub to 5CSox4ZSN4SGLKUG9NYPtfVK9sByXLtxP4hmoF4UgkM4jgDJ")\n' +
35+
'- Checking balance (e.g., "check balance on west/polkadot/kusama")\n' +
36+
'- Checking proxies (e.g., "check proxies on westend" or "check proxies")\n' +
37+
"Try asking something!",
3938
);
4039
});
4140

42-
43-
bot.on('text', async (ctx) => {
41+
bot.on("text", async (ctx) => {
4442
const message = ctx.message.text;
45-
46-
if (message.startsWith('/')) return;
4743

48-
try {
44+
if (message.startsWith("/")) return;
4945

46+
try {
5047
const llmWithTools = llm.bindTools(Object.values(toolsByName));
5148
const messages = [
5249
new SystemMessage({ content: SYSTEM_PROMPT }),
@@ -55,43 +52,43 @@ export function setupHandlers(
5552
const aiMessage = await llmWithTools.invoke(messages);
5653
if (aiMessage.tool_calls && aiMessage.tool_calls.length > 0) {
5754
for (const toolCall of aiMessage.tool_calls) {
58-
5955
const selectedTool = toolsByName[toCamelCase(toolCall.name)];
6056
if (selectedTool) {
6157
const toolMessage = await selectedTool.invoke(toolCall);
6258
if (!toolMessage || !toolMessage.content) {
63-
await ctx.reply('Tool did not return a response.');
59+
await ctx.reply("Tool did not return a response.");
6460
return;
6561
}
66-
const response = JSON.parse(toolMessage.content || '{}');
67-
62+
const response = JSON.parse(toolMessage.content || "{}");
6863
if (response.error) {
6964
await ctx.reply(`Error: ${response.message}`);
7065
} else {
71-
await ctx.reply(response.message || response.content || 'No message from tool.');
66+
const content = JSON.parse(response.content || "{}");
67+
await ctx.reply(content.data || "No message from tool.");
7268
}
7369
} else {
7470
console.warn(`Tool not found: ${toolCall.name}`);
7571
await ctx.reply(`Tool ${toolCall.name} not found.`);
7672
}
7773
}
7874
} else {
79-
const content = String(aiMessage.content || 'No response from LLM.');
75+
const content = String(aiMessage.content || "No response from LLM.");
8076
await ctx.reply(content);
8177
}
8278
} catch (error) {
83-
console.error('Error handling message:', error);
79+
console.error("Error handling message:", error);
8480
if (error instanceof Error) {
85-
console.error('Error stack:', error.stack);
81+
console.error("Error stack:", error.stack);
8682
}
87-
await ctx.reply(`Sorry, an error occurred: ${error instanceof Error ? error.message : String(error)}`);
83+
await ctx.reply(
84+
`Sorry, an error occurred: ${error instanceof Error ? error.message : String(error)}`,
85+
);
8886
}
8987
});
9088

91-
9289
bot.catch((err, ctx) => {
9390
console.error(`Error for ${ctx.updateType}`, err);
94-
ctx.reply('An error occurred. Please try again.');
91+
ctx.reply("An error occurred. Please try again.");
9592
});
9693
}
9794

examples/telegram-bot/src/index.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,19 @@
1+
import { TelegramBot } from "./TelegramBot";
12

2-
import { TelegramBot } from './TelegramBot';
3-
4-
import * as dotenv from 'dotenv';
3+
import * as dotenv from "dotenv";
54
dotenv.config();
65

76
async function runBot() {
87
const bot = new TelegramBot({
98
botToken: process.env.TELEGRAM_BOT_TOKEN!,
109
openAiApiKey: process.env.OPENAI_API_KEY!,
1110
privateKey: process.env.PRIVATE_KEY!,
12-
// delegatePrivateKey: process.env.DELEGATE_PRIVATE_KEY!,
1311
});
1412

1513
await bot.start();
1614

17-
process.once('SIGINT', () => bot.stop());
18-
process.once('SIGTERM', () => bot.stop());
15+
process.once("SIGINT", () => bot.stop());
16+
process.once("SIGTERM", () => bot.stop());
1917
}
2018

21-
runBot();
19+
runBot();
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { BaseChatModel } from "@langchain/core/language_models/chat_models";
2+
import { ChatOpenAI } from "@langchain/openai";
3+
import { ChatOllama } from "@langchain/ollama";
4+
5+
export type ChatProvider = "ollama" | "openai";
6+
7+
export type ChatModelWithTools = BaseChatModel & {
8+
bindTools: (tools: any[]) => any;
9+
};
10+
11+
export interface ChatModelOptions {
12+
provider: ChatProvider;
13+
temperature?: number;
14+
modelName?: string;
15+
verbose?: boolean;
16+
}
17+
18+
const chatModelConstructors: Record<
19+
ChatProvider,
20+
(options: ChatModelOptions) => ChatModelWithTools
21+
> = {
22+
openai: ({ modelName, temperature = 0.7, verbose = false }) =>
23+
new ChatOpenAI({
24+
modelName: modelName ?? "gpt-4o-mini",
25+
temperature,
26+
streaming: true,
27+
openAIApiKey: process.env.OPENAI_API_KEY!,
28+
verbose,
29+
}),
30+
ollama: ({ modelName, temperature = 0.7, verbose = false }) =>
31+
new ChatOllama({
32+
model: modelName ?? "llama3",
33+
temperature,
34+
verbose,
35+
}),
36+
};
37+
38+
export class ChatModelFactory {
39+
static create(options: ChatModelOptions): ChatModelWithTools {
40+
const { provider } = options;
41+
const constructor = chatModelConstructors[provider];
42+
if (!constructor) {
43+
throw new Error(`Unsupported provider: ${provider}`);
44+
}
45+
return constructor(options);
46+
}
47+
}

packages/llm/package.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,8 @@
3737
"dependencies": {
3838
"@polkadot-agent-kit/common": "workspace:*",
3939
"@polkadot-agent-kit/core": "workspace:*",
40-
"@langchain/community": "^0.3.19",
4140
"@langchain/core": "^0.3.40",
4241
"@langchain/langgraph": "^0.2.33",
43-
"@langchain/openai": "^0.3.17",
4442
"@noble/curves": "^1.6.0",
4543
"@polkadot-labs/hdkd": "^0.0.13",
4644
"@polkadot-labs/hdkd-helpers": "^0.0.13",

0 commit comments

Comments
 (0)