@aurite-ai/kai 0.2.0-dev.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +190 -0
- package/dist/kai-mcp.cjs +1652 -0
- package/dist/kai-mcp.cjs.map +7 -0
- package/dist/templates/copilot-configs/claude-code/.claude/CLAUDE.md +272 -0
- package/dist/templates/copilot-configs/claude-code/.claude/agents/architect.md +133 -0
- package/dist/templates/copilot-configs/claude-code/.claude/agents/implementer.md +88 -0
- package/dist/templates/copilot-configs/claude-code/.claude/settings.json +87 -0
- package/dist/templates/copilot-configs/claude-code/.claude/skills/documentation/SKILL.md +54 -0
- package/dist/templates/copilot-configs/claude-code/.claude/skills/verification/SKILL.md +125 -0
- package/dist/templates/frameworks/langgraph/README.md +118 -0
- package/dist/templates/frameworks/langgraph/main.py +97 -0
- package/dist/templates/frameworks/langgraph/pyproject.toml +29 -0
- package/dist/templates/frameworks/langgraph/src/agent/__init__.py +31 -0
- package/dist/templates/frameworks/langgraph/src/agent/graph.py +257 -0
- package/dist/templates/frameworks/langgraph/src/agent/state.py +65 -0
- package/dist/templates/frameworks/langgraph/src/agent/tools.py +30 -0
- package/dist/templates/frameworks/openai/README.md +131 -0
- package/dist/templates/frameworks/openai/main.py +80 -0
- package/dist/templates/frameworks/openai/pyproject.toml +28 -0
- package/dist/templates/frameworks/openai/src/agent/__init__.py +18 -0
- package/dist/templates/frameworks/openai/src/agent/agents.py +122 -0
- package/dist/templates/frameworks/openai/src/agent/tools.py +100 -0
- package/dist/templates/index.d.ts +85 -0
- package/dist/templates/index.d.ts.map +1 -0
- package/dist/templates/index.js +270 -0
- package/dist/templates/index.js.map +1 -0
- package/dist/templates/knowledge-base/langgraph-best-practices.mdc +277 -0
- package/dist/templates/knowledge-base/openai-agents-overview.mdc +602 -0
- package/dist/templates/project-env +7 -0
- package/dist/templates/project-gitignore +26 -0
- package/package.json +66 -0
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template access utilities for file-based templates.
|
|
3
|
+
*
|
|
4
|
+
* Templates are stored as files in the templates/ directory and are
|
|
5
|
+
* read from the filesystem at runtime. This makes templates easier
|
|
6
|
+
* to maintain and edit compared to embedding them as strings.
|
|
7
|
+
*/
|
|
8
|
+
import * as fs from 'node:fs/promises';
|
|
9
|
+
import * as path from 'node:path';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
11
|
+
// =============================================================================
|
|
12
|
+
// Template Directory Resolution
|
|
13
|
+
// =============================================================================
|
|
14
|
+
// Cache the templates directory path
|
|
15
|
+
let _templatesDir = null;
|
|
16
|
+
/**
|
|
17
|
+
* Get the absolute path to the templates directory.
|
|
18
|
+
*
|
|
19
|
+
* Works for both:
|
|
20
|
+
* - Development: templates/ relative to src/templates/
|
|
21
|
+
* - Bundled CJS: templates/ relative to the bundle location
|
|
22
|
+
* - npm installed: templates/ in package directory
|
|
23
|
+
* - Docker: via KAI_TEMPLATES_DIR environment variable
|
|
24
|
+
*/
|
|
25
|
+
function getTemplatesDir() {
|
|
26
|
+
// Return cached value if available
|
|
27
|
+
if (_templatesDir) {
|
|
28
|
+
return _templatesDir;
|
|
29
|
+
}
|
|
30
|
+
// 1. Check KAI_TEMPLATES_DIR env var first (Docker, explicit config)
|
|
31
|
+
const envPath = process.env.KAI_TEMPLATES_DIR;
|
|
32
|
+
if (envPath) {
|
|
33
|
+
_templatesDir = envPath;
|
|
34
|
+
return _templatesDir;
|
|
35
|
+
}
|
|
36
|
+
// 2. Try to detect from module location
|
|
37
|
+
// In CJS bundles, import.meta.url is empty/undefined
|
|
38
|
+
// In ESM development, we use import.meta.url
|
|
39
|
+
// Check if we're in a CJS bundle (import.meta.url will be undefined or empty)
|
|
40
|
+
// biome-ignore lint/suspicious/noExplicitAny: CJS/ESM compatibility check
|
|
41
|
+
const metaUrl = import.meta?.url;
|
|
42
|
+
if (metaUrl) {
|
|
43
|
+
// ESM mode (development or tsc compiled)
|
|
44
|
+
const __filename = fileURLToPath(metaUrl);
|
|
45
|
+
const __dirname = path.dirname(__filename);
|
|
46
|
+
// Development: templates/ is at apps/mcp/templates/ (up from src/templates/)
|
|
47
|
+
if (__dirname.includes('src')) {
|
|
48
|
+
_templatesDir = path.resolve(__dirname, '..', '..', 'templates');
|
|
49
|
+
return _templatesDir;
|
|
50
|
+
}
|
|
51
|
+
// TSC compiled: dist/templates/index.js -> templates are in dist/templates/
|
|
52
|
+
// __dirname = dist/templates, templates are at dist/templates/copilot-configs/, etc.
|
|
53
|
+
if (__dirname.includes('dist')) {
|
|
54
|
+
_templatesDir = __dirname;
|
|
55
|
+
return _templatesDir;
|
|
56
|
+
}
|
|
57
|
+
// ESM bundled fallback
|
|
58
|
+
_templatesDir = path.resolve(__dirname, 'templates');
|
|
59
|
+
return _templatesDir;
|
|
60
|
+
}
|
|
61
|
+
// CJS mode (bundled)
|
|
62
|
+
// In bundled CJS with esbuild, we need to find templates/ relative to the bundle
|
|
63
|
+
// The bundle is at dist/kai-mcp.cjs, templates are at dist/templates/
|
|
64
|
+
// Try __dirname first (available in CJS context, more reliable than process.argv)
|
|
65
|
+
// In bundled CJS, __dirname points to the directory containing the bundle
|
|
66
|
+
// biome-ignore lint/suspicious/noExplicitAny: CJS global check
|
|
67
|
+
const cjsDirname = typeof __dirname !== 'undefined' ? __dirname : undefined;
|
|
68
|
+
if (cjsDirname) {
|
|
69
|
+
_templatesDir = path.resolve(cjsDirname, 'templates');
|
|
70
|
+
return _templatesDir;
|
|
71
|
+
}
|
|
72
|
+
// Fallback: Use process.argv[1] which is the path to the script being executed
|
|
73
|
+
// This works for `node dist/kai-mcp.cjs` and npx scenarios
|
|
74
|
+
// but may fail when loaded as a library or in some test runners
|
|
75
|
+
const scriptPath = process.argv[1];
|
|
76
|
+
if (scriptPath) {
|
|
77
|
+
const scriptDir = path.dirname(scriptPath);
|
|
78
|
+
_templatesDir = path.resolve(scriptDir, 'templates');
|
|
79
|
+
return _templatesDir;
|
|
80
|
+
}
|
|
81
|
+
// Last resort: use cwd
|
|
82
|
+
_templatesDir = path.resolve(process.cwd(), 'templates');
|
|
83
|
+
return _templatesDir;
|
|
84
|
+
}
|
|
85
|
+
// =============================================================================
|
|
86
|
+
// Template Metadata
|
|
87
|
+
// =============================================================================
|
|
88
|
+
/**
|
|
89
|
+
* Available framework templates.
|
|
90
|
+
*/
|
|
91
|
+
export const FRAMEWORK_TEMPLATES = [
|
|
92
|
+
{
|
|
93
|
+
id: 'langgraph',
|
|
94
|
+
name: 'LangGraph',
|
|
95
|
+
description: 'Python agent framework using LangGraph for stateful workflows',
|
|
96
|
+
path: 'frameworks/langgraph',
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
id: 'openai',
|
|
100
|
+
name: 'OpenAI Agent SDK',
|
|
101
|
+
description: 'Python agent framework using OpenAI Agents SDK for tool calling and multi-agent handoffs',
|
|
102
|
+
path: 'frameworks/openai',
|
|
103
|
+
},
|
|
104
|
+
];
|
|
105
|
+
/**
|
|
106
|
+
* Available copilot configuration templates.
|
|
107
|
+
*/
|
|
108
|
+
export const COPILOT_CONFIG_TEMPLATES = [
|
|
109
|
+
{
|
|
110
|
+
id: 'claude-code',
|
|
111
|
+
name: 'Claude Code',
|
|
112
|
+
description: 'Configuration for Claude Code (formerly Cline)',
|
|
113
|
+
path: 'copilot-configs/claude-code',
|
|
114
|
+
},
|
|
115
|
+
];
|
|
116
|
+
/**
|
|
117
|
+
* Get a framework template by ID.
|
|
118
|
+
*/
|
|
119
|
+
export function getFrameworkTemplate(id) {
|
|
120
|
+
return FRAMEWORK_TEMPLATES.find((t) => t.id === id);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Get a copilot config template by ID.
|
|
124
|
+
*/
|
|
125
|
+
export function getCopilotConfigTemplate(id) {
|
|
126
|
+
return COPILOT_CONFIG_TEMPLATES.find((t) => t.id === id);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* List all available frameworks.
|
|
130
|
+
*/
|
|
131
|
+
export function listFrameworks() {
|
|
132
|
+
return [...FRAMEWORK_TEMPLATES];
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* List all available copilot configs.
|
|
136
|
+
*/
|
|
137
|
+
export function listCopilotConfigs() {
|
|
138
|
+
return [...COPILOT_CONFIG_TEMPLATES];
|
|
139
|
+
}
|
|
140
|
+
// =============================================================================
|
|
141
|
+
// File Reading Utilities
|
|
142
|
+
// =============================================================================
|
|
143
|
+
/**
|
|
144
|
+
* Recursively read all files from a directory.
|
|
145
|
+
* Returns TemplateFile[] with relative paths.
|
|
146
|
+
*/
|
|
147
|
+
async function readDirectoryRecursive(dirPath, basePath = '') {
|
|
148
|
+
const files = [];
|
|
149
|
+
try {
|
|
150
|
+
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
151
|
+
for (const entry of entries) {
|
|
152
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
153
|
+
const relativePath = basePath ? path.join(basePath, entry.name) : entry.name;
|
|
154
|
+
if (entry.isDirectory()) {
|
|
155
|
+
// Recursively read subdirectory
|
|
156
|
+
const subFiles = await readDirectoryRecursive(fullPath, relativePath);
|
|
157
|
+
files.push(...subFiles);
|
|
158
|
+
}
|
|
159
|
+
else if (entry.isFile()) {
|
|
160
|
+
// Read file content
|
|
161
|
+
const content = await fs.readFile(fullPath, 'utf-8');
|
|
162
|
+
files.push({
|
|
163
|
+
path: relativePath,
|
|
164
|
+
content,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
// Log template read failures for debugging
|
|
171
|
+
// ENOENT (directory doesn't exist) is expected in some scenarios, log as debug
|
|
172
|
+
const isNotFound = error.code === 'ENOENT';
|
|
173
|
+
if (isNotFound) {
|
|
174
|
+
console.warn(`[kai] Template directory not found: ${dirPath}`);
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
console.error(`[kai] Failed to read template directory: ${dirPath}`, error);
|
|
178
|
+
}
|
|
179
|
+
// Return empty array - caller should handle gracefully
|
|
180
|
+
}
|
|
181
|
+
return files;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Read a single template file.
|
|
185
|
+
*/
|
|
186
|
+
async function readTemplateFile(relativePath) {
|
|
187
|
+
const templatesDir = getTemplatesDir();
|
|
188
|
+
const fullPath = path.join(templatesDir, relativePath);
|
|
189
|
+
try {
|
|
190
|
+
return await fs.readFile(fullPath, 'utf-8');
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// =============================================================================
|
|
197
|
+
// Public API: Get Template Files
|
|
198
|
+
// =============================================================================
|
|
199
|
+
/**
|
|
200
|
+
* Get project-level template files (.env template, .gitignore).
|
|
201
|
+
*/
|
|
202
|
+
export async function getProjectFiles() {
|
|
203
|
+
const files = [];
|
|
204
|
+
const envContent = await readTemplateFile('project-env');
|
|
205
|
+
if (envContent) {
|
|
206
|
+
files.push({ path: '.env', content: envContent });
|
|
207
|
+
}
|
|
208
|
+
const gitignoreContent = await readTemplateFile('project-gitignore');
|
|
209
|
+
if (gitignoreContent) {
|
|
210
|
+
files.push({ path: '.gitignore', content: gitignoreContent });
|
|
211
|
+
}
|
|
212
|
+
return files;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Get LangGraph framework files.
|
|
216
|
+
*/
|
|
217
|
+
export async function getLangGraphFiles() {
|
|
218
|
+
const templatesDir = getTemplatesDir();
|
|
219
|
+
const frameworkDir = path.join(templatesDir, 'frameworks', 'langgraph');
|
|
220
|
+
return readDirectoryRecursive(frameworkDir);
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Get OpenAI framework files.
|
|
224
|
+
*/
|
|
225
|
+
export async function getOpenAIFiles() {
|
|
226
|
+
const templatesDir = getTemplatesDir();
|
|
227
|
+
const frameworkDir = path.join(templatesDir, 'frameworks', 'openai');
|
|
228
|
+
return readDirectoryRecursive(frameworkDir);
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Get Claude Code configuration files.
|
|
232
|
+
*/
|
|
233
|
+
export async function getClaudeCodeFiles() {
|
|
234
|
+
const templatesDir = getTemplatesDir();
|
|
235
|
+
const configDir = path.join(templatesDir, 'copilot-configs', 'claude-code');
|
|
236
|
+
return readDirectoryRecursive(configDir);
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Get knowledge base seed files.
|
|
240
|
+
*/
|
|
241
|
+
export async function getKnowledgeBaseFiles() {
|
|
242
|
+
const templatesDir = getTemplatesDir();
|
|
243
|
+
const kbDir = path.join(templatesDir, 'knowledge-base');
|
|
244
|
+
return readDirectoryRecursive(kbDir);
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Get template files for a framework.
|
|
248
|
+
*/
|
|
249
|
+
export async function getFrameworkFiles(frameworkId) {
|
|
250
|
+
switch (frameworkId) {
|
|
251
|
+
case 'langgraph':
|
|
252
|
+
return getLangGraphFiles();
|
|
253
|
+
case 'openai':
|
|
254
|
+
return getOpenAIFiles();
|
|
255
|
+
default:
|
|
256
|
+
throw new Error(`Unknown framework: ${frameworkId}`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Get config files for a copilot.
|
|
261
|
+
*/
|
|
262
|
+
export async function getCopilotConfigFiles(copilotId) {
|
|
263
|
+
switch (copilotId) {
|
|
264
|
+
case 'claude-code':
|
|
265
|
+
return getClaudeCodeFiles();
|
|
266
|
+
default:
|
|
267
|
+
throw new Error(`Unknown copilot: ${copilotId}`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/templates/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: knowledge
|
|
3
|
+
title: LangGraph Best Practices
|
|
4
|
+
summary: >
|
|
5
|
+
Comprehensive reference for building LangGraph agent workflows including core
|
|
6
|
+
concepts, graph construction patterns, state management, advanced patterns
|
|
7
|
+
like Send API and Command, and production-ready code examples.
|
|
8
|
+
created_at: "2026-02-10T00:00:00Z"
|
|
9
|
+
updated_at: "2026-02-10T00:00:00Z"
|
|
10
|
+
|
|
11
|
+
source:
|
|
12
|
+
file: langgraph-best-practices.mdc
|
|
13
|
+
project: null
|
|
14
|
+
path: null
|
|
15
|
+
|
|
16
|
+
classification:
|
|
17
|
+
category: reference
|
|
18
|
+
confidence: 1.0
|
|
19
|
+
reasoning: >
|
|
20
|
+
Seed content providing technical reference documentation for the LangGraph
|
|
21
|
+
framework. Covers API patterns, state management, and code examples.
|
|
22
|
+
topics:
|
|
23
|
+
- LangGraph
|
|
24
|
+
- Agent Workflows
|
|
25
|
+
- State Management
|
|
26
|
+
- Python AI Agents
|
|
27
|
+
- Structured Output
|
|
28
|
+
|
|
29
|
+
status: active
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
# LangGraph Best Practices
|
|
33
|
+
|
|
34
|
+
## Core Concepts
|
|
35
|
+
|
|
36
|
+
LangGraph models agent workflows as graphs with three key components:
|
|
37
|
+
|
|
38
|
+
1. **State**: A shared data structure representing the current snapshot of your application
|
|
39
|
+
- Use TypedDict or Pydantic BaseModel for schema definition
|
|
40
|
+
- Define reducers to specify how updates are applied to each state key
|
|
41
|
+
- Default reducer overwrites values; use Annotated types for custom reducers (e.g., operator.add for lists)
|
|
42
|
+
- For message-based workflows, use MessagesState or the add_messages reducer
|
|
43
|
+
|
|
44
|
+
2. **Nodes**: Functions that encode logic and perform computation
|
|
45
|
+
- Accept state as input, perform work, return state updates
|
|
46
|
+
- Can be synchronous or asynchronous Python functions
|
|
47
|
+
- Can accept optional config (RunnableConfig) and runtime (Runtime) parameters
|
|
48
|
+
- Nodes do the work - they contain LLM calls or regular code
|
|
49
|
+
- Add nodes with builder.add_node("node_name", node_function)
|
|
50
|
+
|
|
51
|
+
3. **Edges**: Functions that determine which node executes next
|
|
52
|
+
- Edges tell what to do next based on current state
|
|
53
|
+
- Normal edges: Direct transitions with add_edge("node_a", "node_b")
|
|
54
|
+
- Conditional edges: Dynamic routing with add_conditional_edges("node_a", routing_function)
|
|
55
|
+
- Use START constant for entry points, END for terminal nodes
|
|
56
|
+
- Multiple outgoing edges execute destination nodes in parallel
|
|
57
|
+
|
|
58
|
+
## Graph Construction Pattern
|
|
59
|
+
|
|
60
|
+
1. Define State schema (TypedDict or Pydantic model)
|
|
61
|
+
2. Create StateGraph: builder = StateGraph(State)
|
|
62
|
+
3. Add nodes: builder.add_node("name", function)
|
|
63
|
+
4. Add edges: builder.add_edge(START, "first_node") and builder.add_edge("node_a", "node_b")
|
|
64
|
+
5. Add conditional edges if needed: builder.add_conditional_edges("node", routing_func)
|
|
65
|
+
6. Compile: graph = builder.compile()
|
|
66
|
+
|
|
67
|
+
## State Management
|
|
68
|
+
|
|
69
|
+
- **Multiple Schemas**: Use InputState, OutputState, OverallState, and PrivateState for different node communication patterns
|
|
70
|
+
- **Reducers**: Annotate state keys with reducer functions to control update behavior
|
|
71
|
+
- Default: Overwrite existing value
|
|
72
|
+
- operator.add: Append to lists
|
|
73
|
+
- add_messages: Smart message list management with ID tracking
|
|
74
|
+
- **MessagesState**: Prebuilt state for chat applications with messages key using add_messages reducer
|
|
75
|
+
|
|
76
|
+
## Advanced Patterns
|
|
77
|
+
|
|
78
|
+
- **Send API**: For map-reduce patterns, return Send objects from conditional edges to dynamically create parallel branches
|
|
79
|
+
- **Command**: Combine state updates and routing in a single node by returning Command(update={...}, goto="next_node")
|
|
80
|
+
- Use Command when you need both state updates AND routing decisions
|
|
81
|
+
- Requires type annotation: Command[Literal["node_name"]]
|
|
82
|
+
- Command adds dynamic edges but doesn't override static edges
|
|
83
|
+
- **Subgraphs**: Use Command.PARENT to navigate from subgraph nodes to parent graph nodes
|
|
84
|
+
|
|
85
|
+
## Execution Model
|
|
86
|
+
|
|
87
|
+
- Graph uses message passing with discrete "super-steps"
|
|
88
|
+
- Nodes in parallel are part of same super-step
|
|
89
|
+
- Sequential nodes belong to separate super-steps
|
|
90
|
+
- Execution terminates when all nodes are inactive and no messages in transit
|
|
91
|
+
|
|
92
|
+
## Important Requirements
|
|
93
|
+
|
|
94
|
+
- **MUST compile graph** before use: graph = builder.compile()
|
|
95
|
+
- Use START constant for entry points, END for terminal nodes
|
|
96
|
+
- Import required: from langgraph.graph import StateGraph, START, END
|
|
97
|
+
- For messages: from langgraph.graph.message import add_messages
|
|
98
|
+
- For commands: from langgraph.types import Command, Send
|
|
99
|
+
|
|
100
|
+
## LLM Usage
|
|
101
|
+
|
|
102
|
+
When calling llms, such as through ChatAnthropic, always specify structured output and call the models with .with_structured_output. Also specify method="json_schema".
|
|
103
|
+
|
|
104
|
+
Here is an example code snippet:
|
|
105
|
+
```
|
|
106
|
+
from langchain_anthropic import ChatAnthropic
|
|
107
|
+
from pydantic import BaseModel, Field
|
|
108
|
+
|
|
109
|
+
model = ChatAnthropic(model="claude-sonnet-4-5-20250929")
|
|
110
|
+
|
|
111
|
+
class Movie(BaseModel):
|
|
112
|
+
"""A movie with details."""
|
|
113
|
+
title: str = Field(..., description="The title of the movie")
|
|
114
|
+
year: int = Field(..., description="The year the movie was released")
|
|
115
|
+
director: str = Field(..., description="The director of the movie")
|
|
116
|
+
rating: float = Field(..., description="The movie's rating out of 10")
|
|
117
|
+
|
|
118
|
+
model_with_structure = model.with_structured_output(Movie, method="json_schema")
|
|
119
|
+
response = model_with_structure.invoke("Provide details about the movie Inception")
|
|
120
|
+
```
|
|
121
|
+
In this code snippet, response will contain an object like Movie(title='Inception', year=2010, director='Christopher Nolan', rating=8.8)
|
|
122
|
+
|
|
123
|
+
Generate production-ready, well-structured LangGraph workflows following these patterns.
|
|
124
|
+
|
|
125
|
+
Here is an example workflow for reference:
|
|
126
|
+
|
|
127
|
+
# Step 1: Define tools and model
|
|
128
|
+
|
|
129
|
+
from langchain.tools import tool
|
|
130
|
+
from langchain.chat_models import init_chat_model
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
model = init_chat_model(
|
|
134
|
+
"claude-sonnet-4-5-20250929",
|
|
135
|
+
temperature=0
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
# Define tools
|
|
140
|
+
@tool
|
|
141
|
+
def multiply(a: int, b: int) -> int:
|
|
142
|
+
"""Multiply a and b.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
a: First int
|
|
146
|
+
b: Second int
|
|
147
|
+
"""
|
|
148
|
+
return a * b
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@tool
|
|
152
|
+
def add(a: int, b: int) -> int:
|
|
153
|
+
"""Adds a and b.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
a: First int
|
|
157
|
+
b: Second int
|
|
158
|
+
"""
|
|
159
|
+
return a + b
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
@tool
|
|
163
|
+
def divide(a: int, b: int) -> float:
|
|
164
|
+
"""Divide a and b.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
a: First int
|
|
168
|
+
b: Second int
|
|
169
|
+
"""
|
|
170
|
+
return a / b
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
# Augment the LLM with tools
|
|
174
|
+
tools = [add, multiply, divide]
|
|
175
|
+
tools_by_name = {tool.name: tool for tool in tools}
|
|
176
|
+
model_with_tools = model.bind_tools(tools)
|
|
177
|
+
|
|
178
|
+
# Step 2: Define state
|
|
179
|
+
|
|
180
|
+
from langchain.messages import AnyMessage
|
|
181
|
+
from typing_extensions import TypedDict, Annotated
|
|
182
|
+
import operator
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class MessagesState(TypedDict):
|
|
186
|
+
messages: Annotated[list[AnyMessage], operator.add]
|
|
187
|
+
llm_calls: int
|
|
188
|
+
|
|
189
|
+
# Step 3: Define model node
|
|
190
|
+
from langchain.messages import SystemMessage
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def llm_call(state: dict):
|
|
194
|
+
"""LLM decides whether to call a tool or not"""
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
"messages": [
|
|
198
|
+
model_with_tools.invoke(
|
|
199
|
+
[
|
|
200
|
+
SystemMessage(
|
|
201
|
+
content="You are a helpful assistant tasked with performing arithmetic on a set of inputs."
|
|
202
|
+
)
|
|
203
|
+
]
|
|
204
|
+
+ state["messages"]
|
|
205
|
+
)
|
|
206
|
+
],
|
|
207
|
+
"llm_calls": state.get('llm_calls', 0) + 1
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
# Step 4: Define tool node
|
|
212
|
+
|
|
213
|
+
from langchain.messages import ToolMessage
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def tool_node(state: dict):
|
|
217
|
+
"""Performs the tool call"""
|
|
218
|
+
|
|
219
|
+
result = []
|
|
220
|
+
for tool_call in state["messages"][-1].tool_calls:
|
|
221
|
+
tool = tools_by_name[tool_call["name"]]
|
|
222
|
+
observation = tool.invoke(tool_call["args"])
|
|
223
|
+
result.append(ToolMessage(content=observation, tool_call_id=tool_call["id"]))
|
|
224
|
+
return {"messages": result}
|
|
225
|
+
|
|
226
|
+
# Step 5: Define logic to determine whether to end
|
|
227
|
+
|
|
228
|
+
from typing import Literal
|
|
229
|
+
from langgraph.graph import StateGraph, START, END
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
# Conditional edge function to route to the tool node or end based upon whether the LLM made a tool call
|
|
233
|
+
def should_continue(state: MessagesState) -> Literal["tool_node", END]:
|
|
234
|
+
"""Decide if we should continue the loop or stop based upon whether the LLM made a tool call"""
|
|
235
|
+
|
|
236
|
+
messages = state["messages"]
|
|
237
|
+
last_message = messages[-1]
|
|
238
|
+
|
|
239
|
+
# If the LLM makes a tool call, then perform an action
|
|
240
|
+
if last_message.tool_calls:
|
|
241
|
+
return "tool_node"
|
|
242
|
+
|
|
243
|
+
# Otherwise, we stop (reply to the user)
|
|
244
|
+
return END
|
|
245
|
+
|
|
246
|
+
# Step 6: Build agent
|
|
247
|
+
|
|
248
|
+
# Build workflow
|
|
249
|
+
agent_builder = StateGraph(MessagesState)
|
|
250
|
+
|
|
251
|
+
# Add nodes
|
|
252
|
+
agent_builder.add_node("llm_call", llm_call)
|
|
253
|
+
agent_builder.add_node("tool_node", tool_node)
|
|
254
|
+
|
|
255
|
+
# Add edges to connect nodes
|
|
256
|
+
agent_builder.add_edge(START, "llm_call")
|
|
257
|
+
agent_builder.add_conditional_edges(
|
|
258
|
+
"llm_call",
|
|
259
|
+
should_continue,
|
|
260
|
+
["tool_node", END]
|
|
261
|
+
)
|
|
262
|
+
agent_builder.add_edge("tool_node", "llm_call")
|
|
263
|
+
|
|
264
|
+
# Compile the agent
|
|
265
|
+
agent = agent_builder.compile()
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
from IPython.display import Image, display
|
|
269
|
+
# Show the agent
|
|
270
|
+
display(Image(agent.get_graph(xray=True).draw_mermaid_png()))
|
|
271
|
+
|
|
272
|
+
# Invoke
|
|
273
|
+
from langchain.messages import HumanMessage
|
|
274
|
+
messages = [HumanMessage(content="Add 3 and 4.")]
|
|
275
|
+
messages = agent.invoke({"messages": messages})
|
|
276
|
+
for m in messages["messages"]:
|
|
277
|
+
m.pretty_print()
|