@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,125 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: verification
|
|
3
|
+
description: Verify agent code against organizational policies and best practices. Trigger this skill when the user says something like "verify my agent", "review agent code", "check compliance", "audit my agent", "check my agent against rules", or "validate agent implementation"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Verify Agent
|
|
7
|
+
|
|
8
|
+
## Purpose
|
|
9
|
+
|
|
10
|
+
Verify agent code against business rules, security policies, and framework best practices from Kai's knowledge base. All analysis happens locally—code never leaves the user's machine.
|
|
11
|
+
|
|
12
|
+
## When to Use
|
|
13
|
+
|
|
14
|
+
Trigger this skill when the user asks to:
|
|
15
|
+
- "verify my agent"
|
|
16
|
+
- "review agent code"
|
|
17
|
+
- "check compliance"
|
|
18
|
+
- "audit my agent"
|
|
19
|
+
- "check my agent against rules"
|
|
20
|
+
- "validate agent implementation"
|
|
21
|
+
|
|
22
|
+
## Process
|
|
23
|
+
|
|
24
|
+
> **Note:** This skill assumes organizational and IT rules are already available in the conversation context from the `kai_prepare_context` call at conversation start (per CLAUDE.md rules).
|
|
25
|
+
|
|
26
|
+
### Step 1: Load Framework Best Practices
|
|
27
|
+
|
|
28
|
+
Check the `context-guide.md` for framework-specific best practices surfaced by `kai_prepare_context`:
|
|
29
|
+
|
|
30
|
+
These files define best practices, required patterns, and anti-patterns specific to that framework. Load and reference them during verification.
|
|
31
|
+
|
|
32
|
+
If framework best practices aren't available in `context/`, use the **documentation** skill (`.claude/skills/documentation/SKILL.md`) to search for relevant framework documentation.
|
|
33
|
+
### Step 2: Discover Agent Files
|
|
34
|
+
|
|
35
|
+
Locate agent implementation files in the project. Check these common locations:
|
|
36
|
+
|
|
37
|
+
**Directories:**
|
|
38
|
+
- `src/agent/`
|
|
39
|
+
- `agent/`
|
|
40
|
+
- `src/`
|
|
41
|
+
- Project root
|
|
42
|
+
|
|
43
|
+
**Key files to analyze:**
|
|
44
|
+
- `graph.py` - Main workflow/graph definition
|
|
45
|
+
- `tools.py` - Tool implementations
|
|
46
|
+
- `state.py` - State schema definitions
|
|
47
|
+
- `prompts.py` - Prompt templates
|
|
48
|
+
- `nodes.py` - Node function implementations
|
|
49
|
+
- `config.py` - Configuration and settings
|
|
50
|
+
|
|
51
|
+
Use `list_files` or similar to discover the actual structure, then read relevant files.
|
|
52
|
+
|
|
53
|
+
### Step 3: Verify Code Against Rules
|
|
54
|
+
|
|
55
|
+
Analyze the agent code against all available rules:
|
|
56
|
+
|
|
57
|
+
1. **Organizational rules** - Check code against company policies from conversation context
|
|
58
|
+
2. **IT/Security rules** - Check code against security requirements from conversation context
|
|
59
|
+
3. **Framework best practices** - Check code against patterns from the framework best practices in `context-guide.md`
|
|
60
|
+
|
|
61
|
+
For each rule, determine:
|
|
62
|
+
- ✅ **Pass** - Code complies with the rule
|
|
63
|
+
- ⚠️ **Warning** - Potential concern worth reviewing
|
|
64
|
+
- ❌ **Issue** - Clear violation that needs fixing
|
|
65
|
+
|
|
66
|
+
### Step 4: Generate Report
|
|
67
|
+
|
|
68
|
+
Output a structured verification report:
|
|
69
|
+
|
|
70
|
+
```markdown
|
|
71
|
+
# Agent Verification Report
|
|
72
|
+
|
|
73
|
+
**Project:** [project name or path]
|
|
74
|
+
**Date:** [current date]
|
|
75
|
+
**Files analyzed:** [list of files]
|
|
76
|
+
|
|
77
|
+
## Summary
|
|
78
|
+
|
|
79
|
+
✅ X checks passed | ⚠️ Y warnings | ❌ Z issues
|
|
80
|
+
|
|
81
|
+
## Findings
|
|
82
|
+
|
|
83
|
+
### ✅ Passing
|
|
84
|
+
- [Check name]: [Brief confirmation of compliance]
|
|
85
|
+
|
|
86
|
+
### ⚠️ Warnings
|
|
87
|
+
- [Check name]: [Description of concern]
|
|
88
|
+
- **Location:** [file:line if applicable]
|
|
89
|
+
- **Suggestion:** [How to address]
|
|
90
|
+
|
|
91
|
+
### ❌ Issues
|
|
92
|
+
- [Check name]: [Description of violation]
|
|
93
|
+
- **Location:** [file:line]
|
|
94
|
+
- **Rule:** [Which rule this violates]
|
|
95
|
+
- **Fix:** [Specific remediation steps]
|
|
96
|
+
|
|
97
|
+
## Recommendations
|
|
98
|
+
|
|
99
|
+
1. [Priority recommendation based on findings]
|
|
100
|
+
2. [Additional improvements]
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Step 5: Export Report (Optional)
|
|
104
|
+
|
|
105
|
+
After presenting the report, ask the user:
|
|
106
|
+
|
|
107
|
+
> Would you like to save this verification report to a file?
|
|
108
|
+
|
|
109
|
+
If the user confirms, save the report to:
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
reports/verification/YYYY-MM-DD_HH-MM-SS.md
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
- Use the current date and time for the filename (e.g., `2026-02-10_15-28-32.md`)
|
|
116
|
+
- Create the `reports/verification/` directory if it doesn't exist
|
|
117
|
+
- Confirm the save location to the user after writing
|
|
118
|
+
|
|
119
|
+
## Notes
|
|
120
|
+
|
|
121
|
+
- **Privacy first:** All code analysis happens locally. Nothing is sent to external services.
|
|
122
|
+
- **Rules come from Kai:** The knowledge base provides organization-specific rules. Apply them as written.
|
|
123
|
+
- **Framework best practices:** Don't duplicate framework best practices here—reference files in `context-guide.md`.
|
|
124
|
+
- **Be specific:** Include file names and line numbers when reporting issues.
|
|
125
|
+
- **Explain the "why":** Help developers understand why each rule matters.
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# LangGraph Agent Framework
|
|
2
|
+
|
|
3
|
+
A minimal but functional LangGraph Python project demonstrating core agent patterns.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This scaffold provides a working LangGraph agent with:
|
|
8
|
+
- State management using TypedDict
|
|
9
|
+
- Tool calling with the ReAct pattern
|
|
10
|
+
- Conditional routing based on tool calls
|
|
11
|
+
- Well-commented code for learning
|
|
12
|
+
|
|
13
|
+
## Requirements
|
|
14
|
+
|
|
15
|
+
- Python 3.11+
|
|
16
|
+
- `uv` (recommended) or `pip`
|
|
17
|
+
|
|
18
|
+
## Setup
|
|
19
|
+
|
|
20
|
+
### Using uv (Recommended)
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# Create and activate virtual environment
|
|
24
|
+
uv venv
|
|
25
|
+
source .venv/bin/activate # On Windows: .venv\Scripts\activate
|
|
26
|
+
|
|
27
|
+
# Install dependencies
|
|
28
|
+
uv pip install -e .
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Using pip
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# Create and activate virtual environment
|
|
35
|
+
python -m venv .venv
|
|
36
|
+
source .venv/bin/activate # On Windows: .venv\Scripts\activate
|
|
37
|
+
|
|
38
|
+
# Install dependencies
|
|
39
|
+
pip install -e .
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Configuration
|
|
43
|
+
|
|
44
|
+
Set your API key as an environment variable:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# For Anthropic (default)
|
|
48
|
+
export ANTHROPIC_API_KEY="your-key-here"
|
|
49
|
+
|
|
50
|
+
# Or for OpenAI
|
|
51
|
+
export OPENAI_API_KEY="your-key-here"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Running the Agent
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# Run with default example
|
|
58
|
+
python main.py
|
|
59
|
+
|
|
60
|
+
# Or import and use programmatically
|
|
61
|
+
python -c "from src.agent import run_agent; print(run_agent('Your prompt here'))"
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Project Structure
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
langgraph/
|
|
68
|
+
├── README.md # This file
|
|
69
|
+
├── pyproject.toml # Project configuration
|
|
70
|
+
├── main.py # Entry point
|
|
71
|
+
└── src/
|
|
72
|
+
└── agent/
|
|
73
|
+
├── __init__.py # Package exports
|
|
74
|
+
├── state.py # State TypedDict definition
|
|
75
|
+
├── tools.py # Tool definitions (implement your scenario tools here)
|
|
76
|
+
└── graph.py # Main StateGraph definition
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Extending the Agent
|
|
80
|
+
|
|
81
|
+
### Adding New Tools
|
|
82
|
+
|
|
83
|
+
1. Define your tool in `src/agent/tools.py`:
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
@tool
|
|
87
|
+
def my_new_tool(param: str) -> str:
|
|
88
|
+
"""Description of what the tool does.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
param: Description of the parameter
|
|
92
|
+
"""
|
|
93
|
+
return "result"
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
2. Add it to the `tools` list in the same file.
|
|
97
|
+
|
|
98
|
+
### Modifying State
|
|
99
|
+
|
|
100
|
+
Edit `src/agent/state.py` to add new state fields:
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
class AgentState(TypedDict):
|
|
104
|
+
messages: Annotated[list[AnyMessage], operator.add]
|
|
105
|
+
my_new_field: str # Add new fields here
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Changing the Graph Structure
|
|
109
|
+
|
|
110
|
+
Edit `src/agent/graph.py` to:
|
|
111
|
+
- Add new nodes with `builder.add_node()`
|
|
112
|
+
- Add new edges with `builder.add_edge()` or `builder.add_conditional_edges()`
|
|
113
|
+
|
|
114
|
+
## Learning Resources
|
|
115
|
+
|
|
116
|
+
- [LangGraph Documentation](https://langchain-ai.github.io/langgraph/)
|
|
117
|
+
- [LangChain Tools Guide](https://python.langchain.com/docs/modules/tools/)
|
|
118
|
+
- See `.claude/rules/langgraph.md` for patterns and best practices
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
LangGraph Agent Entry Point
|
|
4
|
+
|
|
5
|
+
This script demonstrates how to run the LangGraph agent with example queries.
|
|
6
|
+
It shows both interactive conversation and programmatic usage patterns.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
# Run with default examples
|
|
10
|
+
python main.py
|
|
11
|
+
|
|
12
|
+
# Import and use programmatically
|
|
13
|
+
from src.agent import run_agent
|
|
14
|
+
result = run_agent("What is 10 + 5?")
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import sys
|
|
18
|
+
|
|
19
|
+
from langchain_core.messages import HumanMessage
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def main():
|
|
23
|
+
"""Run the agent with example queries."""
|
|
24
|
+
# Import here to catch any configuration errors early
|
|
25
|
+
try:
|
|
26
|
+
from src.agent import create_agent, run_agent
|
|
27
|
+
except ValueError as e:
|
|
28
|
+
print(f"Configuration error: {e}")
|
|
29
|
+
print("\nPlease set your API key:")
|
|
30
|
+
print(" export ANTHROPIC_API_KEY='your-key-here'")
|
|
31
|
+
print(" # or")
|
|
32
|
+
print(" export OPENAI_API_KEY='your-key-here'")
|
|
33
|
+
sys.exit(1)
|
|
34
|
+
|
|
35
|
+
print("=" * 50)
|
|
36
|
+
print("LangGraph Agent Demo")
|
|
37
|
+
print("=" * 50)
|
|
38
|
+
|
|
39
|
+
# Example queries that demonstrate tool usage
|
|
40
|
+
example_queries = [
|
|
41
|
+
"What is 15 + 27?",
|
|
42
|
+
"Calculate 8 * 12",
|
|
43
|
+
"What is 100 divided by 4?",
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
print("\nRunning example queries...\n")
|
|
47
|
+
|
|
48
|
+
for query in example_queries:
|
|
49
|
+
print(f"User: {query}")
|
|
50
|
+
try:
|
|
51
|
+
response = run_agent(query)
|
|
52
|
+
print(f"Agent: {response}\n")
|
|
53
|
+
except Exception as e:
|
|
54
|
+
print(f"Error: {e}\n")
|
|
55
|
+
|
|
56
|
+
# Demonstrate more detailed usage with state inspection
|
|
57
|
+
print("-" * 50)
|
|
58
|
+
print("Detailed execution example:")
|
|
59
|
+
print("-" * 50)
|
|
60
|
+
|
|
61
|
+
agent = create_agent()
|
|
62
|
+
|
|
63
|
+
# Run with full state tracking
|
|
64
|
+
initial_state = {
|
|
65
|
+
"messages": [HumanMessage(content="What is 25 multiplied by 4?")],
|
|
66
|
+
"tool_call_count": 0,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
print(f"\nInitial query: {initial_state['messages'][0].content}")
|
|
70
|
+
|
|
71
|
+
# Execute the agent
|
|
72
|
+
final_state = agent.invoke(initial_state)
|
|
73
|
+
|
|
74
|
+
# Show the conversation flow
|
|
75
|
+
print("\nConversation flow:")
|
|
76
|
+
for i, msg in enumerate(final_state["messages"]):
|
|
77
|
+
msg_type = type(msg).__name__
|
|
78
|
+
# Handle content that may be a string or list (e.g., tool call blocks)
|
|
79
|
+
raw_content = msg.content
|
|
80
|
+
if isinstance(raw_content, list):
|
|
81
|
+
content = (
|
|
82
|
+
str(raw_content)[:100] + "..."
|
|
83
|
+
if len(str(raw_content)) > 100
|
|
84
|
+
else str(raw_content)
|
|
85
|
+
)
|
|
86
|
+
else:
|
|
87
|
+
content = (
|
|
88
|
+
raw_content[:100] + "..." if len(raw_content) > 100 else raw_content
|
|
89
|
+
)
|
|
90
|
+
print(f" {i + 1}. [{msg_type}] {content}")
|
|
91
|
+
|
|
92
|
+
print(f"\nTotal tool calls: {final_state['tool_call_count']}")
|
|
93
|
+
print(f"Final answer: {final_state['messages'][-1].content}")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
if __name__ == "__main__":
|
|
97
|
+
main()
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "langgraph-agent"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "A minimal LangGraph agent scaffold for learning and testing"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.11"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"langgraph>=0.2.0",
|
|
9
|
+
"langchain>=0.3.0",
|
|
10
|
+
"langchain-anthropic>=0.2.0",
|
|
11
|
+
"langchain-openai>=0.2.0",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[project.optional-dependencies]
|
|
15
|
+
dev = [
|
|
16
|
+
"pytest>=8.0.0",
|
|
17
|
+
"pytest-asyncio>=0.24.0",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[build-system]
|
|
21
|
+
requires = ["hatchling"]
|
|
22
|
+
build-backend = "hatchling.build"
|
|
23
|
+
|
|
24
|
+
[tool.hatch.build.targets.wheel]
|
|
25
|
+
packages = ["src/agent"]
|
|
26
|
+
|
|
27
|
+
[tool.pytest.ini_options]
|
|
28
|
+
asyncio_mode = "auto"
|
|
29
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""
|
|
2
|
+
LangGraph Agent Package
|
|
3
|
+
|
|
4
|
+
This package provides a minimal but functional LangGraph agent that demonstrates
|
|
5
|
+
core patterns including state management, tool calling, and conditional routing.
|
|
6
|
+
|
|
7
|
+
Example usage:
|
|
8
|
+
from agent import run_agent, create_agent
|
|
9
|
+
|
|
10
|
+
# Quick run with a message
|
|
11
|
+
result = run_agent("What is 15 + 27?")
|
|
12
|
+
print(result)
|
|
13
|
+
|
|
14
|
+
# Or get the compiled graph for more control
|
|
15
|
+
agent = create_agent()
|
|
16
|
+
result = agent.invoke({"messages": [HumanMessage(content="Hello")]})
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from .graph import create_agent, run_agent
|
|
20
|
+
from .state import AgentState
|
|
21
|
+
from .tools import add, divide, multiply, tools
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"create_agent",
|
|
25
|
+
"run_agent",
|
|
26
|
+
"AgentState",
|
|
27
|
+
"tools",
|
|
28
|
+
"add",
|
|
29
|
+
"multiply",
|
|
30
|
+
"divide",
|
|
31
|
+
]
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"""
|
|
2
|
+
LangGraph Agent Definition
|
|
3
|
+
|
|
4
|
+
This module defines the main StateGraph for the agent. It implements a standard
|
|
5
|
+
ReAct-style (Reasoning + Acting) agent pattern where the LLM can decide to
|
|
6
|
+
either respond directly or call tools to gather information.
|
|
7
|
+
|
|
8
|
+
Key Concepts:
|
|
9
|
+
- StateGraph: The container for nodes and edges
|
|
10
|
+
- Nodes: Functions that perform work (LLM calls, tool execution)
|
|
11
|
+
- Edges: Connections that determine flow between nodes
|
|
12
|
+
- Conditional edges: Dynamic routing based on state
|
|
13
|
+
|
|
14
|
+
Graph Structure:
|
|
15
|
+
START → call_model → [should_continue] → execute_tools → call_model
|
|
16
|
+
↘ ↗
|
|
17
|
+
→ END (if no tool calls)
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import os
|
|
21
|
+
from typing import Literal
|
|
22
|
+
|
|
23
|
+
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage, ToolMessage
|
|
24
|
+
from langgraph.graph import END, START, StateGraph
|
|
25
|
+
|
|
26
|
+
from .state import AgentState
|
|
27
|
+
from .tools import tools, tools_by_name
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def get_model():
|
|
31
|
+
"""
|
|
32
|
+
Initialize the LLM with tool binding.
|
|
33
|
+
|
|
34
|
+
This function creates the chat model and binds the available tools to it.
|
|
35
|
+
The model will include tool definitions in its context, allowing it to
|
|
36
|
+
decide when and how to call tools.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
A chat model instance with tools bound.
|
|
40
|
+
|
|
41
|
+
Note:
|
|
42
|
+
Set ANTHROPIC_API_KEY or OPENAI_API_KEY environment variable.
|
|
43
|
+
Defaults to Anthropic if ANTHROPIC_API_KEY is set.
|
|
44
|
+
"""
|
|
45
|
+
# Try Anthropic first (preferred), then fall back to OpenAI
|
|
46
|
+
if os.environ.get("ANTHROPIC_API_KEY"):
|
|
47
|
+
from langchain_anthropic import ChatAnthropic
|
|
48
|
+
|
|
49
|
+
model = ChatAnthropic(model="claude-sonnet-4-5-20250929", temperature=0)
|
|
50
|
+
elif os.environ.get("OPENAI_API_KEY"):
|
|
51
|
+
from langchain_openai import ChatOpenAI
|
|
52
|
+
|
|
53
|
+
model = ChatOpenAI(model="gpt-4o", temperature=0)
|
|
54
|
+
else:
|
|
55
|
+
raise ValueError(
|
|
56
|
+
"No API key found. Set ANTHROPIC_API_KEY or OPENAI_API_KEY environment variable."
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# Bind tools to the model so it knows what tools are available
|
|
60
|
+
return model.bind_tools(tools)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# System prompt that instructs the agent on its behavior
|
|
64
|
+
SYSTEM_PROMPT = """You are a helpful assistant that can perform arithmetic operations.
|
|
65
|
+
|
|
66
|
+
When asked to do math, use the available tools (add, multiply, divide) to compute the answer.
|
|
67
|
+
Always show your work by using the appropriate tool calls.
|
|
68
|
+
|
|
69
|
+
Be concise in your responses."""
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def call_model(state: AgentState) -> dict:
|
|
73
|
+
"""
|
|
74
|
+
Node that calls the LLM to generate a response.
|
|
75
|
+
|
|
76
|
+
This node takes the current conversation state, prepends a system message,
|
|
77
|
+
and asks the LLM to generate a response. The LLM may decide to:
|
|
78
|
+
1. Respond directly to the user
|
|
79
|
+
2. Call one or more tools to gather information
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
state: Current agent state containing messages and metadata
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
State update dict with the new AI message appended to messages
|
|
86
|
+
|
|
87
|
+
Note:
|
|
88
|
+
The return value is a partial state update. Since messages uses
|
|
89
|
+
operator.add as its reducer, the new message will be appended
|
|
90
|
+
to the existing list, not replace it.
|
|
91
|
+
"""
|
|
92
|
+
model = get_model()
|
|
93
|
+
|
|
94
|
+
# Prepend system message to guide the model's behavior
|
|
95
|
+
messages_with_system = [SystemMessage(content=SYSTEM_PROMPT)] + state["messages"]
|
|
96
|
+
|
|
97
|
+
# Invoke the model and get response
|
|
98
|
+
response = model.invoke(messages_with_system)
|
|
99
|
+
|
|
100
|
+
# Return state update - the reducer will append this to existing messages
|
|
101
|
+
return {"messages": [response]}
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def execute_tools(state: AgentState) -> dict:
|
|
105
|
+
"""
|
|
106
|
+
Node that executes tool calls from the LLM's response.
|
|
107
|
+
|
|
108
|
+
When the LLM decides to call tools, this node:
|
|
109
|
+
1. Extracts tool calls from the last AI message
|
|
110
|
+
2. Executes each tool with the provided arguments
|
|
111
|
+
3. Returns ToolMessage results for each call
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
state: Current agent state (last message should have tool_calls)
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
State update dict with ToolMessages for each tool call result,
|
|
118
|
+
and incremented tool_call_count
|
|
119
|
+
|
|
120
|
+
Note:
|
|
121
|
+
ToolMessage includes tool_call_id to match results with calls.
|
|
122
|
+
This is important for the LLM to understand which result
|
|
123
|
+
corresponds to which tool call.
|
|
124
|
+
"""
|
|
125
|
+
# Get the last message which contains the tool calls
|
|
126
|
+
last_message: AIMessage = state["messages"][-1]
|
|
127
|
+
|
|
128
|
+
# Execute each tool call and collect results
|
|
129
|
+
tool_results = []
|
|
130
|
+
for tool_call in last_message.tool_calls:
|
|
131
|
+
# Look up the tool by name
|
|
132
|
+
tool = tools_by_name[tool_call["name"]]
|
|
133
|
+
|
|
134
|
+
# Execute the tool with provided arguments
|
|
135
|
+
result = tool.invoke(tool_call["args"])
|
|
136
|
+
|
|
137
|
+
# Create a ToolMessage with the result
|
|
138
|
+
# tool_call_id links this result back to the specific call
|
|
139
|
+
tool_results.append(
|
|
140
|
+
ToolMessage(
|
|
141
|
+
content=str(result),
|
|
142
|
+
tool_call_id=tool_call["id"],
|
|
143
|
+
)
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
# Return state update with tool results and incremented count
|
|
147
|
+
current_count = state.get("tool_call_count", 0)
|
|
148
|
+
return {
|
|
149
|
+
"messages": tool_results,
|
|
150
|
+
"tool_call_count": current_count + len(tool_results),
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def should_continue(state: AgentState) -> Literal["execute_tools", "__end__"]:
|
|
155
|
+
"""
|
|
156
|
+
Conditional edge function to determine next step.
|
|
157
|
+
|
|
158
|
+
This function examines the last message to decide whether to:
|
|
159
|
+
- Execute tools (if the LLM made tool calls)
|
|
160
|
+
- End the conversation (if the LLM gave a final response)
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
state: Current agent state
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
"execute_tools" if there are tool calls to process,
|
|
167
|
+
END if the conversation should terminate
|
|
168
|
+
|
|
169
|
+
Note:
|
|
170
|
+
This is a routing function used by add_conditional_edges().
|
|
171
|
+
It doesn't modify state, just returns the next node name.
|
|
172
|
+
"""
|
|
173
|
+
last_message = state["messages"][-1]
|
|
174
|
+
|
|
175
|
+
# Check if the model made any tool calls
|
|
176
|
+
if hasattr(last_message, "tool_calls") and last_message.tool_calls:
|
|
177
|
+
return "execute_tools"
|
|
178
|
+
|
|
179
|
+
# No tool calls means the model is done - end the conversation
|
|
180
|
+
return END
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def create_agent() -> StateGraph:
|
|
184
|
+
"""
|
|
185
|
+
Build and compile the agent graph.
|
|
186
|
+
|
|
187
|
+
This function constructs the StateGraph by:
|
|
188
|
+
1. Creating a new StateGraph with our state schema
|
|
189
|
+
2. Adding nodes for each processing step
|
|
190
|
+
3. Connecting nodes with edges (both direct and conditional)
|
|
191
|
+
4. Compiling the graph for execution
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
A compiled StateGraph ready for invocation
|
|
195
|
+
|
|
196
|
+
Graph structure:
|
|
197
|
+
START → call_model → [should_continue] → execute_tools → call_model
|
|
198
|
+
↘ ↗
|
|
199
|
+
→ END (if no tool calls)
|
|
200
|
+
"""
|
|
201
|
+
# Step 1: Create the graph builder with our state schema
|
|
202
|
+
builder = StateGraph(AgentState)
|
|
203
|
+
|
|
204
|
+
# Step 2: Add nodes
|
|
205
|
+
# Each node is a function that takes state and returns state updates
|
|
206
|
+
builder.add_node("call_model", call_model)
|
|
207
|
+
builder.add_node("execute_tools", execute_tools)
|
|
208
|
+
|
|
209
|
+
# Step 3: Add edges
|
|
210
|
+
# START → call_model: Entry point
|
|
211
|
+
builder.add_edge(START, "call_model")
|
|
212
|
+
|
|
213
|
+
# call_model → [conditional]: Check if we need to execute tools
|
|
214
|
+
builder.add_conditional_edges(
|
|
215
|
+
"call_model",
|
|
216
|
+
should_continue,
|
|
217
|
+
# Map of return values to destination nodes
|
|
218
|
+
# The function returns either "execute_tools" or END
|
|
219
|
+
["execute_tools", END],
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
# execute_tools → call_model: After executing tools, go back to the model
|
|
223
|
+
builder.add_edge("execute_tools", "call_model")
|
|
224
|
+
|
|
225
|
+
# Step 4: Compile the graph
|
|
226
|
+
# IMPORTANT: Must compile before the graph can be invoked
|
|
227
|
+
return builder.compile()
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def run_agent(user_message: str) -> str:
|
|
231
|
+
"""
|
|
232
|
+
Convenience function to run the agent with a single message.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
user_message: The user's input message
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
The agent's final response as a string
|
|
239
|
+
|
|
240
|
+
Example:
|
|
241
|
+
>>> result = run_agent("What is 25 * 4?")
|
|
242
|
+
>>> print(result)
|
|
243
|
+
"25 * 4 = 100"
|
|
244
|
+
"""
|
|
245
|
+
agent = create_agent()
|
|
246
|
+
|
|
247
|
+
# Create initial state with the user's message
|
|
248
|
+
initial_state = {
|
|
249
|
+
"messages": [HumanMessage(content=user_message)],
|
|
250
|
+
"tool_call_count": 0,
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
# Run the agent to completion
|
|
254
|
+
final_state = agent.invoke(initial_state)
|
|
255
|
+
|
|
256
|
+
# Return the last message content (the agent's response)
|
|
257
|
+
return final_state["messages"][-1].content
|