@agentvoy/core 0.1.0 → 0.3.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/dist/adapters/anthropic.d.ts +8 -0
- package/dist/adapters/anthropic.d.ts.map +1 -0
- package/dist/adapters/anthropic.js +250 -0
- package/dist/adapters/anthropic.js.map +1 -0
- package/dist/adapters/crewai.js +10 -2
- package/dist/adapters/crewai.js.map +1 -1
- package/dist/adapters/google-adk.js +1 -0
- package/dist/adapters/google-adk.js.map +1 -1
- package/dist/adapters/index.js +4 -0
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/langgraph.d.ts +8 -0
- package/dist/adapters/langgraph.d.ts.map +1 -0
- package/dist/adapters/langgraph.js +317 -0
- package/dist/adapters/langgraph.js.map +1 -0
- package/dist/adapters/openai.js +20 -8
- package/dist/adapters/openai.js.map +1 -1
- package/package.json +1 -1
- package/src/adapters/anthropic.ts +271 -0
- package/src/adapters/crewai.ts +10 -2
- package/src/adapters/google-adk.ts +1 -0
- package/src/adapters/index.ts +4 -0
- package/src/adapters/langgraph.ts +344 -0
- package/src/adapters/openai.ts +20 -8
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LangGraph Adapter
|
|
3
|
+
*
|
|
4
|
+
* Scaffolds projects using LangGraph (Python) — stateful, graph-based agent workflows.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type {
|
|
8
|
+
FrameworkAdapter,
|
|
9
|
+
ScaffoldConfig,
|
|
10
|
+
ScaffoldResult,
|
|
11
|
+
AgentGuardConfig,
|
|
12
|
+
ValidationResult,
|
|
13
|
+
GeneratedFile,
|
|
14
|
+
} from "../types.js";
|
|
15
|
+
import { generateDefaultConfig } from "../config.js";
|
|
16
|
+
|
|
17
|
+
export const langgraphAdapter: FrameworkAdapter = {
|
|
18
|
+
name: "langgraph",
|
|
19
|
+
displayName: "LangGraph",
|
|
20
|
+
language: "python",
|
|
21
|
+
|
|
22
|
+
async scaffold(config: ScaffoldConfig): Promise<ScaffoldResult> {
|
|
23
|
+
const files: GeneratedFile[] = [
|
|
24
|
+
{
|
|
25
|
+
path: "agent.py",
|
|
26
|
+
content: generateAgentFile(config),
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
path: "tools.py",
|
|
30
|
+
content: generateToolsFile(),
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
path: "state.py",
|
|
34
|
+
content: generateStateFile(config),
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
path: "run.py",
|
|
38
|
+
content: generateRunFile(config),
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
path: "requirements.txt",
|
|
42
|
+
content: generateRequirements(config),
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
path: ".env.example",
|
|
46
|
+
content: generateEnvExample(config),
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
path: "agent.guard.yml",
|
|
50
|
+
content: generateDefaultConfig(
|
|
51
|
+
config.projectName,
|
|
52
|
+
"langgraph",
|
|
53
|
+
config.model.model || "gpt-4o"
|
|
54
|
+
),
|
|
55
|
+
},
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
files,
|
|
60
|
+
dependencies: {},
|
|
61
|
+
devDependencies: {},
|
|
62
|
+
scripts: {
|
|
63
|
+
start: "python run.py",
|
|
64
|
+
},
|
|
65
|
+
postInstallInstructions: [
|
|
66
|
+
"pip install -r requirements.txt",
|
|
67
|
+
"cp .env.example .env",
|
|
68
|
+
`Add your ${getApiKeyEnv(config)} to .env`,
|
|
69
|
+
"python run.py",
|
|
70
|
+
],
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
validateConfig(config: AgentGuardConfig): ValidationResult {
|
|
75
|
+
const errors: { field: string; message: string }[] = [];
|
|
76
|
+
const warnings: { field: string; message: string }[] = [];
|
|
77
|
+
|
|
78
|
+
const supported = ["openai", "anthropic", "google"];
|
|
79
|
+
if (!supported.includes(config.model.provider)) {
|
|
80
|
+
warnings.push({
|
|
81
|
+
field: "model.provider",
|
|
82
|
+
message: `LangGraph works best with providers: ${supported.join(", ")}. Got "${config.model.provider}"`,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
getDependencies() {
|
|
90
|
+
return {
|
|
91
|
+
langgraph: ">=0.2.0",
|
|
92
|
+
langchain: ">=0.3.0",
|
|
93
|
+
"langchain-core": ">=0.3.0",
|
|
94
|
+
"python-dotenv": ">=1.0.0",
|
|
95
|
+
};
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
function getApiKeyEnv(config: ScaffoldConfig): string {
|
|
100
|
+
const envMap: Record<string, string> = {
|
|
101
|
+
openai: "OPENAI_API_KEY",
|
|
102
|
+
anthropic: "ANTHROPIC_API_KEY",
|
|
103
|
+
google: "GOOGLE_API_KEY",
|
|
104
|
+
};
|
|
105
|
+
return envMap[config.model.provider] || "API_KEY";
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function getLangChainPackage(config: ScaffoldConfig): string {
|
|
109
|
+
const pkgMap: Record<string, string> = {
|
|
110
|
+
openai: "langchain-openai",
|
|
111
|
+
anthropic: "langchain-anthropic",
|
|
112
|
+
google: "langchain-google-genai",
|
|
113
|
+
};
|
|
114
|
+
return pkgMap[config.model.provider] || "langchain-openai";
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function getLLMImport(config: ScaffoldConfig): string {
|
|
118
|
+
const importMap: Record<string, string> = {
|
|
119
|
+
openai: "from langchain_openai import ChatOpenAI",
|
|
120
|
+
anthropic: "from langchain_anthropic import ChatAnthropic",
|
|
121
|
+
google: "from langchain_google_genai import ChatGoogleGenerativeAI",
|
|
122
|
+
};
|
|
123
|
+
return importMap[config.model.provider] || "from langchain_openai import ChatOpenAI";
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function getLLMClass(config: ScaffoldConfig): string {
|
|
127
|
+
const classMap: Record<string, string> = {
|
|
128
|
+
openai: "ChatOpenAI",
|
|
129
|
+
anthropic: "ChatAnthropic",
|
|
130
|
+
google: "ChatGoogleGenerativeAI",
|
|
131
|
+
};
|
|
132
|
+
return classMap[config.model.provider] || "ChatOpenAI";
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function generateAgentFile(config: ScaffoldConfig): string {
|
|
136
|
+
const model = config.model.model || "gpt-4o";
|
|
137
|
+
const maxIterations = config.guardrails?.behavior?.max_iterations || 20;
|
|
138
|
+
const llmImport = getLLMImport(config);
|
|
139
|
+
const llmClass = getLLMClass(config);
|
|
140
|
+
|
|
141
|
+
return `"""
|
|
142
|
+
${config.projectName} — Built with AgentVoy
|
|
143
|
+
https://github.com/agentvoy
|
|
144
|
+
|
|
145
|
+
LangGraph agent with a stateful agentic loop.
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
from langchain_core.messages import HumanMessage, SystemMessage
|
|
149
|
+
from langgraph.graph import StateGraph, END
|
|
150
|
+
from langgraph.prebuilt import ToolNode
|
|
151
|
+
${llmImport}
|
|
152
|
+
|
|
153
|
+
from state import AgentState
|
|
154
|
+
from tools import get_tools
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def create_graph():
|
|
158
|
+
"""Build the agent state graph."""
|
|
159
|
+
tools = get_tools()
|
|
160
|
+
llm = ${llmClass}(model="${model}").bind_tools(tools)
|
|
161
|
+
tool_node = ToolNode(tools)
|
|
162
|
+
|
|
163
|
+
def should_continue(state: AgentState) -> str:
|
|
164
|
+
"""Route: call tools or finish."""
|
|
165
|
+
messages = state["messages"]
|
|
166
|
+
last = messages[-1]
|
|
167
|
+
if last.tool_calls:
|
|
168
|
+
return "tools"
|
|
169
|
+
return END
|
|
170
|
+
|
|
171
|
+
def call_model(state: AgentState) -> dict:
|
|
172
|
+
"""Call the LLM with current messages."""
|
|
173
|
+
messages = state["messages"]
|
|
174
|
+
iteration = state.get("iteration", 0)
|
|
175
|
+
|
|
176
|
+
if iteration >= ${maxIterations}:
|
|
177
|
+
from langchain_core.messages import AIMessage
|
|
178
|
+
return {
|
|
179
|
+
"messages": [AIMessage(content="Max iterations reached.")],
|
|
180
|
+
"iteration": iteration,
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
response = llm.invoke(messages)
|
|
184
|
+
return {
|
|
185
|
+
"messages": [response],
|
|
186
|
+
"iteration": iteration + 1,
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
# Build the graph
|
|
190
|
+
graph = StateGraph(AgentState)
|
|
191
|
+
graph.add_node("agent", call_model)
|
|
192
|
+
graph.add_node("tools", tool_node)
|
|
193
|
+
|
|
194
|
+
graph.set_entry_point("agent")
|
|
195
|
+
graph.add_conditional_edges("agent", should_continue)
|
|
196
|
+
graph.add_edge("tools", "agent")
|
|
197
|
+
|
|
198
|
+
return graph.compile()
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def run_agent(prompt: str) -> str:
|
|
202
|
+
"""Run the agent graph with a user prompt."""
|
|
203
|
+
app = create_graph()
|
|
204
|
+
|
|
205
|
+
initial_state = {
|
|
206
|
+
"messages": [HumanMessage(content=prompt)],
|
|
207
|
+
"iteration": 0,
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
final_state = app.invoke(initial_state)
|
|
211
|
+
messages = final_state["messages"]
|
|
212
|
+
|
|
213
|
+
# Return the last AI message text
|
|
214
|
+
for msg in reversed(messages):
|
|
215
|
+
if hasattr(msg, "content") and isinstance(msg.content, str):
|
|
216
|
+
return msg.content
|
|
217
|
+
|
|
218
|
+
return "Done."
|
|
219
|
+
`;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function generateStateFile(_config: ScaffoldConfig): string {
|
|
223
|
+
return `"""
|
|
224
|
+
Agent state definition for LangGraph.
|
|
225
|
+
"""
|
|
226
|
+
|
|
227
|
+
from typing import TypedDict, Annotated, Sequence
|
|
228
|
+
from langchain_core.messages import BaseMessage
|
|
229
|
+
import operator
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class AgentState(TypedDict):
|
|
233
|
+
"""State passed between nodes in the graph."""
|
|
234
|
+
messages: Annotated[Sequence[BaseMessage], operator.add]
|
|
235
|
+
iteration: int
|
|
236
|
+
`;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function generateToolsFile(): string {
|
|
240
|
+
return `"""
|
|
241
|
+
Agent tools — add your custom tools here.
|
|
242
|
+
LangGraph uses @tool decorated functions from langchain_core.
|
|
243
|
+
"""
|
|
244
|
+
|
|
245
|
+
from langchain_core.tools import tool
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
@tool
|
|
249
|
+
def search_web(query: str) -> str:
|
|
250
|
+
"""Search the web for information on a given topic.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
query: The search query.
|
|
254
|
+
"""
|
|
255
|
+
# TODO: Implement your search logic (e.g., Tavily, Serper, Brave Search)
|
|
256
|
+
# Example with Tavily:
|
|
257
|
+
# from tavily import TavilyClient
|
|
258
|
+
# client = TavilyClient(api_key=os.environ["TAVILY_API_KEY"])
|
|
259
|
+
# return client.search(query)["results"][0]["content"]
|
|
260
|
+
return f"Search results for: {query}"
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
@tool
|
|
264
|
+
def read_file(path: str) -> str:
|
|
265
|
+
"""Read the contents of a file.
|
|
266
|
+
|
|
267
|
+
Args:
|
|
268
|
+
path: Path to the file to read.
|
|
269
|
+
"""
|
|
270
|
+
try:
|
|
271
|
+
with open(path, "r") as f:
|
|
272
|
+
return f.read()
|
|
273
|
+
except FileNotFoundError:
|
|
274
|
+
return f"File not found: {path}"
|
|
275
|
+
except PermissionError:
|
|
276
|
+
return f"Permission denied: {path}"
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def get_tools() -> list:
|
|
280
|
+
"""Return all available tools."""
|
|
281
|
+
return [search_web, read_file]
|
|
282
|
+
`;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function generateRunFile(config: ScaffoldConfig): string {
|
|
286
|
+
return `"""
|
|
287
|
+
Run the ${config.projectName} agent.
|
|
288
|
+
"""
|
|
289
|
+
|
|
290
|
+
from dotenv import load_dotenv
|
|
291
|
+
from agentvoy_guard import Guard
|
|
292
|
+
from agent import run_agent
|
|
293
|
+
|
|
294
|
+
load_dotenv()
|
|
295
|
+
|
|
296
|
+
guard = Guard.from_config()
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def main():
|
|
300
|
+
print("\\n🚀 ${config.projectName} — Powered by AgentVoy")
|
|
301
|
+
print("=" * 50)
|
|
302
|
+
print("Type your prompt (or 'quit' to exit):\\n")
|
|
303
|
+
|
|
304
|
+
while True:
|
|
305
|
+
try:
|
|
306
|
+
prompt = input("> ")
|
|
307
|
+
if prompt.lower() in ("quit", "exit", "q"):
|
|
308
|
+
print("\\nGoodbye!")
|
|
309
|
+
break
|
|
310
|
+
if not prompt.strip():
|
|
311
|
+
continue
|
|
312
|
+
|
|
313
|
+
print("\\nThinking...\\n")
|
|
314
|
+
with guard.session() as session:
|
|
315
|
+
session.check_input(prompt)
|
|
316
|
+
result = run_agent(prompt)
|
|
317
|
+
session.check_output(result)
|
|
318
|
+
print(f"\\n{result}\\n")
|
|
319
|
+
print(f"[guard] {guard.last_summary}")
|
|
320
|
+
except KeyboardInterrupt:
|
|
321
|
+
print("\\n\\nGoodbye!")
|
|
322
|
+
break
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
if __name__ == "__main__":
|
|
326
|
+
main()
|
|
327
|
+
`;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function generateRequirements(config: ScaffoldConfig): string {
|
|
331
|
+
const langchainPkg = getLangChainPackage(config);
|
|
332
|
+
return `langgraph>=0.2.0
|
|
333
|
+
langchain>=0.3.0
|
|
334
|
+
langchain-core>=0.3.0
|
|
335
|
+
${langchainPkg}>=0.2.0
|
|
336
|
+
python-dotenv>=1.0.0
|
|
337
|
+
agentvoy-guard>=0.1.0
|
|
338
|
+
`;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function generateEnvExample(config: ScaffoldConfig): string {
|
|
342
|
+
const envKey = getApiKeyEnv(config);
|
|
343
|
+
return `${envKey}=your-api-key-here\n`;
|
|
344
|
+
}
|
package/src/adapters/openai.ts
CHANGED
|
@@ -123,14 +123,25 @@ Follow these guidelines:
|
|
|
123
123
|
|
|
124
124
|
|
|
125
125
|
async def run_agent(prompt: str) -> str:
|
|
126
|
-
"""Run the agent with the given prompt."""
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
126
|
+
"""Run the agent with the given prompt, enforcing agent.guard.yml at runtime."""
|
|
127
|
+
from agentvoy_guard import Guard
|
|
128
|
+
guard = Guard.from_config()
|
|
129
|
+
|
|
130
|
+
with guard.session() as session:
|
|
131
|
+
session.check_input(prompt)
|
|
132
|
+
|
|
133
|
+
agent = create_agent()
|
|
134
|
+
result = await Runner.run(
|
|
135
|
+
agent,
|
|
136
|
+
prompt,
|
|
137
|
+
max_turns=${maxTurns},
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
final = result.final_output or ""
|
|
141
|
+
session.check_output(final)
|
|
142
|
+
|
|
143
|
+
print(f"[guard] {guard.last_summary}")
|
|
144
|
+
return final
|
|
134
145
|
`;
|
|
135
146
|
}
|
|
136
147
|
|
|
@@ -210,5 +221,6 @@ if __name__ == "__main__":
|
|
|
210
221
|
function generateRequirements(): string {
|
|
211
222
|
return `openai-agents>=0.1.0
|
|
212
223
|
python-dotenv>=1.0.0
|
|
224
|
+
agentvoy-guard>=0.1.0
|
|
213
225
|
`;
|
|
214
226
|
}
|