Skip to content

Commit

Permalink
🔨 Moving tools to external files, managing quick replies better
Browse files Browse the repository at this point in the history
  • Loading branch information
MiloradFilipovic committed May 22, 2024
1 parent 0a4c8a6 commit 51b980b
Show file tree
Hide file tree
Showing 7 changed files with 69 additions and 25 deletions.
30 changes: 30 additions & 0 deletions packages/cli/src/aiAssistant/history/chat_history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,44 @@
// TODO:
// - Add sessions support
// - We can use UserMessage and SystemMessage classes to make it more readable

import { QUICK_ACTIONS } from "../prompts/debug_prompts";

// but in the end it has to render to a string
export let chatHistory: string[] = [];
export const stringifyHistory = (history: string[]) => history.join('\n');

export const usedQuickActions: Record<string, number> ={
...QUICK_ACTIONS.reduce((acc, { label }) => ({ ...acc, [label]: 0 }), {}),
}

export const increaseSuggestionCounter = (label: string) => {
if (label in usedQuickActions) {
usedQuickActions[label]++;
}
}

export const resetSuggestionsCounter = (label: string) => {
if (label in usedQuickActions) {
usedQuickActions[label] = 0;
}
}

export const checkIfAllQuickActionsUsed = () => {
// Check if any of the quick actions have been used more than three times
return QUICK_ACTIONS.some(({ label }) => usedQuickActions[label] >= 3);
}

export const getHumanMessages = (history: string[]) => {
return history.filter((message, index) => message.startsWith('Human:'));
};

export const clearChatHistory = () => {
chatHistory = [];
for (const key in usedQuickActions) {
usedQuickActions[key] = 0;
}
QUICK_ACTIONS.forEach((action) => {
action.disabled = false;
})
}
16 changes: 9 additions & 7 deletions packages/cli/src/aiAssistant/prompts/debug_prompts.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
export const QUICK_ACTIONS = [
{ label: 'I need more detailed instructions', key: 'more_details' },
{ label: 'I need another suggestion', key: 'another_suggestion' }
import { QuickAction } from "../types";

export const QUICK_ACTIONS: QuickAction[] = [
{ label: 'Yes, help me fix the issue', key: 'more_details', disabled: false},
{ label: 'No, try something else', key: 'another_suggestion', disabled: false},
];

export const DEBUG_CONVERSATION_RULES = `
Expand All @@ -9,10 +11,10 @@ export const DEBUG_CONVERSATION_RULES = `
- 'suggestionTitle': Suggestion title
- 'suggestionText': Must be limited to one sentence. Must not contain any code snippets or detailed instructions.
3. User will always respond to the suggestion with one of the following, so make sure to formulate the suggestion accordingly:
- "I need more detailed instructions"
- "I need another suggestion"
4. If the user responds that they need more detailed instructions, assistant must use the available tools to provide more detailed instructions. These instructions must come from n8n documentation or other official n8n sources.
5. If the user responds that they need another suggestion, start the process again from step 1 but follow also the following rules:
- "Yes, help me fix the issue"
- "No, try something else"
4. If the user responds that they need help (yes), assistant must provide official n8n step-by-step instructions on how to solve the problem.
5. If the user responds that they need another suggestion (no), start the process again from step 1 but follow also the following rules:
- At this point, assistant must use it's tools to formulate a new suggestion
- Each new suggestion must be different from the previous ones and must provide a new direction to the user.
- Assistant must stop providing suggestions after it has provided three suggestions to the user. This is very important for keeping the conversation focused and efficient.
Expand Down
7 changes: 7 additions & 0 deletions packages/cli/src/aiAssistant/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// TODO: Add more types from the ai controller

export type QuickAction = {
label: string;
key: string;
disabled: boolean;
};
32 changes: 20 additions & 12 deletions packages/cli/src/controllers/ai.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ import zodToJsonSchema from 'zod-to-json-schema';
import { ChatMessageHistory } from 'langchain/stores/message/in_memory';
import { ApplicationError } from 'n8n-workflow';
import { QUICK_ACTIONS, REACT_DEBUG_PROMPT } from '@/aiAssistant/prompts/debug_prompts';
import { chatHistory, clearChatHistory, getHumanMessages, stringifyHistory } from '@/aiAssistant/history/chat_history';
import { chatHistory, checkIfAllQuickActionsUsed, clearChatHistory, getHumanMessages, increaseSuggestionCounter, stringifyHistory, usedQuickActions } from '@/aiAssistant/history/chat_history';
import { resetToolHistory, toolHistory } from '@/aiAssistant/history/tool_history';
import { n8nInfoTool, searchDocsVectorStore } from '@/aiAssistant/tools/n8n_docs_tool';
import { internetSearchTool } from '@/aiAssistant/tools/internet_search_tool';
import { n8nInfoTool, searchDocsVectorStore } from '@/aiAssistant/tools/n8n_docs.tool';
import { internetSearchTool } from '@/aiAssistant/tools/internet_search.tool';

const errorSuggestionSchema = z.object({
suggestion: z.object({
Expand Down Expand Up @@ -185,17 +185,13 @@ export class AIController {
const toolNames = tools.map((tool) => tool.name);

// ----------------- Agent -----------------
// Different prompts for debug and free-chat modes
const chatPrompt = debug ? ChatPromptTemplate.fromTemplate(REACT_DEBUG_PROMPT) : ChatPromptTemplate.fromTemplate(REACT_CHAT_PROMPT);
// Different conversation rules for debug and free-chat modes
const humanAskedForSuggestions = getHumanMessages(chatHistory).filter((msg) => {
return (
msg.includes('I need another suggestion') ||
msg.includes('I need more detailed instructions')
);
});

// Hard-stop if human asks for too many suggestions
if (humanAskedForSuggestions.length >= 3) {
increaseSuggestionCounter(message.trim());
const noMoreHelp = checkIfAllQuickActionsUsed();
if (noMoreHelp) {
if (debug) {
message =
'I have asked for too many new suggestions. Please follow your conversation rules for this case.';
Expand Down Expand Up @@ -245,7 +241,19 @@ export class AIController {
debugInfo += toolHistory.n8n_documentation.length > 0 ? `N8N DOCS DOCUMENTS USED: ${toolHistory.n8n_documentation.join(', ')}\n` : '';
debugInfo += toolHistory.internet_search.length > 0 ? `FORUM PAGES USED: ${toolHistory.internet_search.join(',')}\n` : '';
debugInfo += toolHistory.n8n_documentation.length === 0 && toolHistory.internet_search.length === 0 ? 'NO TOOLS USED' : '';
res.end(JSON.stringify({ response, debugInfo, quickActions: debug ? QUICK_ACTIONS : undefined }));

// If users asked for detailed information already, don't show it again until they ask for another suggestion
const quickActions = debug ? QUICK_ACTIONS : undefined;
if (quickActions) {
if (usedQuickActions[QUICK_ACTIONS[0].label] > 0) {
QUICK_ACTIONS[0].disabled = true;
}
}
if (message.trim() === QUICK_ACTIONS[1].label) {
QUICK_ACTIONS[0].disabled = false;
}

res.end(JSON.stringify({ response, debugInfo, quickActions: noMoreHelp ? [] : quickActions}));
}

@Post('/debug-chat', { skipAuth: true })
Expand Down
9 changes: 3 additions & 6 deletions packages/editor-ui/src/stores/ai.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -429,7 +429,7 @@ export const useAIStore = defineStore('ai', () => {
let jsonResponse: {
response?: string;
debugInfo?: string;
quickActions?: Array<{ label: string; value: string }>;
quickActions?: Array<{ label: string; value: string; disabled: boolean }>;
} | null = null;
try {
jsonResponse = JSON.parse(messageChunk);
Expand Down Expand Up @@ -470,10 +470,7 @@ export const useAIStore = defineStore('ai', () => {
}

if (jsonResponse?.quickActions) {
// const followUpActions = [
// { label: 'I need more detailed instructions', key: 'more_details' },
// { label: 'I need another suggestion', key: 'another_suggestion' },
// ];
const quickReplies = jsonResponse?.quickActions.filter((action) => !action.disabled);
const newMessageId = Math.random().toString();
messages.value.push({
createdAt: new Date().toISOString(),
Expand All @@ -483,7 +480,7 @@ export const useAIStore = defineStore('ai', () => {
type: 'component',
id: newMessageId,
arguments: {
suggestions: jsonResponse.quickActions,
suggestions: quickReplies,
async onReplySelected({ label, key }: { action: string; label: string }) {
await sendMessage(label);
// Remove the quick replies so only user message is shown
Expand Down

0 comments on commit 51b980b

Please sign in to comment.