!function(){function t(t){document.documentElement.setAttribute("data-theme",t)}var e=function(){var t=null;try{t=new URLSearchParams(window.location.search).get("docusaurus-theme")}catch(t){}return t}()||function(){var t=null;try{t=localStorage.getItem("theme")}catch(t){}return t}();t(null!==e?e:"light")}(),document.documentElement.setAttribute("data-announcement-bar-initially-dismissed",function(){try{return"true"===localStorage.getItem("docusaurus.announcement.dismiss")}catch(t){}return!1}())

[Skip to main content](#__docusaurus_skipToContent_fallback)

LangChain v0.2 is coming soon! Preview the new docs [here](/v0.2/docs/introduction/).

[

![🦜️🔗 Langchain](/v0.1/img/brand/wordmark.png)![🦜️🔗 Langchain](/v0.1/img/brand/wordmark-dark.png)

](/v0.1/)[Docs](/v0.1/docs/get_started/introduction/)[Use cases](/v0.1/docs/use_cases/)[Integrations](/v0.1/docs/integrations/platforms/)[API Reference](https://api.js.langchain.com)

[More](#)

*   [People](/v0.1/docs/people/)
*   [Community](/v0.1/docs/community/)
*   [Tutorials](/v0.1/docs/additional_resources/tutorials/)
*   [Contributing](/v0.1/docs/contributing/)

[v0.1](#)

*   [v0.2](https://js.langchain.com/v0.2/docs/introduction)
*   [v0.1](/v0.1/docs/get_started/introduction/)

[🦜🔗](#)

*   [LangSmith](https://smith.langchain.com)
*   [LangSmith Docs](https://docs.smith.langchain.com)
*   [LangChain Hub](https://smith.langchain.com/hub)
*   [LangServe](https://github.com/langchain-ai/langserve)
*   [Python Docs](https://python.langchain.com/)

[Chat](https://chatjs.langchain.com)[](https://github.com/langchain-ai/langchainjs)

Search

*   [Get started](/v0.1/docs/get_started/)
    
    *   [Introduction](/v0.1/docs/get_started/introduction/)
    *   [Installation](/v0.1/docs/get_started/installation/)
    *   [Quickstart](/v0.1/docs/get_started/quickstart/)
*   [LangChain Expression Language](/v0.1/docs/expression_language/)
    
    *   [Get started](/v0.1/docs/expression_language/get_started/)
    *   [Why use LCEL?](/v0.1/docs/expression_language/why/)
    *   [Interface](/v0.1/docs/expression_language/interface/)
    *   [Streaming](/v0.1/docs/expression_language/streaming/)
    *   [How to](/v0.1/docs/expression_language/how_to/routing/)
        
    *   [Cookbook](/v0.1/docs/expression_language/cookbook/)
        
    *   [LangChain Expression Language (LCEL)](/v0.1/docs/expression_language/)
*   [Modules](/v0.1/docs/modules/)
    
    *   [Model I/O](/v0.1/docs/modules/model_io/)
        
    *   [Retrieval](/v0.1/docs/modules/data_connection/)
        
    *   [Chains](/v0.1/docs/modules/chains/)
    *   [Agents](/v0.1/docs/modules/agents/)
        
        *   [Quick start](/v0.1/docs/modules/agents/quick_start/)
        *   [Concepts](/v0.1/docs/modules/agents/concepts/)
        *   [Agent Types](/v0.1/docs/modules/agents/agent_types/)
            
        *   [How-to](/v0.1/docs/modules/agents/how_to/custom_agent/)
            
            *   [Custom agent](/v0.1/docs/modules/agents/how_to/custom_agent/)
            *   [Returning structured output](/v0.1/docs/modules/agents/how_to/agent_structured/)
            *   [Subscribing to events](/v0.1/docs/modules/agents/how_to/callbacks/)
            *   [Cancelling requests](/v0.1/docs/modules/agents/how_to/cancelling_requests/)
            *   [Custom LLM Agent](/v0.1/docs/modules/agents/how_to/custom_llm_agent/)
            *   [Custom LLM Agent (with a ChatModel)](/v0.1/docs/modules/agents/how_to/custom_llm_chat_agent/)
            *   [Custom MRKL agent](/v0.1/docs/modules/agents/how_to/custom_mrkl_agent/)
            *   [Handle parsing errors](/v0.1/docs/modules/agents/how_to/handle_parsing_errors/)
            *   [Access intermediate steps](/v0.1/docs/modules/agents/how_to/intermediate_steps/)
            *   [Logging and tracing](/v0.1/docs/modules/agents/how_to/logging_and_tracing/)
            *   [Cap the max number of iterations](/v0.1/docs/modules/agents/how_to/max_iterations/)
            *   [Streaming](/v0.1/docs/modules/agents/how_to/streaming/)
            *   [Timeouts for agents](/v0.1/docs/modules/agents/how_to/timeouts/)
        *   [Agents](/v0.1/docs/modules/agents/)
        *   [Tools](/v0.1/docs/modules/agents/tools/)
            
    *   [More](/v0.1/docs/modules/memory/)
        
*   [Security](/v0.1/docs/security/)
*   [Guides](/v0.1/docs/guides/)
    
*   [Ecosystem](/v0.1/docs/ecosystem/)
    
*   [LangGraph](/v0.1/docs/langgraph/)
*   * * *
    

*   [](/v0.1/)
*   [Modules](/v0.1/docs/modules/)
*   [Agents](/v0.1/docs/modules/agents/)
*   How-to
*   Returning structured output

Returning structured output
===========================

Here is a simple example of an agent which uses LCEL, a web search tool (Tavily) and a structured output parser to create an OpenAI functions agent that returns source chunks.

The first step is to import necessary modules

tip

See [this section for general instructions on installing integration packages](/v0.1/docs/get_started/installation/#installing-integration-packages).

*   npm
*   Yarn
*   pnpm

    npm install @langchain/openai

    yarn add @langchain/openai

    pnpm add @langchain/openai

    import { zodToJsonSchema } from "zod-to-json-schema";import { z } from "zod";import {  type BaseMessage,  AIMessage,  FunctionMessage,  type AgentFinish,  type AgentStep,} from "langchain/schema";import { RunnableSequence } from "langchain/runnables";import { ChatPromptTemplate, MessagesPlaceholder } from "langchain/prompts";import { ChatOpenAI } from "@langchain/openai";import { AgentExecutor } from "langchain/agents";import { DynamicTool } from "@langchain/core/tools";import type { FunctionsAgentAction } from "langchain/agents/openai/output_parser";import { convertToOpenAIFunction } from "@langchain/core/utils/function_calling";import { TavilySearchAPIRetriever } from "@langchain/community/retrievers/tavily_search_api";

Next, we initialize an LLM and a search tool that wraps our web search retriever. We will later bind this as an OpenAI function:

    const llm = new ChatOpenAI({  model: "gpt-4-1106-preview",});const searchTool = new DynamicTool({  name: "web-search-tool",  description: "Tool for getting the latest information from the web",  func: async (searchQuery: string, runManager) => {    const retriever = new TavilySearchAPIRetriever();    const docs = await retriever.invoke(searchQuery, runManager?.getChild());    return docs.map((doc) => doc.pageContent).join("\n-----\n");  },});

Now we can define our prompt template. We'll use a simple `ChatPromptTemplate` with placeholders for the user's question, and the agent scratchpad.

    const prompt = ChatPromptTemplate.fromMessages([  [    "system",    "You are a helpful assistant. You must always call one of the provided tools.",  ],  ["user", "{input}"],  new MessagesPlaceholder("agent_scratchpad"),]);

After that, we define our structured response schema using [Zod](https://zod.dev). This schema defines the structure of the final response from the agent.

    const responseSchema = z.object({  answer: z.string().describe("The final answer to return to the user"),  sources: z    .array(z.string())    .describe(      "List of page chunks that contain answer to the question. Only include a page chunk if it contains relevant information"    ),});

Once our response schema is defined, we can construct it as an OpenAI function to later be passed to the model. This is an important step regarding consistency as the model will always respond in this schema when it successfully completes a task

    const responseOpenAIFunction = {  name: "response",  description: "Return the response to the user",  parameters: zodToJsonSchema(responseSchema),};

Next, we construct a custom structured output parsing function that can detect when the model has called our final response function. This is similar to the method in the stock [JSONOutputFunctionsParser](https://api.js.langchain.com/classes/langchain_output_parsers.JsonOutputFunctionsParser.html), but with a change to directly return a response when the final response function is called.

    const structuredOutputParser = (  message: AIMessage): FunctionsAgentAction | AgentFinish => {  if (message.content && typeof message.content !== "string") {    throw new Error("This agent cannot parse non-string model responses.");  }  if (message.additional_kwargs.function_call) {    const { function_call } = message.additional_kwargs;    try {      const toolInput = function_call.arguments        ? JSON.parse(function_call.arguments)        : {};      // If the function call name is `response` then we know it's used our final      // response function and can return an instance of `AgentFinish`      if (function_call.name === "response") {        return { returnValues: { ...toolInput }, log: message.content };      }      return {        tool: function_call.name,        toolInput,        log: `Invoking "${function_call.name}" with ${          function_call.arguments ?? "{}"        }\n${message.content}`,        messageLog: [message],      };    } catch (error) {      throw new Error(        `Failed to parse function arguments from chat model response. Text: "${function_call.arguments}". ${error}`      );    }  } else {    return {      returnValues: { output: message.content },      log: message.content,    };  }};

After this, we can bind our two functions to the LLM, and create a runnable sequence which will be used as the agent.

**Important** - note here we pass in `agent_scratchpad` as an input variable, which formats all the previous steps using the `formatForOpenAIFunctions` function. This is very important as it contains all the context history the model needs to preform accurate tasks. Without this, the model would have no context on the previous steps taken. The `formatForOpenAIFunctions` function returns the steps as an array of `BaseMessage`s. This is necessary as the `MessagesPlaceholder` class expects this type as the input.

    const formatAgentSteps = (steps: AgentStep[]): BaseMessage[] =>  steps.flatMap(({ action, observation }) => {    if ("messageLog" in action && action.messageLog !== undefined) {      const log = action.messageLog as BaseMessage[];      return log.concat(new FunctionMessage(observation, action.tool));    } else {      return [new AIMessage(action.log)];    }  });const llmWithTools = llm.bind({  functions: [convertToOpenAIFunction(searchTool), responseOpenAIFunction],});/** Create the runnable */const runnableAgent = RunnableSequence.from<{  input: string;  steps: Array<AgentStep>;}>([  {    input: (i) => i.input,    agent_scratchpad: (i) => formatAgentSteps(i.steps),  },  prompt,  llmWithTools,  structuredOutputParser,]);

Finally, we can create an instance of `AgentExecutor` and run the agent.

    const executor = AgentExecutor.fromAgentAndTools({  agent: runnableAgent,  tools: [searchTool],});/** Call invoke on the agent */const res = await executor.invoke({  input: "what is the current weather in honolulu?",});console.log({  res,});

The output will look something like this

    {  res: {    answer: 'The current weather in Honolulu is 71 \bF with light rain and broken clouds.',    sources: [      'Currently: 71 \bF. Light rain. Broken clouds. (Weather station: Honolulu International Airport, USA). See more current weather'    ]  }}

* * *

#### Help us out by providing feedback on this documentation page:

[

Previous

Custom agent

](/v0.1/docs/modules/agents/how_to/custom_agent/)[

Next

Subscribing to events

](/v0.1/docs/modules/agents/how_to/callbacks/)

Community

*   [Discord](https://discord.gg/cU2adEyC7w)
*   [Twitter](https://twitter.com/LangChainAI)

GitHub

*   [Python](https://github.com/langchain-ai/langchain)
*   [JS/TS](https://github.com/langchain-ai/langchainjs)

More

*   [Homepage](https://langchain.com)
*   [Blog](https://blog.langchain.dev)

Copyright © 2024 LangChain, Inc.