Our Code Assistant v1, relying on a RAG workflow, had limited capabilities linked to its lack of reasoning.
The second version of our Code Assistant will introduce reasoning capabilities to adapt analysis based on the user’s input:
Copy
Ask AI
const { state: { kv },} = await network.run( `Analyze the files/example.ts file by suggesting improvements and documentation.`);console.log("Analysis:", kv.get("summary"));// Analysis: The code analysis suggests several key areas for improvement:// 1. Type Safety and Structure:// - Implement strict TypeScript configurations// - Add explicit return types and interfaces// - Break down complex functions// - Follow Single Responsibility Principle// - Implement proper error handling// 2. Performance Optimization:// - Review and optimize critical operations// ...
These agentic (reasoning) capabilities are introduced by the following AgentKit concepts:
Tools: Enables Agents to interact with their environment (ex: file system or shared State).
Router: Powers the flow of the conversation between Agents.
Network: Add a shared State to share information between Agents.
Our Code Assistant v2 introduces reasoning to perform tailored recommendations based on a given code file: refactoring, documentation, etc.
To achieve this behavior, we will need to:
Create a code_assistant_agent Agent that will load a given filename from disk and plan a workflow using the following available Agents:
analysis_agent that will analyze the code file and suggest improvements
documentation_agent that will generate documentation for the code file
Finally, create a summarization_agent Agent that will generate a summary of the suggestions made by other agents
Compared to our Code Assistant v1, this new version does not consist of simple retrieval and generations steps.
Instead, it introduces more flexibility by enabling LLM models to plan actions and select tools to use.
Our Code Assistant v2 is composed of 4 Agents collaborating together to analyze a given code file.
Such collaboration is made possible by using a Network to orchestrate the Agents and share State between them.
Unlike the Code Assistant v1, the user prompt will be passed to the network instead of an individual Agent:
Copy
Ask AI
await network.run( `Analyze the files/example.ts file by suggesting improvements and documentation.`);
To successfully run, a Network relies on:
A Router to indicate which Agent should be run next
A shared State, updated by the Agents’ LLM responses and tool calls
Let’s start by implementing our Agents and registering them into the Network.
Our first two analysis Agents are straightforward:
Copy
Ask AI
import { createAgent } from "@inngest/agent-kit";const documentationAgent = createAgent({ name: "documentation_agent", system: "You are an expert at generating documentation for code",});const analysisAgent = createAgent({ name: "analysis_agent", system: "You are an expert at analyzing code and suggesting improvements",});
Defining task specific LLM calls (Agents) is a great way to make the LLM reasoning more efficient and avoid unnecessary generations.
Our documentation_agent and analysis_agent are currently stateless and need to be connected to the Network by saving their suggestions into the shared State.
For this, we will create our first Tool using createTool:
Copy
Ask AI
const saveSuggestions = createTool({ name: "save_suggestions", description: "Save the suggestions made by other agents into the state", parameters: z.object({ suggestions: z.array(z.string()), }), handler: async (input, { network }) => { const suggestions = network?.state.kv.get("suggestions") || []; network?.state.kv.set("suggestions", [ ...suggestions, ...input.suggestions, ]); return "Suggestions saved!"; },});
A Tool is a function that can be called by an Agent.
The name, description and parameters are used by the Agent to understand what the Tool does and what it expects as input.
The handler is the function that will be called when the Tool is used. save_suggestions’s handler relies on the Network’s State kv (key-value store) API to share information with other Agents.
The save_suggestions Tool is used by both documentation_agent and analysis_agent to save their suggestions into the shared State:
Copy
Ask AI
import { createAgent } from "@inngest/agent-kit";// `save_suggestions` definition...const documentationAgent = createAgent({ name: "documentation_agent", system: "You are an expert at generating documentation for code", tools: [saveSuggestions],});const analysisAgent = createAgent({ name: "analysis_agent", system: "You are an expert at analyzing code and suggesting improvements", tools: [saveSuggestions],});
Our documentation_agent and analysis_agent are now connected to the Network and will save their suggestions into the shared State.
Let’s now create our code_assistant_agent that will read the code file from disk and plan the workflow to run.
The Code Assistant Agent
Let’s jump into the action by looking at the full implementation of our code_assistant_agent:
Copy
Ask AI
const codeAssistantAgent = createAgent({ name: "code_assistant_agent", system: ({ network }) => { const agents = Array.from(network?.agents.values() || []) .filter( (agent) => !["code_assistant_agent", "summarization_agent"].includes(agent.name) ) .map((agent) => `${agent.name} (${agent.system})`); return `From a given user request, ONLY perform the following tool calls:- read the file content- generate a plan of agents to run from the following list: ${agents.join(", ")}Answer with "done" when you are finished.`; }, tools: [ createTool({ name: "read_file", description: "Read a file from the current directory", parameters: z.object({ filename: z.string(), }), handler: async (input, { network }) => { const filePath = join(process.cwd(), `files/${input.filename}`); const code = readFileSync(filePath, "utf-8"); network?.state.kv.set("code", code); return "File read!"; }, }), createTool({ name: "generate_plan", description: "Generate a plan of agents to run", parameters: z.object({ plan: z.array(z.string()), }), handler: async (input, { network }) => { network?.state.kv.set("plan", input.plan); return "Plan generated!"; }, }), ],});
The highlighted lines emphasize three important parts of the code_assistant_agent:
The system property can take a function receiving the current Network state as argument, enabling more flexibility in the Agent’s behavior
Here, the system function is used to generate a prompt for the LLM based on the available Agents in the Network, enabling the LLM to plan the workflow to run
The code_assistant_agent relies on two Tools to achieve its goal:
read_file to read the code file from disk and save it into the shared State
generate_plan to generate a plan of agents to run and save it into the shared State
The pattern of dynamic system prompt and tools are also used by the summarization_agent to generate a summary of the suggestions made by other agents.
The Summarization Agent
Copy
Ask AI
const summarizationAgent = createAgent({ name: "summarization_agent", system: ({ network }) => { const suggestions = network?.state.kv.get("suggestions") || []; return `Save a summary of the following suggestions: ${suggestions.join("\n")}`; }, tools: [ createTool({ name: "save_summary", description: "Save a summary of the suggestions made by other agents into the state", parameters: z.object({ summary: z.string(), }), handler: async (input, { network }) => { network?.state.kv.set("summary", input.summary); return "Saved!"; }, }), ],});
The summarization_agent is a good example on how the State can be used to
store intermediate results and pass them to the next Agent: - the
suggestions are stored in the State by the documentation_agent and
analysis_agent - the summarization_agent will read the suggestions from
the State and generate a summary - the summary is then stored in the State as
the summary key
Our four Agents are now propely defined and connected to the Network’s State.
Let’s now configure our Network to run the Agents with a Router.
Let’s have a closer look at the Router implementation:
Copy
Ask AI
const router = ({ network }) => { // the first iteration of the network will have an empty state // also, the first run of `code_assistant_agent` will store the `code`, // requiring a second run to generate the plan if (!network?.state.kv.has("code") || !network?.state.kv.has("plan")) { return codeAssistantAgent; } else { // once the `plan` available in the state, we iterate over the agents to execute const plan = (network?.state.kv.get("plan") || []) as string[]; const nextAgent = plan.pop(); if (nextAgent) { network?.state.kv.set("plan", plan); return network?.agents.get(nextAgent); // we no agents are left to run, we generate a summary } else if (!network?.state.kv.has("summary")) { return summarizationAgent; // if no agent are left to run and a summary is available, we are done } else { return undefined; } }};
Our Code Assistant v2 iteration is now complete. Let’s run it!
First, go to your Anthropic dashboard and create a new API key.
Then, run the following command to execute our Code Assistant:
Copy
Ask AI
ANTHROPIC_API_KEY=<your-api-key> npm run start
The following output should be displayed in your terminal:
Copy
Ask AI
Analysis: The code analysis suggests several key areas for improvement:1. Type Safety and Structure:- Implement strict TypeScript configurations- Add explicit return types and interfaces- Break down complex functions- Follow Single Responsibility Principle- Implement proper error handling2. Performance Optimization:- Review and optimize critical operations- Consider caching mechanisms- Improve data processing efficiency3. Documentation:- Add comprehensive JSDoc comments- Document complex logic and assumptions- Create detailed README- Include setup and usage instructions- Add code examples
Updating the files/example.ts by applying the suggestions and running the Code Assistant again will yield a different planning with a different summary.
This Code Assistant v2 shines by its analysis capabilities, but cannot be qualified as an AI Agent.
In the next version of our Code Assistant, we will transform it into a semi-autonomous AI Agent that can solve bugs and improve code of a small project.