{"id":13644238,"url":"https://github.com/brainlid/langchain","last_synced_at":"2026-05-15T05:19:11.230Z","repository":{"id":180778215,"uuid":"665653661","full_name":"brainlid/langchain","owner":"brainlid","description":"Elixir implementation of a LangChain style framework that lets Elixir projects integrate with and leverage LLMs.","archived":false,"fork":false,"pushed_at":"2026-03-04T12:49:06.000Z","size":1930,"stargazers_count":1096,"open_issues_count":52,"forks_count":187,"subscribers_count":22,"default_branch":"main","last_synced_at":"2026-03-04T19:29:25.234Z","etag":null,"topics":["ai","anthropic","bumblebee","chatgpt","claude-ai","elixir","langchain","llm"],"latest_commit_sha":null,"homepage":"https://hexdocs.pm/langchain/","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/brainlid.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2023-07-12T17:27:16.000Z","updated_at":"2026-03-04T13:27:19.000Z","dependencies_parsed_at":"2023-07-12T20:36:24.853Z","dependency_job_id":"f487f807-ae20-447b-afed-790d2a466a2e","html_url":"https://github.com/brainlid/langchain","commit_stats":{"total_commits":376,"total_committers":29,"mean_commits":12.96551724137931,"dds":"0.15957446808510634","last_synced_commit":"29c1134e89b2d1f93841d808cc586a27a190af77"},"previous_names":["brainlid/langchain"],"tags_count":30,"template":false,"template_full_name":null,"purl":"pkg:github/brainlid/langchain","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brainlid%2Flangchain","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brainlid%2Flangchain/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brainlid%2Flangchain/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brainlid%2Flangchain/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/brainlid","download_url":"https://codeload.github.com/brainlid/langchain/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brainlid%2Flangchain/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30113124,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-05T03:40:26.266Z","status":"ssl_error","status_checked_at":"2026-03-05T03:39:15.902Z","response_time":93,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai","anthropic","bumblebee","chatgpt","claude-ai","elixir","langchain","llm"],"created_at":"2024-08-02T01:01:59.565Z","updated_at":"2026-05-15T05:19:11.208Z","avatar_url":"https://github.com/brainlid.png","language":"Elixir","funding_links":[],"categories":["Reimplementations","High-Level Agent Frameworks","Ports to other languages","Elixir","Generative AI","Agent Frameworks"],"sub_categories":["How to Join","Agent Frameworks"],"readme":"[![Elixir CI](https://github.com/brainlid/langchain/actions/workflows/elixir.yml/badge.svg)](https://github.com/brainlid/langchain/actions/workflows/elixir.yml)\n[![Module Version](https://img.shields.io/hexpm/v/langchain.svg)](https://hex.pm/packages/langchain)\n[![Hex Docs](https://img.shields.io/badge/hex-docs-lightgreen.svg)](https://hexdocs.pm/langchain)\n\n# ![Logo with chat chain links](https://github.com/brainlid/langchain/blob/main/images/elixir-langchain-link-logo_32px.png?raw=true) Elixir LangChain\n\nElixir LangChain enables Elixir applications to integrate AI services and self-hosted models into an application.\n\n**Supported chat models:**\n\n- **Anthropic Claude** - Claude models including extended thinking support and AWS Bedrock\n- **AWS Bedrock Mantle** - OpenAI-compatible gateway for third-party models hosted on Bedrock (Moonshot Kimi K2 family, OpenAI gpt-oss, and many others)\n- **OpenAI ChatGPT** - GPT models via the Chat Completions API\n- **OpenAI Responses API** - OpenAI's newer Responses API with WebSocket transport support\n- **xAI Grok** - Grok-4, Grok-3-mini, Grok-4 Heavy (multi-agent), and more\n- **Google Gemini** - Gemini AI models\n- **Google Vertex AI** - Google's enterprise AI offering\n- **DeepSeek** - DeepSeek models with prompt caching support\n- **Ollama** - Locally hosted open-source models\n- **Mistral** - Mistral AI models\n- **Perplexity** - Perplexity AI models\n- **orq.ai** - orq.ai Deployments API\n- **Bumblebee** - Self-hosted models via Nx (Llama, Mistral, Zephyr)\n- **ReqLLM** - Multi-provider adapter via the `req_llm` library (Anthropic, OpenAI, Gemini, Groq, Ollama, AWS Bedrock, etc.)\n\n**LangChain** is short for Language Chain. An LLM, or Large Language Model, is the \"Language\" part. This library makes it easier for Elixir applications to \"chain\" or connect different processes, integrations, libraries, services, or functionality together with an LLM.\n\n**LangChain** is a framework for developing applications powered by language models. It enables applications that are:\n\n- **Data-aware:** connect a language model to other sources of data\n- **Agentic:** allow a language model to interact with its environment\n\nThe main value props of LangChain are:\n\n1. **Components:** abstractions for working with language models, along with a collection of implementations for each abstraction. Components are modular and easy-to-use, whether you are using the rest of the LangChain framework or not\n1. **Off-the-shelf chains:** a structured assembly of components for accomplishing specific higher-level tasks\n\nOff-the-shelf chains make it easy to get started. For more complex applications and nuanced use-cases, components make it easy to customize existing chains or build new ones.\n\n## What is this?\n\nLarge Language Models (LLMs) are emerging as a transformative technology, enabling developers to build applications that they previously could not. But using these LLMs in isolation is often not enough to create a truly powerful app - the real power comes when you can combine them with other sources of computation or knowledge.\n\nThis library is aimed at assisting in the development of those types of applications.\n\n## Documentation\n\nThe online documentation can be [found here](https://hexdocs.pm/langchain).\n\n## Demo\n\nCheck out the [demo project](https://github.com/brainlid/langchain_demo) that you can download and review.\n\n## Relationship with JavaScript and Python LangChain\n\nThis library is written in [Elixir](https://elixir-lang.org/) and intended to be used with Elixir applications. The original libraries are [LangChain JS/TS](https://js.langchain.com/) and [LangChain Python](https://python.langchain.com/).\n\nThe JavaScript and Python projects aim to integrate with each other as seamlessly as possible. The intended integration is so strong that that all objects (prompts, LLMs, chains, etc) are designed in a way where they can be serialized and shared between the two languages.\n\nThis Elixir version does not aim for parity with the JavaScript and Python libraries. Why not?\n\n- JavaScript and Python are both Object Oriented languages. Elixir is Functional. We're not going to force a design that doesn't apply.\n- The JS and Python versions started before conversational LLMs were standard. They put a lot of effort into preserving history (like a conversation) when the LLM didn't support it. We're not doing that here.\n\nThis library was heavily inspired by, and based on, the way the JavaScript library actually worked and interacted with an LLM.\n\n## Installation\n\n**Requirements:** Elixir 1.17 or higher\n\nThe package can be installed by adding `langchain` to your list of dependencies\nin `mix.exs`:\n\n```elixir\ndef deps do\n  [\n    {:langchain, \"~\u003e 0.8.0\"}\n  ]\nend\n```\n\n## Configuration\n\nCurrently, the library is written to use the `Req` library for making API calls.\n\nYou can configure an _organization ID_, and _API key_ for OpenAI's API, but this library also works with [other compatible APIs](#alternative-openai-compatible-apis) as well as other services and even [local models running on Bumblebee](#bumblebee-chat-support).\n\n`config/runtime.exs`:\n\n```elixir\nconfig :langchain, openai_key: System.fetch_env!(\"OPENAI_API_KEY\")\nconfig :langchain, openai_org_id: System.fetch_env!(\"OPENAI_ORG_ID\")\n# OR\nconfig :langchain, openai_key: \"YOUR SECRET KEY\"\nconfig :langchain, openai_org_id: \"YOUR_OPENAI_ORG_ID\"\n\nconfig :langchain, :anthropic_key, System.fetch_env!(\"ANTHROPIC_API_KEY\")\nconfig :langchain, :xai_api_key, System.fetch_env!(\"XAI_API_KEY\")\n```\n\nIt's possible to use a function or a tuple to resolve the secret:\n\n```elixir\nconfig :langchain, openai_key: {MyApp.Secrets, :openai_api_key, []}\nconfig :langchain, openai_org_id: {MyApp.Secrets, :openai_org_id, []}\n# OR\nconfig :langchain, openai_key: fn -\u003e System.fetch_env!(\"OPENAI_API_KEY\") end\nconfig :langchain, openai_org_id: fn -\u003e System.fetch_env!(\"OPENAI_ORG_ID\") end\n```\n\nThe API keys should be treated as secrets and not checked into your repository.\n\nFor [fly.io](https://fly.io), adding the secrets looks like this:\n\n```\nfly secrets set OPENAI_API_KEY=MyOpenAIApiKey\nfly secrets set ANTHROPIC_API_KEY=MyAnthropicApiKey\nfly secrets set XAI_API_KEY=MyXaiApiKey\n```\n\nA list of models to use:\n\n- [Anthropic Claude models](https://docs.anthropic.com/en/docs/about-claude/models)\n- [Anthropic models on AWS Bedrock](https://docs.anthropic.com/en/api/claude-on-amazon-bedrock#accessing-bedrock)\n- [OpenAI models](https://platform.openai.com/docs/models)\n- [OpenAI models on Azure](https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/models)\n- [xAI Grok models](https://docs.x.ai/docs/models)\n- [Gemini AI models](https://ai.google.dev/gemini-api/docs/models/gemini)\n\n## Prompt caching\n\nChatGPT, Claude, and DeepSeek all offer prefix-based prompt caching, which can offer cost and performance benefits for longer prompts. Gemini offers context caching, which is similar.\n\n- [ChatGPT's prompt caching](https://openai.com/index/api-prompt-caching/) is automatic for prompts longer than 1024 tokens, caching the longest common prefix.\n- [Claude's prompt caching](https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching) is not automatic. It's prefixing processes tools, system, and then messages, in that order, up to and including the block designated with {\"cache_control\": {\"type\": \"ephemeral\"}} . See LangChain.ChatModels.ChatAnthropicTest and for an example.\n- [DeepSeek's prompt caching](https://api-docs.deepseek.com/guides/kv_cache) provides automatic caching for repeated prompts and system messages, helping reduce costs and improve response times for longer conversations.\n- [Gemini's context caching]((https://ai.google.dev/gemini-api/docs/caching?lang=python)) requires a separate call which is not supported by Langchain.\n\n## Usage\n\nThe central module in this library is `LangChain.Chains.LLMChain`. Most other pieces are either inputs to this, or structures used by it. For understanding how to use the library, start there.\n\n### xAI Grok Support\n\nLangChain supports all xAI Grok models including the advanced Grok-4 variants:\n\n```elixir\nalias LangChain.ChatModels.ChatGrok\nalias LangChain.Chains.LLMChain\nalias LangChain.Message\n\n# Basic Grok-4 usage\n{:ok, grok} = ChatGrok.new(%{model: \"grok-4\", temperature: 0.7})\n\n{:ok, chain} =\n  LLMChain.new!(%{llm: grok})\n  |\u003e LLMChain.add_message(Message.new_user!(\"Explain quantum computing\"))\n  |\u003e LLMChain.run()\n\n# Fast and efficient Grok-3-mini\n{:ok, mini_grok} = ChatGrok.new(%{model: \"grok-3-mini\", temperature: 0.8})\n```\n\nGrok models offer unique capabilities:\n- **130K+ context window** for extensive conversations\n- **Multi-agent reasoning** (Grok-4 Heavy) where multiple agents collaborate\n- **Advanced reasoning mode** with first-principles thinking\n- **Specialized coding support** (Grok-4 Code)\n- **Multimodal capabilities** including vision and image analysis\n\n### AWS Bedrock Mantle Support\n\nLangChain supports AWS Bedrock's **Mantle** endpoint, an OpenAI-compatible gateway for third-party models hosted on Bedrock. A single `ChatAwsMantle` module covers the entire Mantle catalog, including Moonshot's Kimi K2 family (`moonshotai.kimi-k2-thinking`, `moonshotai.kimi-k2.5`) and OpenAI's gpt-oss series (`openai.gpt-oss-120b`), with new models becoming available as AWS adds them.\n\n```elixir\nalias LangChain.ChatModels.ChatAwsMantle\nalias LangChain.Chains.LLMChain\nalias LangChain.Message\n\n# Bearer auth with a Bedrock API key\n{:ok, mantle} = ChatAwsMantle.new(%{\n  model: \"moonshotai.kimi-k2.5\",\n  region: \"us-east-1\",\n  api_key: System.fetch_env!(\"AWS_BEARER_TOKEN_BEDROCK\")\n})\n\n{:ok, chain} =\n  LLMChain.new!(%{llm: mantle})\n  |\u003e LLMChain.add_message(Message.new_user!(\"Summarize Elixir's actor model\"))\n  |\u003e LLMChain.run()\n```\n\nTwo authentication modes are supported:\n\n- **Bearer token** (simplest): pass `:api_key` with a long-term Bedrock API key.\n- **AWS SigV4** (IAM-friendly): pass `:credentials` as a zero-arity function returning IAM credentials (e.g. from `ExAws.Config`). Useful when the host already has IAM-based auth configured.\n\nKey capabilities:\n\n- **Reasoning extraction**: models that produce chain-of-thought (Kimi K2 Thinking always, K2.5 via `reasoning_effort: \"high\"`) surface their reasoning as a `ContentPart` of type `:thinking` on the assistant message, so downstream UIs can render thinking the same way as Anthropic extended thinking.\n- **Multimodal (K2.5)**: send images using the standard `ContentPart.image!/2` helper; Mantle accepts the OpenAI-shaped `image_url` wire format.\n- **Streaming** with per-chunk `MessageDelta` updates, including separate deltas for reasoning and content.\n- **Tool calling** via the standard OpenAI `tool_calls` shape.\n- **Region-aware URL building**: set `:region` and the endpoint is derived as `https://bedrock-mantle.{region}.api.aws/v1/chat/completions`.\n\nSee the `LangChain.ChatModels.ChatAwsMantle` module documentation for the full list of tested models, per-model quirks, sampling controls (`:temperature`, `:top_p`, `:frequency_penalty`, `:presence_penalty`), and usage notes.\n\n### Exposing a custom Elixir function to ChatGPT\n\nA really powerful feature of LangChain is making it easy to integrate an LLM into your application and expose features, data, and functionality _from_ your application to the LLM.\n\n\u003cimg src=\"https://github.com/brainlid/langchain/blob/main/images/langchain_functions_overview_sm_v1.png?raw=true\" style=\"text-align: center;\" width=50% height=50% alt=\"Diagram showing LLM integration to application logic and data through a LangChain.Function\"\u003e\n\nA `LangChain.Function` bridges the gap between the LLM and our application code. We choose what to expose and using `context`, we can ensure any actions are limited to what the user has permission to do and access.\n\nFor an interactive example, refer to the project [Livebook notebook \"LangChain: Executing Custom Elixir Functions\"](notebooks/custom_functions.livemd).\n\nThe following is an example of a function that receives parameter arguments.\n\n```elixir\nalias LangChain.Function\nalias LangChain.Message\nalias LangChain.Chains.LLMChain\nalias LangChain.ChatModels.ChatOpenAI\nalias LangChain.Utils.ChainResult\n\n# map of data we want to be passed as `context` to the function when\n# executed.\ncustom_context = %{\n  \"user_id\" =\u003e 123,\n  \"hairbrush\" =\u003e \"drawer\",\n  \"dog\" =\u003e \"backyard\",\n  \"sandwich\" =\u003e \"kitchen\"\n}\n\n# a custom Elixir function made available to the LLM\ncustom_fn =\n  Function.new!(%{\n    name: \"custom\",\n    description: \"Returns the location of the requested element or item.\",\n    parameters_schema: %{\n      type: \"object\",\n      properties: %{\n        thing: %{\n          type: \"string\",\n          description: \"The thing whose location is being requested.\"\n        }\n      },\n      required: [\"thing\"]\n    },\n    function: fn %{\"thing\" =\u003e thing} = _arguments, context -\u003e\n      # our context is a pretend item/location location map\n      {:ok, context[thing]}\n    end\n  })\n\n# create and run the chain\n{:ok, updated_chain} =\n  LLMChain.new!(%{\n    llm: ChatOpenAI.new!(),\n    custom_context: custom_context,\n    verbose: true\n  })\n  |\u003e LLMChain.add_tools(custom_fn)\n  |\u003e LLMChain.add_message(Message.new_user!(\"Where is the hairbrush located?\"))\n  |\u003e LLMChain.run(mode: :while_needs_response)\n\n# print the LLM's answer\nIO.puts(ChainResult.to_string!(updated_chain))\n# =\u003e \"The hairbrush is located in the drawer.\"\n```\n\n### Alternative OpenAI compatible APIs\n\nThere are several services or self-hosted applications that provide an OpenAI compatible API for ChatGPT-like behavior. To use a service like that, the `endpoint` of the `ChatOpenAI` struct can be pointed to an API compatible `endpoint` for chats.\n\nFor example, if a locally running service provided that feature, the following code could connect to the service:\n\n```elixir\n{:ok, updated_chain} =\n  LLMChain.new!(%{\n    llm: ChatOpenAI.new!(%{endpoint: \"http://localhost:1234/v1/chat/completions\"}),\n  })\n  |\u003e LLMChain.add_message(Message.new_user!(\"Hello!\"))\n  |\u003e LLMChain.run()\n```\n\n### Bumblebee Chat Support\n\nBumblebee hosted chat models are supported. There is built-in support for Llama 2, Mistral, and Zephyr models.\n\nCurrently, function calling is only supported for llama 3.1 Json Tool calling for Llama 2, Mistral, and Zephyr is NOT supported.\nThere is an example notebook in the notebook folder.\n\n    ChatBumblebee.new!(%{\n      serving: @serving_name,\n      template_format: @template_format,\n      receive_timeout: @receive_timeout,\n      stream: true\n    })\n\nThe `serving` is the module name of the `Nx.Serving` that is hosting the model.\n\nSee the [`LangChain.ChatModels.ChatBumblebee` documentation](https://hexdocs.pm/langchain/LangChain.ChatModels.ChatBumblebee.html) for more details.\n\n## Testing\n\nBefore you can run live API tests, you need to provide your API keys. Copy the example file and populate it with your values:\n\n```\ncp .env.example .env\n# Edit .env with your private API keys\n```\n\nThe `.env` file is gitignored and is loaded automatically by the test suite via [dotenvy](https://hex.pm/packages/dotenvy) — no shell setup or external tools required.\n\nTo run all the tests including the ones that perform live calls against the OpenAI API, use the following command:\n\n```\nmix test --include live_call\nmix test --include live_open_ai\nmix test --include live_ollama_ai\nmix test --include live_anthropic\nmix test --include live_aws_mantle\nmix test --include live_mistral_ai\nmix test --include live_grok\nmix test --include live_vertex_ai\nmix test test/tools/calculator_test.exs --include live_call\n```\n\nNOTE: This will use the configured API credentials which creates billable events.\n\nOtherwise, running the following will only run local tests making no external API calls:\n\n```\nmix test\n```\n\nExecuting a specific test, whether it is a `live_call` or not, will execute it creating a potentially billable event.\n\n**Multi-modal support:**\n\nLangChain now supports multi-modal messages and tool results. This means you can include text, images, files, and even \"thinking\" blocks in a single message using ContentParts. See module docs for details. Support for this depends on the LLM and service. Not all models may yet support all modalities.\n\n## Evaluating Agent Behavior\n\nWhen building agent systems, the final answer is only part of the story. Two agents can produce the same answer through very different reasoning paths — one might make a single efficient tool call while another makes five redundant ones. LangChain provides `LangChain.Trajectory` to evaluate the *process*, not just the outcome.\n\nA trajectory captures the structured sequence of tool calls produced during an `LLMChain` run, enabling regression testing, cost control, safety verification, and debugging of agent workflows.\n\n### Capturing a Trajectory\n\nAfter running a chain, extract its trajectory:\n\n```elixir\nalias LangChain.Trajectory\n\n{:ok, chain} =\n  LLMChain.new!(%{llm: llm})\n  |\u003e LLMChain.add_tools(my_tools)\n  |\u003e LLMChain.add_message(Message.new_user!(\"What's the weather in Paris?\"))\n  |\u003e LLMChain.run(mode: :while_needs_response)\n\ntrajectory = Trajectory.from_chain(chain)\ntrajectory.tool_calls\n#=\u003e [%{name: \"search\", arguments: %{\"query\" =\u003e \"weather paris\"}},\n#    %{name: \"get_forecast\", arguments: %{\"city\" =\u003e \"Paris\"}}]\n```\n\n### Matching Tool Call Sequences\n\nUse `Trajectory.matches?/3` to compare actual tool calls against expected patterns:\n\n```elixir\n# Strict: exact order and arguments\nTrajectory.matches?(trajectory, [\n  %{name: \"search\", arguments: %{\"query\" =\u003e \"weather paris\"}},\n  %{name: \"get_forecast\", arguments: %{\"city\" =\u003e \"Paris\"}}\n])\n\n# Wildcard arguments: pass nil to match any arguments\nTrajectory.matches?(trajectory, [\n  %{name: \"search\", arguments: nil},\n  %{name: \"get_forecast\", arguments: nil}\n])\n\n# Unordered: same calls in any order\nTrajectory.matches?(trajectory, expected, mode: :unordered)\n\n# Superset: actual contains at least all expected calls\nTrajectory.matches?(trajectory, [%{name: \"search\", arguments: nil}], mode: :superset)\n\n# Subset args: expected arguments are a subset of actual\nTrajectory.matches?(trajectory, expected, args: :subset)\n```\n\n### ExUnit Assertions\n\n`LangChain.Trajectory.Assertions` provides `assert_trajectory` and `refute_trajectory` macros with informative failure diffs:\n\n```elixir\nuse LangChain.Trajectory.Assertions\n\ntest \"agent calls the right tools in order\" do\n  trajectory = Trajectory.from_chain(chain)\n\n  assert_trajectory trajectory, [\n    %{name: \"search\", arguments: %{\"query\" =\u003e \"weather\"}},\n    %{name: \"get_forecast\", arguments: nil}\n  ]\nend\n\ntest \"agent does not call dangerous tools\" do\n  trajectory = Trajectory.from_chain(chain)\n\n  refute_trajectory trajectory, [\n    %{name: \"delete_all\", arguments: nil}\n  ], mode: :superset\nend\n```\n\nBoth macros also accept an `LLMChain` directly, extracting the trajectory automatically.\n\n### Golden-File Testing\n\nSave a known-good trajectory and compare future runs against it to catch regressions:\n\n```elixir\n# Save the golden file\ngolden = chain |\u003e Trajectory.from_chain() |\u003e Trajectory.to_map()\nFile.write!(\"test/fixtures/weather_agent.json\", Jason.encode!(golden))\n\n# In your test\ngolden_map = \"test/fixtures/weather_agent.json\" |\u003e File.read!() |\u003e Jason.decode!()\nexpected = Trajectory.from_map(golden_map)\nactual = Trajectory.from_chain(chain)\n\nassert_trajectory actual, expected\n```\n\n### Inspecting Trajectories\n\nFilter and group tool calls for deeper analysis:\n\n```elixir\n# All calls to a specific tool\nTrajectory.calls_by_name(trajectory, \"search\")\n\n# Group calls by conversation turn\nTrajectory.calls_by_turn(trajectory)\n#=\u003e [{0, [%{name: \"search\", ...}]}, {1, [%{name: \"get_forecast\", ...}]}]\n\n# Check aggregated token usage\ntrajectory.token_usage\n#=\u003e %TokenUsage{input: 150, output: 45}\n\n# Check metadata\ntrajectory.metadata\n#=\u003e %{model: \"gpt-4\", llm_module: LangChain.ChatModels.ChatOpenAI}\n```\n\nSee `LangChain.Trajectory` and `LangChain.Trajectory.Assertions` module docs for the full API reference.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbrainlid%2Flangchain","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbrainlid%2Flangchain","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbrainlid%2Flangchain/lists"}