-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathagent.js
More file actions
87 lines (73 loc) · 2.94 KB
/
Copy pathagent.js
File metadata and controls
87 lines (73 loc) · 2.94 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
// agent.js
//
// Notice this loop is byte-for-byte the SAME SHAPE as the filesystem agent's.
// That is the lesson: once you have the loop, building a new agent is mostly
// writing new tools. Only the SYSTEM prompt and the tool set changed.
import "dotenv/config";
import Anthropic from "@anthropic-ai/sdk";
import { toolSchemas, runTool } from "./tools.js";
import { pool } from "./db.js";
const client = new Anthropic();
const MODEL = "claude-sonnet-4-6"; // or "claude-opus-4-8" for harder analysis
const MAX_TURNS = 15;
const SYSTEM = `You are a data analyst with read-only access to a PostgreSQL
database. Answer the user's question by exploring the schema, then writing and
running SQL.
Rules:
- Always call inspect_schema before writing a query against an unfamiliar table.
- Prefer parameterized queries ($1, $2) over interpolating values into SQL.
- If a query errors, read the message and correct your SQL.
- Show the SQL you ran, then explain the result in plain language.`;
async function runAgent(userQuestion) {
// The transcript array IS the agent's memory across tool rounds.
const messages = [{ role: "user", content: userQuestion }];
for (let turn = 0; turn < MAX_TURNS; turn++) {
// STEP 1: ask the model what to do, given everything so far + the tools.
const response = await client.messages.create({
model: MODEL,
max_tokens: 1500,
system: SYSTEM,
tools: toolSchemas,
messages,
});
// STEP 2: append the model's turn verbatim (tool_use blocks included).
messages.push({ role: "assistant", content: response.content });
// STEP 3: no tool requested => the model is done. Return its text.
if (response.stop_reason !== "tool_use") {
return response.content
.filter((b) => b.type === "text")
.map((b) => b.text)
.join("");
}
// STEP 4: run each requested tool, tagging results with their tool_use_id.
const toolResults = [];
for (const block of response.content) {
if (block.type !== "tool_use") continue;
console.log(` [tool] ${block.name} ${JSON.stringify(block.input)}`);
const { content, isError } = await runTool(block.name, block.input);
toolResults.push({
type: "tool_result",
tool_use_id: block.id,
content,
is_error: isError,
});
}
// STEP 5: hand results back as a user turn, then loop to STEP 1.
messages.push({ role: "user", content: toolResults });
}
return "Stopped: hit the maximum number of turns without a final answer.";
}
// --- CLI entry point -------------------------------------------------------
const question = process.argv.slice(2).join(" ");
if (!question) {
console.error('Usage: node agent.js "your question about the database"');
process.exit(1);
}
console.log(`\nQ: ${question}\n`);
try {
const answer = await runAgent(question);
console.log(`\nA: ${answer}\n`);
} finally {
// Close the pool so the process can exit cleanly.
await pool.end();
}