Build a composable AI Devin

A step-by-step guide to build an AI coding agent using Langbase SDK.


In this guide, you will build an AI coding agent, CodeAlchemist aka Devin, that uses multiple pipe agents to:

  • Analyze the user prompt to identify if it's related to coding, database architecture, or a random prompt.
  • ReAct based architecture that decides whether to call code pipe agent or database pipe agent.
  • Generate raw React code for code query.
  • Generate optimized SQL for database query.


You will create a basic Next.js application that will use the Langbase SDK to connect to the pipe agents and stream the final response back to user.

Let's get started!


Step #0Setup your project

Let's quickly set up the project.

Initialize the project

To build the agent, you need to have a Next.js starter application. If you don't have one, you can create a new Next.js application using the following command:

Initialize project

npx create-next-app@latest code-alchemist

This will create a new Next.js application in the code-alchemist directory. Navigate to the directory and start the development server:

Run dev server

npm run dev

Install dependencies

Install the Langbase SDK in this project using npm or pnpm.

Run dev server

npm install langbase

Step #1Get Langbase API Key

Every request you send to Langbase needs an API key. This guide assumes you already have one. In case, you do not have an API key, please check the instructions below.

Create an .env file in the root of your project and add your Langbase API key.

.env

LANGBASE_API_KEY=xxxxxxxxx

Replace xxxxxxxxx with your Langbase API key.

Step #2Fork agent pipes

Fork the following agent pipes in Langbase studio. These agent pipes will power CodeAlchemist:

  • Code Alchemist: Decision maker pipe agent. Analyze user prompt and decide which pipe agent to call.
  • React Copilot: Generates a single React component for a given user prompt.
  • Database Architect: Generates optimized SQL for a query or entire database schema

Step #3Create a Generate API Route

Create a new file app/api/generate/route.ts. This API route will generate the AI response for the user prompt. Please add the following code:

API route

import { runCodeGenerationAgent } from '@/utils/run-agents';
import { validateRequestBody } from '@/utils/validate-request-body';

export const runtime = 'edge';

/**
 * Handles the POST request for the specified route.
 *
 * @param req - The request object.
 * @returns A response object.
 */
export async function POST(req: Request) {
	try {
		const { prompt, error } = await validateRequestBody(req);

		if (error || !prompt) {
			return new Response(JSON.stringify(error), { status: 400 });
		}

		const { stream, pipe } = await runCodeGenerationAgent(prompt);

		if (stream) {
			return new Response(stream, {
				headers: {
					pipe
				}
			});
		}

		return new Response(JSON.stringify({ error: 'No stream' }), {
			status: 500
		});
	} catch (error: any) {
		console.error('Uncaught API Error:', error.message);
		return new Response(JSON.stringify(error.message), { status: 500 });
	}
}

Here is a quick explanation of what's happening in the code above:

  • Import runCodeGenerationAgent function from run-agents.ts file.
  • Import validateRequestBody function from validate-request-body.ts file.
  • Define the POST function that handles the POST request made to the /api/generate endpoint.
  • Validate the request body using the validateRequestBody function.
  • Call the runCodeGenerationAgent function with the user prompt.
  • Return the response stream and pipe name.

You can find all these functions in the utils directory of the CodeAlchemist source code.

Step #4Decision Maker Pipe

You are building a ReAct based architecture which means the sytem first reason over the info it has and then decide to act.

The Code Alchemist agent pipe is a decision-making agent. It contains two tool calls, runPairProgrammer and runDatabaseArchitect, which are called depending on the user's query.

Create a new file app/utils/run-agents.ts. This file will contain the logic to call all the pipe agents and stream the final response back to the user. Please add the following code:

run-agents.ts

import 'server-only';
import { Langbase, getToolsFromRunStream } from 'langbase';

const langbase = new Langbase({
	apiKey: process.env.LANGBASE_API_KEY!
});

/**
 * Runs a code generation agent with the provided prompt.
 *
 * @param prompt - The input prompt for code generation
 * @returns A promise that resolves to an object containing:
 *          - pipe: The name of the pipe used for generation
 *          - stream: The output stream containing the generated content
 *
 * @remarks
 * This function processes the prompt through a langbase pipe and handles potential tool calls.
 * If tool calls are detected, it delegates to the appropriate PipeAgent.
 * Otherwise, it returns the direct output from the code-alchemist pipe.
 */
export async function runCodeGenerationAgent(prompt: string) {
	const {stream} = await langbase.pipes.run({
		stream: true,
		name: 'code-alchemist',
		messages: [{ role: 'user', content: prompt }],
	})

	const [streamForCompletion, streamForReturn] = stream.tee();

	const toolCalls = await getToolsFromRunStream(streamForCompletion);
	const hasToolCalls = toolCalls.length > 0;

	if(hasToolCalls) {
		const toolCall = toolCalls[0];
		const toolName = toolCall.function.name;
		const response = await PipeAgents[toolName](prompt);

		return {
			pipe: response.pipe,
			stream: response.stream
		};
	} else {
		return {
			pipe: 'code-alchemist',
			stream: streamForReturn
		}
	}
}

type PipeAgentsT = Record<string, (prompt: string) => Promise<{
	stream: ReadableStream<any>,
	pipe: string
}>>;

export const PipeAgents: PipeAgentsT = {
	runPairProgrammer: runPairProgrammerAgent,
	runDatabaseArchitect: runDatabaseArchitectAgent
};

async function runPairProgrammerAgent(prompt: string) {}

async function runDatabaseArchitectAgent(prompt: string) {}

Let's go through the above code.

  • Import Langbase and getToolsFromRunStream from the Langbase SDK.
  • Initialize the Langbase SDK with the API key.
  • Define the runCodeGenerationAgent function that sends the prompt through the code-alchemist agent.
  • Inside runCodeGenerationAgent, get the tools from the stream using the getToolsFromRunStream function.
  • Check if there are any tool calls in the stream.
  • If there are tool calls, call the appropriate pipe agent. In this example, only one pipe agent will be called by the decision-making agent.
  • If there are no tool calls, return the direct output from the code-alchemist pipe.

Step #5React Copilot Pipe

The React Copilot is a code generation pipe agent. It takes the user prompt and generates a React component based on it. It also writes clear and concise comments and use Tailwind CSS classes for styling.

You have already defined runPairProgrammerAgent in the previous step. Let's write its implementation. Add the following code to the run-agents.ts file:

runPairProgrammerAgent function code

/**
 * Executes a pair programmer agent with React-specific configuration.
 *
 * @param prompt - The user input prompt to be processed by the agent
 * @returns {Promise<{stream: ReadableStream, pipe: string}>} An object containing:
 *   - stream: A ReadableStream of the agent's response
 *   - pipe: The name of the pipe being used ('react-copilot')
 *
 * @example
 * const result = await runPairProgrammerAgent("Create a React button component");
 * // Use the returned stream to process the agent's response
 */
async function runPairProgrammerAgent(prompt: string) {
	const { stream } = await langbase.pipes.run({
		stream: true,
		name: 'react-copilot',
		messages: [{ role: 'user', content: prompt }],
		variables: [
			{
				name: 'framework',
				value: 'React'
			}
		]
	});

	return {
		stream,
		pipe: 'react-copilot'
	};
}

Let's break down the above code:

  • Called react-copilot pipe agent with the user prompt and React as a variable.
  • Returned the stream and pipe as react-copilot.

Step #6Database Architect Pipe

The Database Architect pipe agent generates SQL queries. It takes the user prompt and generates either SQL query or whole database schema. It automatically incorporate partitioning strategies if necessary and also indexing options to optimize query performance.

You have already defined runDatabaseArchitectAgent in the step 4. Let's write its implementation. Add the following code to the run-agents.ts file:

runDatabaseArchitectAgent function code

/**
 * Executes the Database Architect Agent with the given prompt.
 *
 * @param prompt - The input prompt string to be processed by the database architect agent
 * @returns An object containing:
 *          - stream: The output stream from the agent
 *          - pipe: The name of the pipe used ('database-architect')
 * @async
 */
async function runDatabaseArchitectAgent(prompt: string) {
	const {stream} = await langbase.pipes.run({
		stream: true,
		name: 'database-architect',
		messages: [{ role: 'user', content: prompt }]
	})


	return {
		stream,
		pipe: 'database-architect'
	};
}

Here is a quick explanation of what's happening in the code above:

  • Call the database-architect pipe agent with the user prompt.
  • Return the stream and pipe as database-architect.

Step #7Build the CodeAlchmemist

Now that you have all the pipes in place, you can call the /api/generate endpoint to either generate a React component or SQL query based on the user prompt.

You will write a custom React hook useLangbase that will call the /api/generate endpoint with the user prompt and handle the response stream.

use-langbase.ts hook

import { getRunner } from 'langbase';
import { FormEvent, useState } from 'react';

const useLangbase = () => {
	const [loading, setLoading] = useState(false);
	const [completion, setCompletion] = useState('');
	const [hasFinishedRun, setHasFinishedRun] = useState(false);

	/**
	 * Executes the Code Alchemist agent with the provided prompt and handles the response stream.
	 *
	 * @param {Object} params - The parameters for running the Code Alchemist agent
	 * @param {FormEvent<HTMLFormElement>} [params.e] - Optional form event to prevent default behavior
	 * @param {string} params.prompt - The prompt to send to the Code Alchemist agent
	 * @param {string} [params.originalPrompt] - Optional original prompt text
	 *
	 * @throws {Error} When the API request fails or returns an error response
	 *
	 * @returns {Promise<void>}
	 */
	async function runCodeAlchemistAgent({
		e,
		prompt,
	}: {
		prompt: string;
		e?: FormEvent<HTMLFormElement>;
	}) {
		e && e.preventDefault();

		// if the prompt is empty, return
		if (!prompt.trim()) {
			console.info('Please enter a prompt first.');
			return;
		}

		try {
			setLoading(true);
			setHasFinishedRun(false);

			// make a POST request to the API endpoint to call AI pipes
			const response = await fetch('/api/generate', {
				method: 'POST',
				headers: {
					'Content-Type': 'application/json'
				},
				body: JSON.stringify({ prompt })
			});

			// if the response is not ok, throw an error
			if (!response.ok) {
				const error = await response.json();
				console.error(error);
				return;
			}

			// get the response body as a stream
			if (response.body) {
				const runner = getRunner(response.body);
				// const stream = fromReadableStream(response.body);
				runner.on('content', (content) => {
					content && setCompletion(prev => prev + content);
				});
			}


		} catch (error) {
			console.error('Something went wrong. Please try again later.');
		} finally {
			setLoading(false);
			setHasFinishedRun(true);
		}
	}


	return {
		loading,
		completion,
		hasFinishedRun,
		runCodeAlchemistAgent
	};
};

export default useLangbase;

Here's what you have done in this step:

  • Create a custom React hook useLangbase that will call the /api/generate endpoint with the user prompt.
  • Use the getRunner function from the Langbase SDK to get the response body as a stream.
  • Listen for the content event on the runner and update the completion state with the generated content.
Complete code

You can find the complete code for the CodeAlchemist app in the GitHub repository.


Live demo

You can try out the live demo of the CodeAlchemist here.


Next Steps