# LangGraph Integration Guide

How to build LangGraph agents that work with the UiPath platform. This guide covers project structure, entrypoint detection, LLM models, and common pitfalls.

## Scaffolding a New Project

If there is **no existing agent code**, scaffold a LangGraph project with:

```bash
mkdir my-agent && cd my-agent
uip codedagent new my-agent
```

This generates `main.py` (with a StateGraph template), `langgraph.json`, and `pyproject.toml`. Then modify `main.py` to implement your actual agent logic.

> **Prerequisite:** `uipath-langchain` must be installed for the LangGraph template to be used. If you get a base template instead, install `uipath-langchain` first.

## Project Structure

LangGraph agents use a **different structure** from coded function agents. There are two supported patterns:

### Pattern A: `langgraph.json` (Recommended for LangGraph)

```
my-agent/
├── main.py               # LangGraph definition — exports `graph` variable
├── langgraph.json        # Graph configuration — maps names to graph variables
├── pyproject.toml        # Dependencies (must include uipath-langchain)
├── .env                  # Environment variables
└── ...                   # Generated by uip codedagent init
```

**`langgraph.json`** tells the runtime where to find your graph:

```json
{
  "graphs": {
    "agent": "./main.py:graph"
  }
}
```

- `"agent"` — the entrypoint name (used with `uip codedagent run agent`)
- `"./main.py:graph"` — path to the Python file and the variable name (`file:variable`)
- The file can be named `main.py` or `graph.py` — just ensure the path matches

### Pattern B: `uipath.json` with `functions`

For agents that use `main.py` instead of `graph.py`:

```
my-agent/
├── main.py               # LangGraph definition — exports `graph` variable
├── uipath.json           # UiPath config with functions mapping
├── pyproject.toml        # Dependencies
└── ...
```

**`uipath.json`** maps the graph entrypoint:

```json
{
  "$schema": "https://cloud.uipath.com/draft/2024-12/uipath",
  "runtimeOptions": {
    "isConversational": false
  },
  "packOptions": {
    "includeUvLock": true
  },
  "functions": {
    "graph": "main.py:graph"
  }
}
```

### Which Pattern to Use?

| Pattern | When to Use |
|---------|-------------|
| `langgraph.json` | New LangGraph projects, multi-graph setups |
| `uipath.json` functions | Existing UiPath projects adding a LangGraph graph |

Both patterns require the `graph` variable to be a compiled `StateGraph` or `CompiledStateGraph`.

---

## Required Dependencies

Every LangGraph agent needs these in `pyproject.toml`:

```toml
[project]
name = "my-agent"
version = "0.0.1"
description = "My LangGraph agent"
requires-python = ">=3.11"
dependencies = [
    "uipath",
    "uipath-langchain",
]

[dependency-groups]
dev = [
    "uipath-dev",
]
```

**Key point:** `uipath-langchain` is required — it registers the LangGraph runtime factory that enables `uip codedagent run`, `uip codedagent init`, and deployment to detect and execute your graph.

For agents that use LLM calls, add the appropriate model dependency:

```toml
dependencies = [
    "uipath",
    "uipath-langchain",
    "langgraph>=1.0.4",          # If using StateGraph directly
    "langchain-community",        # If using community tools (DuckDuckGo, etc.)
]
```

---

## LLM Models

Use UiPath's LLM libraries instead of raw `langchain_openai.ChatOpenAI`. These route through UiPath's LLM Gateway for centralized model management and billing.

### UiPathAzureChatOpenAI (Passthrough)

Drop-in replacement for LangChain's `ChatOpenAI` that routes through UiPath's passthrough endpoint (Azure OpenAI API format). No API keys needed — usage consumes Agent Units from your UiPath account.

Instantiate LLM clients inside graph nodes or functions — module-level instantiation fails during `uip codedagent init`. See [../lifecycle/build.md](../lifecycle/build.md) § Additional Instructions for the full rule.

```python
from uipath_langchain.chat.models import UiPathAzureChatOpenAI

# Inside a graph node:
llm = UiPathAzureChatOpenAI(
    model="gpt-4o-mini-2024-07-18",
    temperature=0.7,
    max_tokens=4000,
    timeout=30,
    max_retries=2
)
```

**Model names:** Pass the model identifier as a string. Run `uip codedagent list-models` to list the models available in your tenant.

**Features:**
- No API key needed — uses UiPath authentication
- Supports streaming, structured output, and tool calling
- Integrates seamlessly with LangChain agents and RAG

### UiPathChat (Normalized)

Versatile model class supporting multiple vendors through UiPath's normalized LLM Gateway API. Ideal for multi-vendor strategies.

```python
from uipath_langchain.chat.models import UiPathChat

llm = UiPathChat(
    model="gpt-4o-2024-11-20",
    temperature=0.5,
    max_tokens=2000
)
```

**Supported Models Include:**
- **OpenAI**: `gpt-4o-2024-11-20`, `o3-mini-2025-01-31`
- **Anthropic**: `anthropic.claude-3-5-sonnet-20240620-v1:0`
- **Google**: `gemini-2.0-flash-001`
- **AWS & Others**: Additional models based on your region and account

**Features:**
- Supports multiple model providers
- Custom streaming headers for compatibility
- Flexible model switching for cost/quality optimization
- Regional availability may vary by data residency requirements

### Configuration Parameters

Both models support these configuration options:

- `temperature` (0-1): Controls randomness vs. determinism (default: 0.7)
- `max_tokens`: Maximum response length (default varies by model)
- `timeout`: API request timeout in seconds
- `max_retries`: Number of retries on transient failures (default: 2)
- `top_p`: Nucleus sampling parameter for diversity
- `frequency_penalty`: Reduce token repetition
- `presence_penalty`: Encourage new topics

### Structured Output

Both models support `with_structured_output()` for Pydantic schema validation:

```python
from pydantic import BaseModel

class Analysis(BaseModel):
    sentiment: str
    confidence: float

llm = UiPathAzureChatOpenAI()
structured_llm = llm.with_structured_output(Analysis)
raw_dict: dict = await structured_llm.ainvoke("Analyze: I love this product!")
result: Analysis = Analysis.model_validate(raw_dict)
```

### Model Selection Guide

| Use Case | Class |
|----------|-------|
| Cost-conscious, general tasks | `UiPathAzureChatOpenAI` |
| Complex reasoning, state-of-the-art | `UiPathChat` or `UiPathAzureChatOpenAI` |
| Multi-vendor flexibility (OpenAI, Anthropic, Google) | `UiPathChat` |
| Specialized domains (e.g., code) | `UiPathChat` with a Claude model |

Run `uip codedagent list-models` to see the model strings available in your tenant and pass the exact `model_name` to the class constructor.

---

## Graph Definition

### Input/Output Schemas

Define `GraphInput` and `GraphOutput` as Pydantic models. These become the agent's schema for `uip codedagent init` and deployment:

```python
from pydantic import BaseModel, Field

class GraphInput(BaseModel):
    query: str = Field(description="The user's question")
    max_results: int = Field(default=5, description="Max results to return")

class GraphOutput(BaseModel):
    answer: str = Field(description="The answer")
    sources: list[str] = Field(default_factory=list, description="Sources used")
```

### State Definition

Use `MessagesState` for agents that need conversation history, or a plain `TypedDict`/`BaseModel` for simpler workflows:

```python
from langgraph.graph import MessagesState

class GraphState(MessagesState):
    query: str
    answer: str | None = None
    sources: list[str] | None = None
```

### Building the Graph

```python
from langgraph.graph import START, END, StateGraph
from langgraph.types import Command

async def search(state: GraphState) -> Command:
    # ... your logic ...
    return Command(update={"answer": "result", "sources": ["url1"]})

builder = StateGraph(GraphState, input_schema=GraphInput, output_schema=GraphOutput)
builder.add_node("search", search)
builder.add_edge(START, "search")
builder.add_edge("search", END)

graph = builder.compile()
```

**Important:** The variable MUST be named `graph` and be a `CompiledStateGraph`. This is what the runtime factory looks for.

### Runtime Output Quirk — No Channel Reducers For Output Fields

The UiPath LangGraph runtime captures the **last node's update delta** as the final output (`runtime.py` uses `stream_mode=["updates",...]` and overwrites `final_chunk` per event). Channel reducers like `Annotated[list, operator.add]` accumulate inside the graph but **do not appear in `--output-file` JSON or eval trajectories** — only the terminal node's delta does.

For any aggregate field that must appear in the output, carry it forward explicitly instead of relying on a reducer:

```python
return {"items": [*state.get("items", []), new_item]}
```

Or have the terminal node compute the full aggregate and emit it.

---

## Available Tools

The `uipath-langchain` package provides UiPath-specific LangChain tools:

| Tool | Import | Purpose |
|------|--------|---------|
| Process invocation | `uipath_langchain.agent.tools import create_process_tool` | Trigger UiPath processes |
| Escalation (HITL) | `uipath_langchain.agent.tools import create_escalation_tool` | Send to human reviewer |
| Context search | `uipath_langchain.retrievers import ContextGroundingRetriever` | Search Context Grounding indexes |
| MCP tools | `uipath_langchain.agent.tools import open_mcp_tools` | Connect to MCP servers |

> **Context Grounding retrieval in LangGraph:** always use `ContextGroundingRetriever` from `uipath_langchain.retrievers` — not `sdk.context_grounding.search()` / `search_async()`. The LangChain retriever integrates natively with the graph pipeline and declares `index_name` + `folder_path` at one call site — the exact shape the `index` binding entry needs (see [../lifecycle/bindings-reference.md](../lifecycle/bindings-reference.md)). See [../capabilities/context-grounding.md](../capabilities/context-grounding.md) for usage examples.

### Context Grounding RAG Example

Minimal RAG shape: retrieval node + answer node.

```python
from typing import TypedDict

from langchain_core.messages import HumanMessage, SystemMessage
from uipath_langchain.chat.models import UiPathAzureChatOpenAI
from uipath_langchain.retrievers import ContextGroundingRetriever

class GraphState(TypedDict):
    question: str
    snippets: list[str]
    answer: str

async def retrieve(state: GraphState) -> dict:
    retriever = ContextGroundingRetriever(index_name="company_docs", folder_path="Shared")
    documents = await retriever.ainvoke(state["question"])
    return {"snippets": [doc.page_content for doc in documents]}

async def answer(state: GraphState) -> dict:
    context = "\n\n".join(state["snippets"])
    llm = UiPathAzureChatOpenAI(temperature=0)
    response = await llm.ainvoke([
        SystemMessage(content="Answer only from the provided context."),
        HumanMessage(content=f"Context:\n{context}\n\nQuestion: {state['question']}"),
    ])
    return {"answer": response.content}
```

Wire `retrieve → answer` as in § Building the Graph.

---

## Tracing

LangGraph agents get **automatic tracing** — you do NOT need `@traced()` on graph nodes. The UiPath LangGraph runtime instruments all node executions, LLM calls, and tool invocations automatically.

Use `@traced()` only on helper functions **outside** the graph that you want to monitor:

```python
from uipath.tracing import traced

@traced(name="postprocess", span_type="tool")
async def postprocess(text: str) -> str:
    """This gets its own trace span because it's not a graph node."""
    return text.strip().lower()
```

---

## Running `uip codedagent init`

After creating your graph, generate project configuration:

```bash
uv sync
uip codedagent init
```

### How Entrypoint Detection Works

1. `uip codedagent init` loads all registered middleware via Python entry points
2. The `uipath-langchain` package registers a LangGraph middleware
3. The middleware checks for `langgraph.json` — if found, it handles init
4. If no `langgraph.json`, the standard init checks `uipath.json` `"functions"` field
5. The runtime imports your Python file and validates the `graph` variable
6. Input/Output schemas are extracted from the graph's `input`/`output` type annotations

### Troubleshooting `uip codedagent init`

**"No function entrypoints found"**
- Ensure `langgraph.json` exists with the correct `"graphs"` mapping, OR
- Ensure `uipath.json` has a `"functions"` entry pointing to your graph
- The file path and variable name must match exactly (e.g., `"./main.py:graph"`)

**Import errors during init / authentication failures**
- `uip codedagent init` imports your Python file to introspect the graph
- Module-level `UiPathAzureChatOpenAI()` or `UiPathChat()` will try to authenticate on import and fail
- **Fix: Move LLM instantiation inside functions/nodes, never at module level:**
  ```python
  # BAD — fails during uip codedagent init:
  llm = UiPathAzureChatOpenAI()

  # GOOD — lazy initialization inside a node:
  async def my_node(state):
      llm = UiPathAzureChatOpenAI()
      return await llm.ainvoke(state["messages"])
  ```

**Schema not detected**
- Ensure your `StateGraph` specifies `input_schema=GraphInput, output_schema=GraphOutput` 
- The Input/Output classes must be Pydantic `BaseModel` subclasses

---

## Running Your Agent

```bash
# With langgraph.json
uip codedagent run agent '{"query": "What is Python?"}'

# With uipath.json functions (using the function name)
uip codedagent run graph '{"query": "What is Python?"}'
```

The entrypoint name comes from the key in `langgraph.json` `"graphs"` or `uipath.json` `"functions"`.

---

## Conversational Agents

For the cross-framework contract (the `isConversational` flag, local-run options), see `../capabilities/conversational-agents.md`.

### In-Process Input

Type the graph state's `messages` field as a list of `AnyMessage` with the `add_messages` reducer:

```python
from typing import Annotated
from langchain_core.messages import AnyMessage
from langgraph.graph.message import add_messages
from pydantic import BaseModel

class State(BaseModel):
    messages: Annotated[list[AnyMessage], add_messages] = []
```

`langgraph.graph.MessagesState` is the equivalent shorthand.

The wire envelope (see `../capabilities/conversational-agents.md` § Wire Envelope) lands as the `messages` field; the `uipath-langchain` runtime converts each `UiPathConversationMessage` dict into a LangChain `HumanMessage` before the graph runs.

### Two Implementation Options

Both are valid — pick based on the agent's needs:

- **Prebuilt ReAct agent** — `from langchain.agents import create_agent`. The input contract above is already wired in; just export the compiled graph from your entry point.
- **Custom graph** — any `StateGraph` topology consuming the same `messages` field. The first node can be fully deterministic (e.g. validation or routing) before any LLM call.

### Local Run

See `../capabilities/conversational-agents.md` § Running Locally for the `--keep-state-file` flag (required on every turn) and § Wire Envelope for the `turn1.json` shape.

---
