mirror of
https://github.com/run-llama/flow-maker.git
synced 2026-07-19 23:13:58 -04:00
Prompt LLM works now
This commit is contained in:
@@ -1,10 +1,8 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { OpenAI } from '@llamaindex/openai';
|
||||
import { Anthropic } from '@llamaindex/anthropic';
|
||||
import { Gemini, GEMINI_MODEL } from '@llamaindex/google';
|
||||
import { Settings, LLM, tool } from 'llamaindex';
|
||||
import { agent } from '@llamaindex/workflow';
|
||||
import { z } from 'zod';
|
||||
import { getLlm } from '@/lib/llm-utils';
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
@@ -50,31 +48,7 @@ export async function POST(req: NextRequest) {
|
||||
}
|
||||
|
||||
// 2. Configure LLM
|
||||
const llmProvider = settings?.defaultLLM || 'gpt-4-turbo';
|
||||
let llm;
|
||||
let llmApiKey: string | undefined;
|
||||
|
||||
if (llmProvider.startsWith('gpt')) {
|
||||
llmApiKey = settings?.apiKeys?.openai;
|
||||
llm = new OpenAI({ model: 'gpt-4.1-mini', apiKey: llmApiKey });
|
||||
} else if (llmProvider.startsWith('claude')) {
|
||||
llmApiKey = settings?.apiKeys?.anthropic;
|
||||
llm = new Anthropic({
|
||||
model: 'claude-sonnet-4-20250514',
|
||||
apiKey: llmApiKey,
|
||||
});
|
||||
} else if (llmProvider.startsWith('gemini')) {
|
||||
llmApiKey = settings?.apiKeys?.google;
|
||||
llm = new Gemini({
|
||||
model: GEMINI_MODEL.GEMINI_PRO_LATEST,
|
||||
apiKey: llmApiKey,
|
||||
});
|
||||
} else {
|
||||
return NextResponse.json(
|
||||
{ error: `Unsupported LLM provider: ${llmProvider}` },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
const llm = getLlm(settings);
|
||||
|
||||
// 3. Create dynamic tools for the agent
|
||||
const tools: any[] = [];
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { getLlm } from "@/lib/llm-utils";
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
const body = await req.json();
|
||||
const { input, node, settings } = body;
|
||||
|
||||
if (!input || !node || !settings) {
|
||||
return NextResponse.json({ error: "Missing required parameters" }, { status: 400 });
|
||||
}
|
||||
|
||||
const llm = getLlm(settings, node);
|
||||
|
||||
const response = await llm.chat({
|
||||
messages: [{ role: "user", content: typeof input === 'string' ? input : JSON.stringify(input) }]
|
||||
});
|
||||
|
||||
return NextResponse.json({ output: response.message.content });
|
||||
|
||||
} catch (error: any) {
|
||||
return NextResponse.json({ error: error.message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ interface AgentBuilderSettingsProps {
|
||||
}
|
||||
|
||||
const AgentBuilderSettings = memo(({ settings, onUpdateSettings }: AgentBuilderSettingsProps) => {
|
||||
const [isClient, setIsClient] = useState(false);
|
||||
const [showApiKeys, setShowApiKeys] = useState({
|
||||
llamaCloud: false,
|
||||
openai: false,
|
||||
@@ -62,6 +63,10 @@ const AgentBuilderSettings = memo(({ settings, onUpdateSettings }: AgentBuilderS
|
||||
}));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setIsClient(true);
|
||||
}, []);
|
||||
|
||||
// Get the current API key based on selected LLM
|
||||
const getCurrentApiKey = () => {
|
||||
switch (settings.defaultLLM) {
|
||||
@@ -119,6 +124,10 @@ const AgentBuilderSettings = memo(({ settings, onUpdateSettings }: AgentBuilderS
|
||||
}
|
||||
};
|
||||
|
||||
if (!isClient) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className="p-3 space-y-4">
|
||||
{/* LlamaCloud API Key */}
|
||||
|
||||
@@ -210,7 +210,7 @@ const AgentFlowInner = ({ nodes, edges, onNodesChange, onEdgesChange, setNodes,
|
||||
|
||||
<AgentBuilderSidebar onAddNode={onAddNode} onReset={onReset} settings={settings} onUpdateSettings={onUpdateSettings} />
|
||||
|
||||
<div className="flex-1" ref={reactFlowWrapper}>
|
||||
<div className="flex-1 h-full">
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
@@ -222,6 +222,7 @@ const AgentFlowInner = ({ nodes, edges, onNodesChange, onEdgesChange, setNodes,
|
||||
nodeTypes={nodeTypes}
|
||||
deleteKeyCode={['Backspace', 'Delete']}
|
||||
fitView
|
||||
proOptions={{ hideAttribution: true }}
|
||||
className="bg-flow-bg"
|
||||
defaultEdgeOptions={{
|
||||
type: 'default',
|
||||
|
||||
@@ -74,6 +74,28 @@ const loadSavedGraph = () => {
|
||||
};
|
||||
};
|
||||
|
||||
const getMessageContent = (data: any): string => {
|
||||
if (typeof data === 'string') {
|
||||
return data;
|
||||
}
|
||||
if (Array.isArray(data) && data.length > 0 && data[0].type === 'text' && typeof data[0].text === 'string') {
|
||||
return data[0].text;
|
||||
}
|
||||
return JSON.stringify(data, null, 2);
|
||||
}
|
||||
|
||||
const loadSavedSettings = () => {
|
||||
try {
|
||||
const savedSettings = localStorage.getItem('agent-builder-settings');
|
||||
if (savedSettings) {
|
||||
return JSON.parse(savedSettings);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading saved settings:', error);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
const RunViewInner = () => {
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState<Node>([]);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState<Edge>([]);
|
||||
@@ -158,21 +180,61 @@ const RunViewInner = () => {
|
||||
// The workflow will now wait for user input.
|
||||
// The onUserInput function will be called when the user submits a message.
|
||||
return; // Don't proceed further until user input
|
||||
case 'promptLLM':
|
||||
// This will be handled similarly to promptAgent
|
||||
const llmOutput = `Mock output from ${node.data.label}`;
|
||||
case 'promptLLM': {
|
||||
// find the previous node to get the input
|
||||
const incomingEdge = edges.find((e) => e.target === nodeId);
|
||||
const parentNodeId = incomingEdge?.source;
|
||||
const input = parentNodeId ? workflowStateRef.current[parentNodeId] : null;
|
||||
|
||||
if (!input) {
|
||||
setError(`Input for node ${nodeId} not found.`);
|
||||
setExecutionStatus('error');
|
||||
return;
|
||||
}
|
||||
|
||||
const thinkingMessageId = Math.random().toString();
|
||||
setMessages((prev) => [
|
||||
...prev,
|
||||
{
|
||||
id: Math.random().toString(),
|
||||
id: thinkingMessageId,
|
||||
role: 'assistant',
|
||||
content: `Executing ${node.data.label}...`,
|
||||
content: `Thinking with ${node.data.label}...`,
|
||||
},
|
||||
]);
|
||||
|
||||
const llmResponse = await fetch('/api/llm/call', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
input: input,
|
||||
node: node,
|
||||
settings: loadSavedSettings()
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!llmResponse.ok) {
|
||||
throw new Error(
|
||||
`LLM execution failed: ${await llmResponse.text()}`,
|
||||
);
|
||||
}
|
||||
|
||||
const { output: llmOutput } = await llmResponse.json();
|
||||
|
||||
setWorkflowState((prevState) => ({
|
||||
...prevState,
|
||||
[nodeId]: llmOutput,
|
||||
}));
|
||||
|
||||
setMessages((prev) =>
|
||||
prev.map((msg) =>
|
||||
msg.id === thinkingMessageId
|
||||
? { ...msg, content: getMessageContent(llmOutput) }
|
||||
: msg
|
||||
)
|
||||
);
|
||||
|
||||
const llmEdge = edges.find((e) => e.source === nodeId);
|
||||
if (llmEdge) {
|
||||
nextNodeId = llmEdge.target;
|
||||
@@ -180,6 +242,7 @@ const RunViewInner = () => {
|
||||
setExecutionStatus('finished');
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'promptAgent':
|
||||
const compiledWorkflow = compileWorkflow(nodes, edges);
|
||||
const response = await fetch('/api/agent/run', {
|
||||
@@ -207,7 +270,7 @@ const RunViewInner = () => {
|
||||
{
|
||||
id: Math.random().toString(),
|
||||
role: 'assistant',
|
||||
content: output,
|
||||
content: getMessageContent(output),
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -305,6 +368,7 @@ const RunViewInner = () => {
|
||||
nodesDraggable={false}
|
||||
nodesConnectable={false}
|
||||
elementsSelectable={false}
|
||||
proOptions={{ hideAttribution: true }}
|
||||
className="bg-flow-bg"
|
||||
defaultEdgeOptions={{
|
||||
type: 'default',
|
||||
|
||||
@@ -1,67 +1,21 @@
|
||||
import { memo } from 'react';
|
||||
import { Handle, Position, useReactFlow } from '@xyflow/react';
|
||||
import { Handle, Position } from '@xyflow/react';
|
||||
import { Brain } from 'lucide-react';
|
||||
|
||||
interface PromptLLMNodeProps {
|
||||
id: string;
|
||||
data: {
|
||||
label?: string;
|
||||
model?: string;
|
||||
temperature?: number;
|
||||
};
|
||||
selected?: boolean;
|
||||
}
|
||||
|
||||
const PromptLLMNode = memo(({ id, data, selected }: PromptLLMNodeProps) => {
|
||||
const { setNodes } = useReactFlow();
|
||||
|
||||
const handleModelChange = (value: string) => {
|
||||
setNodes((nodes) =>
|
||||
nodes.map((node) =>
|
||||
node.id === id
|
||||
? { ...node, data: { ...node.data, model: value } }
|
||||
: node
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const handleTemperatureChange = (value: string) => {
|
||||
const temperature = value === '' ? undefined : parseFloat(value);
|
||||
setNodes((nodes) =>
|
||||
nodes.map((node) =>
|
||||
node.id === id
|
||||
? { ...node, data: { ...node.data, temperature } }
|
||||
: node
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`agent-node node-llm ${selected ? 'selected' : ''}`}>
|
||||
<div className="node-content flex flex-col items-center justify-center text-foreground p-4 min-w-[180px]">
|
||||
<Brain className="w-5 h-5 mb-2" />
|
||||
<span className="text-sm font-medium mb-2">{data.label || 'Prompt LLM'}</span>
|
||||
<div className="w-full space-y-1">
|
||||
<input
|
||||
type="text"
|
||||
value={data.model || ''}
|
||||
placeholder="Model (e.g., gpt-4)"
|
||||
className="w-full px-2 py-1 text-xs bg-muted border border-border rounded text-foreground placeholder-muted-foreground"
|
||||
onChange={(e) => handleModelChange(e.target.value)}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
<input
|
||||
type="number"
|
||||
value={data.temperature !== undefined ? data.temperature.toString() : ''}
|
||||
placeholder="Temperature"
|
||||
min="0"
|
||||
max="2"
|
||||
step="0.1"
|
||||
className="w-full px-2 py-1 text-xs bg-muted border border-border rounded text-foreground placeholder-muted-foreground"
|
||||
onChange={(e) => handleTemperatureChange(e.target.value)}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Handle type="target" position={Position.Top} />
|
||||
<Handle type="source" position={Position.Bottom} />
|
||||
@@ -71,4 +25,4 @@ const PromptLLMNode = memo(({ id, data, selected }: PromptLLMNodeProps) => {
|
||||
|
||||
PromptLLMNode.displayName = 'PromptLLMNode';
|
||||
|
||||
export default PromptLLMNode;
|
||||
export default PromptLLMNode;
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { OpenAI } from "@llamaindex/openai";
|
||||
import { Anthropic } from "@llamaindex/anthropic";
|
||||
import { Gemini, GEMINI_MODEL } from "@llamaindex/google";
|
||||
import { LLM } from "llamaindex";
|
||||
|
||||
export function getLlm(settings: any, nodeData: any = {}): LLM {
|
||||
const llmProvider = settings?.defaultLLM || "gpt-4o";
|
||||
let llm: LLM;
|
||||
let llmApiKey: string | undefined;
|
||||
|
||||
const model = nodeData?.data?.model;
|
||||
const temperature = nodeData?.data?.temperature;
|
||||
|
||||
if (llmProvider.startsWith("gpt")) {
|
||||
llmApiKey = settings?.apiKeys?.openai;
|
||||
llm = new OpenAI({
|
||||
model: model || "gpt-4.1-mini",
|
||||
temperature: temperature ?? 0.2,
|
||||
apiKey: llmApiKey
|
||||
});
|
||||
} else if (llmProvider.startsWith("claude")) {
|
||||
llmApiKey = settings?.apiKeys?.anthropic;
|
||||
llm = new Anthropic({
|
||||
model: model || "claude-sonnet-4-20250514",
|
||||
temperature: temperature ?? 0.2,
|
||||
apiKey: llmApiKey,
|
||||
});
|
||||
} else if (llmProvider.startsWith("gemini")) {
|
||||
llmApiKey = settings?.apiKeys?.google;
|
||||
llm = new Gemini({
|
||||
model: model || "gemini-pro",
|
||||
temperature: temperature ?? 0.2,
|
||||
apiKey: llmApiKey,
|
||||
});
|
||||
} else {
|
||||
throw new Error(`Unsupported LLM provider: ${llmProvider}`);
|
||||
}
|
||||
return llm;
|
||||
}
|
||||
@@ -29,14 +29,17 @@ const getEventName = (eventId: string) => eventId.replace(/-/g, "_");
|
||||
const generateImports = (json: WorkflowJson): string => {
|
||||
const imports = new Set<string>();
|
||||
const hasPromptAgent = json.nodes.some((node) => node.type === "promptAgent");
|
||||
const hasPromptLLM = json.nodes.some((node) => node.type === "promptLLM");
|
||||
const hasTools = json.nodes.some(
|
||||
(node: any) =>
|
||||
node.type === "promptAgent" && node.tools && node.tools.length > 0,
|
||||
);
|
||||
const hasUserInput = json.nodes.some((node) => node.type === "userInput");
|
||||
|
||||
if (hasPromptAgent) {
|
||||
imports.add('import { agent } from "@llamaindex/workflow";');
|
||||
if (hasPromptAgent || hasPromptLLM) {
|
||||
if (hasPromptAgent) {
|
||||
imports.add('import { agent } from "@llamaindex/workflow";');
|
||||
}
|
||||
const llmProvider = json.settings?.defaultLLM || "gpt-4o";
|
||||
if (llmProvider.startsWith("gpt")) {
|
||||
imports.add('import { OpenAI } from "@llamaindex/openai";');
|
||||
@@ -87,7 +90,7 @@ const generateLlmInit = (json: WorkflowJson): string => {
|
||||
});
|
||||
}
|
||||
|
||||
if (json.nodes.some((node) => node.type === "promptAgent")) {
|
||||
if (json.nodes.some((node) => node.type === "promptAgent" || node.type === "promptLLM")) {
|
||||
const llmProvider = json.settings?.defaultLLM || "gpt-4o";
|
||||
if (llmProvider.startsWith("gpt")) {
|
||||
lines.push(
|
||||
@@ -231,8 +234,9 @@ const generateEvents = (nodes: WorkflowNodeJson[]): string => {
|
||||
.join("\n");
|
||||
};
|
||||
|
||||
const generateHandlers = (nodes: WorkflowNodeJson[]): string => {
|
||||
const generateHandlers = (json: WorkflowJson): string => {
|
||||
let handlerLines: string[] = [];
|
||||
const { nodes } = json;
|
||||
|
||||
for (const node of nodes) {
|
||||
if (node.type === "stop") {
|
||||
@@ -276,6 +280,36 @@ const generateHandlers = (nodes: WorkflowNodeJson[]): string => {
|
||||
(node as any).prompt?.replace(/`/g, "\\`") || "Enter your input:";
|
||||
handlerBody = `
|
||||
return ${needInputEventName}.with("${prompt}");`;
|
||||
} else if (node.type === "promptLLM") {
|
||||
const nodeData = node as any;
|
||||
const model = nodeData.data?.model;
|
||||
const temperature = nodeData.data?.temperature;
|
||||
const settings = json.settings;
|
||||
const defaultLLM = settings?.defaultLLM || "gpt-4o";
|
||||
|
||||
let llmToUse = "llm";
|
||||
let llmDefinition = "";
|
||||
|
||||
if (model || typeof temperature !== "undefined") {
|
||||
const llmVar = `llm_${node.id.replace(/-/g, "_")}`;
|
||||
llmToUse = llmVar;
|
||||
const defaultModelForProvider = defaultLLM.startsWith("gpt")
|
||||
? "gpt-4.1-mini"
|
||||
: defaultLLM.startsWith("claude")
|
||||
? "claude-sonnet-4-20250514"
|
||||
: "gemini-2.5-pro-latest";
|
||||
const finalModel = model || defaultModelForProvider;
|
||||
const tempValue = typeof temperature !== "undefined" ? temperature : 0.2;
|
||||
|
||||
let llmClass = "OpenAI";
|
||||
if (defaultLLM.startsWith("claude")) llmClass = "Anthropic";
|
||||
else if (defaultLLM.startsWith("gemini")) llmClass = "Gemini";
|
||||
llmDefinition = ` const ${llmVar} = new ${llmClass}({ model: "${finalModel}", temperature: ${tempValue} });`;
|
||||
}
|
||||
handlerBody = `
|
||||
${llmDefinition}
|
||||
const result = await ${llmToUse}.chat({ messages: [{ role: "user", content: typeof ctx.data === 'string' ? ctx.data : JSON.stringify(ctx.data) }]});
|
||||
return ${nextEventName}.with(result.message.content);`;
|
||||
} else if (node.type === "promptAgent") {
|
||||
const toolNames =
|
||||
(node as any).tools?.map(
|
||||
@@ -366,7 +400,7 @@ export const generateTypescript = (json: WorkflowJson): string => {
|
||||
const llmInit = generateLlmInit(json);
|
||||
const tools = generateTools(json);
|
||||
const events = generateEvents(json.nodes);
|
||||
const handlers = generateHandlers(json.nodes);
|
||||
const handlers = generateHandlers(json);
|
||||
const execution = generateExecution("startEvent", "stopEvent", json.nodes);
|
||||
|
||||
return LlamaIndexTemplate(
|
||||
|
||||
Reference in New Issue
Block a user