or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

agent-creation.mdhuman-in-the-loop.mdindex.mdstate-store-injection.mdtool-execution.mdtool-validation.md

state-store-injection.mddocs/

0

# State and Store Injection

1

2

Annotations for injecting graph state and persistent storage into tool arguments, enabling context-aware tools without exposing internal state management to the language model.

3

4

## Capabilities

5

6

### InjectedState Annotation

7

8

Annotation for injecting graph state into tool arguments. Enables tools to access graph state without exposing state management details to the language model.

9

10

```python { .api }

11

class InjectedState(InjectedToolArg):

12

def __init__(self, field: Optional[str] = None) -> None

13

```

14

15

**Parameters:**

16

- `field`: Optional key to extract from the state dictionary. If None, the entire state is injected.

17

18

**Usage Examples:**

19

20

```python

21

from typing import List

22

from typing_extensions import Annotated, TypedDict

23

from langchain_core.messages import BaseMessage

24

from langchain_core.tools import tool

25

from langgraph.prebuilt import InjectedState, ToolNode

26

27

class AgentState(TypedDict):

28

messages: List[BaseMessage]

29

user_id: str

30

session_data: dict

31

32

# Inject entire state

33

@tool

34

def state_aware_tool(

35

query: str,

36

state: Annotated[dict, InjectedState]

37

) -> str:

38

"""Tool that accesses full graph state."""

39

user_id = state.get("user_id", "unknown")

40

message_count = len(state.get("messages", []))

41

42

if message_count > 5:

43

return f"Continuing conversation for user {user_id}: {query}"

44

else:

45

return f"Starting fresh conversation for user {user_id}: {query}"

46

47

# Inject specific state field

48

@tool

49

def user_specific_tool(

50

action: str,

51

user_id: Annotated[str, InjectedState("user_id")]

52

) -> str:

53

"""Tool that accesses specific state field."""

54

return f"Performing {action} for user {user_id}"

55

56

# Create tool node

57

tool_node = ToolNode([state_aware_tool, user_specific_tool])

58

59

# Example state

60

state = {

61

"messages": [BaseMessage(content="Hello"), BaseMessage(content="How are you?")],

62

"user_id": "user_123",

63

"session_data": {"theme": "dark", "language": "en"}

64

}

65

66

# Tool calls with state injection

67

tool_calls = [

68

{"name": "state_aware_tool", "args": {"query": "weather"}, "id": "1", "type": "tool_call"},

69

{"name": "user_specific_tool", "args": {"action": "save_preference"}, "id": "2", "type": "tool_call"}

70

]

71

72

# State is automatically injected

73

result = tool_node.invoke({"messages": [...], **state})

74

```

75

76

### InjectedStore Annotation

77

78

Annotation for injecting persistent store into tool arguments. Enables tools to access LangGraph's persistent storage system for cross-session data persistence.

79

80

```python { .api }

81

class InjectedStore(InjectedToolArg):

82

pass

83

```

84

85

**Usage Examples:**

86

87

```python

88

from typing_extensions import Annotated

89

from langchain_core.tools import tool

90

from langgraph.prebuilt import InjectedStore, ToolNode

91

from langgraph.store.base import BaseStore

92

93

@tool

94

def save_user_preference(

95

key: str,

96

value: str,

97

user_id: str,

98

store: Annotated[BaseStore, InjectedStore()]

99

) -> str:

100

"""Save user preference to persistent storage."""

101

namespace = ("user_preferences", user_id)

102

store.put(namespace, key, value)

103

return f"Saved {key} = {value} for user {user_id}"

104

105

@tool

106

def get_user_preference(

107

key: str,

108

user_id: str,

109

store: Annotated[BaseStore, InjectedStore()]

110

) -> str:

111

"""Retrieve user preference from persistent storage."""

112

namespace = ("user_preferences", user_id)

113

result = store.get(namespace, key)

114

return result.value if result else f"No preference found for {key}"

115

116

@tool

117

def list_user_data(

118

user_id: str,

119

store: Annotated[BaseStore, InjectedStore()]

120

) -> str:

121

"""List all stored data for a user."""

122

namespace = ("user_preferences", user_id)

123

items = store.search(namespace)

124

if items:

125

return f"Stored preferences: {', '.join(item.key for item in items)}"

126

return "No stored preferences found"

127

128

# Usage with graph compilation

129

from langgraph.graph import StateGraph

130

from langgraph.store.memory import InMemoryStore

131

132

store = InMemoryStore()

133

tool_node = ToolNode([save_user_preference, get_user_preference, list_user_data])

134

135

graph = StateGraph(AgentState)

136

graph.add_node("tools", tool_node)

137

compiled_graph = graph.compile(store=store) # Store is injected automatically

138

```

139

140

## Advanced State Injection Patterns

141

142

### Conditional State Access

143

144

```python

145

@tool

146

def adaptive_tool(

147

query: str,

148

state: Annotated[dict, InjectedState]

149

) -> str:

150

"""Tool that adapts behavior based on state."""

151

messages = state.get("messages", [])

152

user_tier = state.get("user_tier", "basic")

153

154

# Adapt behavior based on conversation length

155

if len(messages) < 3:

156

response_style = "detailed"

157

elif len(messages) > 10:

158

response_style = "concise"

159

else:

160

response_style = "balanced"

161

162

# Adapt features based on user tier

163

advanced_features = user_tier in ["premium", "enterprise"]

164

165

return f"Processing '{query}' with {response_style} style, advanced features: {advanced_features}"

166

```

167

168

### Multi-Field State Injection

169

170

```python

171

@tool

172

def context_rich_tool(

173

task: str,

174

user_id: Annotated[str, InjectedState("user_id")],

175

session_data: Annotated[dict, InjectedState("session_data")],

176

messages: Annotated[list, InjectedState("messages")]

177

) -> str:

178

"""Tool with multiple injected state fields."""

179

user_language = session_data.get("language", "en")

180

conversation_length = len(messages)

181

182

return f"Task: {task} | User: {user_id} | Language: {user_language} | Messages: {conversation_length}"

183

```

184

185

## Persistent Storage Patterns

186

187

### Namespaced Data Organization

188

189

```python

190

@tool

191

def save_conversation_summary(

192

summary: str,

193

conversation_id: str,

194

store: Annotated[BaseStore, InjectedStore()]

195

) -> str:

196

"""Save conversation summary with organized namespacing."""

197

namespace = ("conversations", "summaries")

198

store.put(namespace, conversation_id, {

199

"summary": summary,

200

"created_at": datetime.now().isoformat(),

201

"version": "1.0"

202

})

203

return f"Saved summary for conversation {conversation_id}"

204

205

@tool

206

def get_user_history(

207

user_id: str,

208

store: Annotated[BaseStore, InjectedStore()]

209

) -> str:

210

"""Retrieve user's conversation history."""

211

namespace = ("users", user_id, "history")

212

items = store.search(namespace)

213

return f"Found {len(items)} historical items for user {user_id}"

214

```

215

216

### Cross-Session Data Sharing

217

218

```python

219

@tool

220

def update_global_stats(

221

action: str,

222

store: Annotated[BaseStore, InjectedStore()]

223

) -> str:

224

"""Update global application statistics."""

225

namespace = ("global", "stats")

226

current_stats = store.get(namespace, "counters")

227

228

if current_stats:

229

counters = current_stats.value

230

else:

231

counters = {}

232

233

counters[action] = counters.get(action, 0) + 1

234

store.put(namespace, "counters", counters)

235

236

return f"Updated {action} count to {counters[action]}"

237

```

238

239

## Integration with ToolNode

240

241

ToolNode automatically handles injection during tool execution:

242

243

```python

244

from langgraph.prebuilt import ToolNode

245

246

# Tools with injected dependencies

247

tools_with_injection = [

248

save_user_preference,

249

get_user_preference,

250

state_aware_tool,

251

context_rich_tool

252

]

253

254

tool_node = ToolNode(tools_with_injection)

255

256

# Injection happens automatically during invoke()

257

state = {

258

"messages": [...],

259

"user_id": "user_123",

260

"session_data": {"language": "en", "theme": "dark"}

261

}

262

263

# Both state and store are injected as needed

264

result = tool_node.invoke(state, store=my_store)

265

```

266

267

## Manual Injection for Advanced Use Cases

268

269

For custom routing or Send API usage:

270

271

```python

272

# Manually inject arguments for custom routing

273

tool_call = {

274

"name": "save_user_preference",

275

"args": {"key": "theme", "value": "dark", "user_id": "user_123"},

276

"id": "tool_1",

277

"type": "tool_call"

278

}

279

280

# Inject state and store

281

injected_call = tool_node.inject_tool_args(tool_call, state, store)

282

283

# Use with Send API

284

from langgraph.types import Send

285

286

def custom_router(state):

287

tool_calls = state["messages"][-1].tool_calls

288

injected_calls = [

289

tool_node.inject_tool_args(call, state, store)

290

for call in tool_calls

291

]

292

return [Send("tools", call) for call in injected_calls]

293

```

294

295

## Error Handling

296

297

### Missing Store Error

298

299

```python

300

# Error occurs if tool requires store injection but none provided

301

@tool

302

def requires_store_tool(

303

data: str,

304

store: Annotated[BaseStore, InjectedStore()]

305

) -> str:

306

return store.get(("data",), "key").value

307

308

tool_node = ToolNode([requires_store_tool])

309

310

# This will raise ValueError about missing store

311

try:

312

result = tool_node.invoke(state) # No store provided

313

except ValueError as e:

314

print(f"Error: {e}") # "Cannot inject store into tools with InjectedStore annotations"

315

```

316

317

### State Field Missing

318

319

```python

320

@tool

321

def requires_field_tool(

322

query: str,

323

user_id: Annotated[str, InjectedState("user_id")]

324

) -> str:

325

return f"Query for user {user_id}: {query}"

326

327

# If state doesn't contain "user_id" field, KeyError occurs

328

state_without_user_id = {"messages": []}

329

330

try:

331

result = tool_node.invoke(state_without_user_id)

332

except KeyError as e:

333

print(f"Missing required state field: {e}")

334

```

335

336

## Best Practices

337

338

### State Design

339

340

```python

341

# Good: Clear, typed state schema

342

class ApplicationState(TypedDict):

343

messages: List[BaseMessage]

344

user_id: str

345

session_metadata: dict

346

preferences: dict

347

348

# Avoid: Overly nested or unclear state structure

349

```

350

351

### Store Organization

352

353

```python

354

# Good: Consistent namespacing strategy

355

namespace = ("domain", "entity_type", "entity_id")

356

store.put(("users", "preferences", user_id), "theme", "dark")

357

store.put(("conversations", "summaries", conv_id), "summary", text)

358

359

# Good: Version your stored data

360

data = {"value": content, "version": 1, "created_at": timestamp}

361

```

362

363

### Tool Design

364

365

```python

366

# Good: Clear parameter separation

367

@tool

368

def well_designed_tool(

369

user_input: str, # From model

370

user_id: Annotated[str, InjectedState("user_id")], # From state

371

store: Annotated[BaseStore, InjectedStore()] # From system

372

) -> str:

373

"""Clear separation of concerns."""

374

pass

375

376

# Avoid: Mixing injected and model parameters confusingly

377

```