@fastino-ai/pioneer-cli 0.1.0 → 0.2.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 +161 -22
- package/bun.lock +82 -0
- package/cache/cache.db +0 -0
- package/cache/cache.db-shm +0 -0
- package/cache/cache.db-wal +0 -0
- package/fastino-ai-pioneer-cli-0.2.0.tgz +0 -0
- package/package.json +6 -3
- package/src/agent/Agent.ts +342 -0
- package/src/agent/BudgetManager.ts +167 -0
- package/src/agent/FileResolver.ts +321 -0
- package/src/agent/LLMClient.ts +435 -0
- package/src/agent/ToolRegistry.ts +97 -0
- package/src/agent/index.ts +15 -0
- package/src/agent/types.ts +84 -0
- package/src/chat/ChatApp.tsx +701 -0
- package/src/chat/index.ts +7 -0
- package/src/config.ts +185 -3
- package/src/evolution/EvalRunner.ts +301 -0
- package/src/evolution/EvolutionEngine.ts +319 -0
- package/src/evolution/FeedbackCollector.ts +197 -0
- package/src/evolution/ModelTrainer.ts +371 -0
- package/src/evolution/index.ts +18 -0
- package/src/evolution/types.ts +110 -0
- package/src/index.tsx +101 -2
- package/src/tools/bash.ts +184 -0
- package/src/tools/filesystem.ts +444 -0
- package/src/tools/index.ts +29 -0
- package/src/tools/modal.ts +269 -0
- package/src/tools/sandbox.ts +310 -0
- package/src/tools/training.ts +443 -0
- package/src/tools/wandb.ts +348 -0
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ModelTrainer - Fine-tune or train models based on feedback
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as fs from "fs";
|
|
6
|
+
import * as path from "path";
|
|
7
|
+
import * as os from "os";
|
|
8
|
+
import { spawn } from "child_process";
|
|
9
|
+
import type { TrainingData } from "./types.js";
|
|
10
|
+
|
|
11
|
+
export interface ModelTrainerConfig {
|
|
12
|
+
provider: "openai" | "anthropic" | "modal" | "local";
|
|
13
|
+
baseModel: string;
|
|
14
|
+
outputDir?: string;
|
|
15
|
+
openaiApiKey?: string;
|
|
16
|
+
anthropicApiKey?: string;
|
|
17
|
+
modalTokenId?: string;
|
|
18
|
+
modalTokenSecret?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface TrainingResult {
|
|
22
|
+
success: boolean;
|
|
23
|
+
modelId?: string;
|
|
24
|
+
modelPath?: string;
|
|
25
|
+
metrics?: {
|
|
26
|
+
loss?: number;
|
|
27
|
+
accuracy?: number;
|
|
28
|
+
epochs?: number;
|
|
29
|
+
};
|
|
30
|
+
error?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class ModelTrainer {
|
|
34
|
+
private config: ModelTrainerConfig;
|
|
35
|
+
private outputDir: string;
|
|
36
|
+
|
|
37
|
+
constructor(config: ModelTrainerConfig) {
|
|
38
|
+
this.config = config;
|
|
39
|
+
this.outputDir =
|
|
40
|
+
config.outputDir || path.join(os.homedir(), ".pioneer", "models");
|
|
41
|
+
this.ensureOutputDir();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private ensureOutputDir(): void {
|
|
45
|
+
if (!fs.existsSync(this.outputDir)) {
|
|
46
|
+
fs.mkdirSync(this.outputDir, { recursive: true });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async train(trainingData: TrainingData[]): Promise<TrainingResult> {
|
|
51
|
+
if (trainingData.length < 10) {
|
|
52
|
+
return {
|
|
53
|
+
success: false,
|
|
54
|
+
error: "Insufficient training data. Need at least 10 examples.",
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
switch (this.config.provider) {
|
|
59
|
+
case "openai":
|
|
60
|
+
return this.trainOpenAI(trainingData);
|
|
61
|
+
case "modal":
|
|
62
|
+
return this.trainModal(trainingData);
|
|
63
|
+
case "local":
|
|
64
|
+
return this.trainLocal(trainingData);
|
|
65
|
+
default:
|
|
66
|
+
return {
|
|
67
|
+
success: false,
|
|
68
|
+
error: `Unsupported provider: ${this.config.provider}`,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private async trainOpenAI(trainingData: TrainingData[]): Promise<TrainingResult> {
|
|
74
|
+
const apiKey = this.config.openaiApiKey || process.env.OPENAI_API_KEY;
|
|
75
|
+
if (!apiKey) {
|
|
76
|
+
return { success: false, error: "OpenAI API key not provided" };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Prepare training file
|
|
80
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openai-train-"));
|
|
81
|
+
const trainingFile = path.join(tempDir, "training.jsonl");
|
|
82
|
+
|
|
83
|
+
const formattedData = trainingData.map((d) => ({
|
|
84
|
+
messages: d.messages,
|
|
85
|
+
}));
|
|
86
|
+
|
|
87
|
+
fs.writeFileSync(
|
|
88
|
+
trainingFile,
|
|
89
|
+
formattedData.map((d) => JSON.stringify(d)).join("\n")
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
// Upload file
|
|
94
|
+
const uploadResponse = await fetch(
|
|
95
|
+
"https://api.openai.com/v1/files",
|
|
96
|
+
{
|
|
97
|
+
method: "POST",
|
|
98
|
+
headers: {
|
|
99
|
+
Authorization: `Bearer ${apiKey}`,
|
|
100
|
+
},
|
|
101
|
+
body: (() => {
|
|
102
|
+
const formData = new FormData();
|
|
103
|
+
formData.append("purpose", "fine-tune");
|
|
104
|
+
formData.append(
|
|
105
|
+
"file",
|
|
106
|
+
new Blob([fs.readFileSync(trainingFile)]),
|
|
107
|
+
"training.jsonl"
|
|
108
|
+
);
|
|
109
|
+
return formData;
|
|
110
|
+
})(),
|
|
111
|
+
}
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
if (!uploadResponse.ok) {
|
|
115
|
+
const error = await uploadResponse.text();
|
|
116
|
+
return { success: false, error: `File upload failed: ${error}` };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const uploadResult = await uploadResponse.json() as { id: string };
|
|
120
|
+
|
|
121
|
+
// Create fine-tuning job
|
|
122
|
+
const ftResponse = await fetch(
|
|
123
|
+
"https://api.openai.com/v1/fine_tuning/jobs",
|
|
124
|
+
{
|
|
125
|
+
method: "POST",
|
|
126
|
+
headers: {
|
|
127
|
+
Authorization: `Bearer ${apiKey}`,
|
|
128
|
+
"Content-Type": "application/json",
|
|
129
|
+
},
|
|
130
|
+
body: JSON.stringify({
|
|
131
|
+
training_file: uploadResult.id,
|
|
132
|
+
model: this.config.baseModel || "gpt-4o-mini-2024-07-18",
|
|
133
|
+
}),
|
|
134
|
+
}
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
if (!ftResponse.ok) {
|
|
138
|
+
const error = await ftResponse.text();
|
|
139
|
+
return { success: false, error: `Fine-tuning failed: ${error}` };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const ftResult = await ftResponse.json() as { id: string; fine_tuned_model?: string };
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
success: true,
|
|
146
|
+
modelId: ftResult.id,
|
|
147
|
+
modelPath: ftResult.fine_tuned_model,
|
|
148
|
+
};
|
|
149
|
+
} finally {
|
|
150
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private async trainModal(trainingData: TrainingData[]): Promise<TrainingResult> {
|
|
155
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "modal-train-"));
|
|
156
|
+
const dataFile = path.join(tempDir, "training_data.jsonl");
|
|
157
|
+
const appFile = path.join(tempDir, "train_app.py");
|
|
158
|
+
|
|
159
|
+
// Save training data
|
|
160
|
+
fs.writeFileSync(
|
|
161
|
+
dataFile,
|
|
162
|
+
trainingData.map((d) => JSON.stringify(d)).join("\n")
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
// Create Modal training app
|
|
166
|
+
const modalCode = `
|
|
167
|
+
import modal
|
|
168
|
+
import json
|
|
169
|
+
|
|
170
|
+
app = modal.App("pioneer-finetune")
|
|
171
|
+
|
|
172
|
+
volume = modal.Volume.from_name("pioneer-models", create_if_missing=True)
|
|
173
|
+
|
|
174
|
+
image = modal.Image.debian_slim(python_version="3.11").pip_install([
|
|
175
|
+
"torch",
|
|
176
|
+
"transformers",
|
|
177
|
+
"datasets",
|
|
178
|
+
"accelerate",
|
|
179
|
+
"peft",
|
|
180
|
+
"bitsandbytes",
|
|
181
|
+
])
|
|
182
|
+
|
|
183
|
+
@app.function(
|
|
184
|
+
image=image,
|
|
185
|
+
gpu="A10G",
|
|
186
|
+
timeout=7200,
|
|
187
|
+
volumes={"/models": volume},
|
|
188
|
+
)
|
|
189
|
+
def finetune(data_json: str, base_model: str, output_name: str):
|
|
190
|
+
import torch
|
|
191
|
+
from transformers import AutoModelForCausalLM, AutoTokenizer, TrainingArguments, Trainer
|
|
192
|
+
from datasets import Dataset
|
|
193
|
+
from peft import LoraConfig, get_peft_model
|
|
194
|
+
|
|
195
|
+
# Parse data
|
|
196
|
+
data = [json.loads(line) for line in data_json.strip().split("\\n")]
|
|
197
|
+
|
|
198
|
+
# Convert to dataset
|
|
199
|
+
texts = []
|
|
200
|
+
for item in data:
|
|
201
|
+
text = ""
|
|
202
|
+
for msg in item.get("messages", []):
|
|
203
|
+
text += f"{msg['role']}: {msg['content']}\\n"
|
|
204
|
+
texts.append(text)
|
|
205
|
+
|
|
206
|
+
dataset = Dataset.from_dict({"text": texts})
|
|
207
|
+
|
|
208
|
+
# Load model
|
|
209
|
+
model = AutoModelForCausalLM.from_pretrained(
|
|
210
|
+
base_model,
|
|
211
|
+
torch_dtype=torch.float16,
|
|
212
|
+
device_map="auto",
|
|
213
|
+
)
|
|
214
|
+
tokenizer = AutoTokenizer.from_pretrained(base_model)
|
|
215
|
+
tokenizer.pad_token = tokenizer.eos_token
|
|
216
|
+
|
|
217
|
+
# Apply LoRA
|
|
218
|
+
lora_config = LoraConfig(r=16, lora_alpha=32, target_modules=["q_proj", "v_proj"])
|
|
219
|
+
model = get_peft_model(model, lora_config)
|
|
220
|
+
|
|
221
|
+
# Tokenize
|
|
222
|
+
def tokenize(examples):
|
|
223
|
+
return tokenizer(examples["text"], truncation=True, max_length=512, padding="max_length")
|
|
224
|
+
|
|
225
|
+
tokenized = dataset.map(tokenize, batched=True)
|
|
226
|
+
|
|
227
|
+
# Train
|
|
228
|
+
training_args = TrainingArguments(
|
|
229
|
+
output_dir=f"/models/{output_name}",
|
|
230
|
+
num_train_epochs=3,
|
|
231
|
+
per_device_train_batch_size=4,
|
|
232
|
+
learning_rate=2e-5,
|
|
233
|
+
save_steps=500,
|
|
234
|
+
logging_steps=10,
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
trainer = Trainer(
|
|
238
|
+
model=model,
|
|
239
|
+
args=training_args,
|
|
240
|
+
train_dataset=tokenized,
|
|
241
|
+
)
|
|
242
|
+
trainer.train()
|
|
243
|
+
|
|
244
|
+
# Save
|
|
245
|
+
trainer.save_model(f"/models/{output_name}")
|
|
246
|
+
volume.commit()
|
|
247
|
+
|
|
248
|
+
return {"success": True, "model_path": f"/models/{output_name}"}
|
|
249
|
+
|
|
250
|
+
@app.local_entrypoint()
|
|
251
|
+
def main():
|
|
252
|
+
import sys
|
|
253
|
+
data_file = sys.argv[1] if len(sys.argv) > 1 else "training_data.jsonl"
|
|
254
|
+
base_model = "${this.config.baseModel || "meta-llama/Llama-2-7b-hf"}"
|
|
255
|
+
output_name = "pioneer-ft-" + str(int(__import__("time").time()))
|
|
256
|
+
|
|
257
|
+
with open(data_file) as f:
|
|
258
|
+
data_json = f.read()
|
|
259
|
+
|
|
260
|
+
result = finetune.remote(data_json, base_model, output_name)
|
|
261
|
+
print(json.dumps(result))
|
|
262
|
+
`;
|
|
263
|
+
|
|
264
|
+
fs.writeFileSync(appFile, modalCode);
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
return new Promise((resolve) => {
|
|
268
|
+
let stdout = "";
|
|
269
|
+
let stderr = "";
|
|
270
|
+
|
|
271
|
+
const env: NodeJS.ProcessEnv = { ...process.env };
|
|
272
|
+
if (this.config.modalTokenId) {
|
|
273
|
+
env.MODAL_TOKEN_ID = this.config.modalTokenId;
|
|
274
|
+
}
|
|
275
|
+
if (this.config.modalTokenSecret) {
|
|
276
|
+
env.MODAL_TOKEN_SECRET = this.config.modalTokenSecret;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const proc = spawn("modal", ["run", appFile, "--", dataFile], {
|
|
280
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
281
|
+
env,
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
proc.stdout.on("data", (data: Buffer) => {
|
|
285
|
+
stdout += data.toString();
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
proc.stderr.on("data", (data: Buffer) => {
|
|
289
|
+
stderr += data.toString();
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
proc.on("close", (code) => {
|
|
293
|
+
if (code === 0) {
|
|
294
|
+
try {
|
|
295
|
+
const result = JSON.parse(stdout.trim().split("\n").pop() || "{}");
|
|
296
|
+
resolve({
|
|
297
|
+
success: true,
|
|
298
|
+
modelPath: result.model_path,
|
|
299
|
+
});
|
|
300
|
+
} catch {
|
|
301
|
+
resolve({ success: true, modelPath: stdout });
|
|
302
|
+
}
|
|
303
|
+
} else {
|
|
304
|
+
resolve({
|
|
305
|
+
success: false,
|
|
306
|
+
error: stderr || `Exit code: ${code}`,
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
proc.on("error", (err) => {
|
|
312
|
+
resolve({ success: false, error: err.message });
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
} finally {
|
|
316
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
private async trainLocal(trainingData: TrainingData[]): Promise<TrainingResult> {
|
|
321
|
+
// For local training, we'll save the data and provide instructions
|
|
322
|
+
const outputPath = path.join(
|
|
323
|
+
this.outputDir,
|
|
324
|
+
`training_${Date.now()}.jsonl`
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
fs.writeFileSync(
|
|
328
|
+
outputPath,
|
|
329
|
+
trainingData.map((d) => JSON.stringify(d)).join("\n")
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
return {
|
|
333
|
+
success: true,
|
|
334
|
+
modelPath: outputPath,
|
|
335
|
+
metrics: {
|
|
336
|
+
epochs: 0,
|
|
337
|
+
},
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Prompt optimization (alternative to full fine-tuning)
|
|
342
|
+
async optimizePrompt(
|
|
343
|
+
currentPrompt: string,
|
|
344
|
+
feedback: { positive: string[]; negative: string[] }
|
|
345
|
+
): Promise<string> {
|
|
346
|
+
// Use the agent itself to improve its system prompt based on feedback
|
|
347
|
+
const optimizationPrompt = `You are a prompt engineer. Given the current system prompt and feedback, create an improved version.
|
|
348
|
+
|
|
349
|
+
Current Prompt:
|
|
350
|
+
${currentPrompt}
|
|
351
|
+
|
|
352
|
+
Positive Examples (what worked well):
|
|
353
|
+
${feedback.positive.slice(0, 5).join("\n---\n")}
|
|
354
|
+
|
|
355
|
+
Negative Examples (what didn't work):
|
|
356
|
+
${feedback.negative.slice(0, 5).join("\n---\n")}
|
|
357
|
+
|
|
358
|
+
Create an improved system prompt that:
|
|
359
|
+
1. Keeps what's working well
|
|
360
|
+
2. Addresses the issues in negative examples
|
|
361
|
+
3. Maintains the core capabilities
|
|
362
|
+
4. Is clear and actionable
|
|
363
|
+
|
|
364
|
+
Improved Prompt:`;
|
|
365
|
+
|
|
366
|
+
// For now, return the original prompt
|
|
367
|
+
// In a full implementation, this would call an LLM to generate the improved prompt
|
|
368
|
+
return currentPrompt;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Evolution module exports
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { EvolutionEngine } from "./EvolutionEngine.js";
|
|
6
|
+
export type { EvolutionEngineConfig, EvolutionEvents } from "./EvolutionEngine.js";
|
|
7
|
+
|
|
8
|
+
export { FeedbackCollector } from "./FeedbackCollector.js";
|
|
9
|
+
export type { FeedbackCollectorConfig } from "./FeedbackCollector.js";
|
|
10
|
+
|
|
11
|
+
export { EvalRunner, DEFAULT_EVAL_CASES } from "./EvalRunner.js";
|
|
12
|
+
export type { EvalRunnerConfig } from "./EvalRunner.js";
|
|
13
|
+
|
|
14
|
+
export { ModelTrainer } from "./ModelTrainer.js";
|
|
15
|
+
export type { ModelTrainerConfig, TrainingResult } from "./ModelTrainer.js";
|
|
16
|
+
|
|
17
|
+
export * from "./types.js";
|
|
18
|
+
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for the self-evolution system
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface Feedback {
|
|
6
|
+
id: string;
|
|
7
|
+
sessionId: string;
|
|
8
|
+
timestamp: Date;
|
|
9
|
+
userMessage: string;
|
|
10
|
+
agentResponse: string;
|
|
11
|
+
toolCalls: string[];
|
|
12
|
+
rating?: number; // 1-5
|
|
13
|
+
corrections?: string;
|
|
14
|
+
wasSuccessful: boolean;
|
|
15
|
+
metadata?: Record<string, unknown>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface EvalCase {
|
|
19
|
+
id: string;
|
|
20
|
+
name: string;
|
|
21
|
+
description: string;
|
|
22
|
+
input: string;
|
|
23
|
+
expectedOutput?: string;
|
|
24
|
+
expectedToolCalls?: string[];
|
|
25
|
+
successCriteria: EvalCriteria[];
|
|
26
|
+
weight?: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface EvalCriteria {
|
|
30
|
+
type: "contains" | "not_contains" | "tool_called" | "tool_not_called" | "regex" | "custom";
|
|
31
|
+
value: string;
|
|
32
|
+
description?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface EvalResult {
|
|
36
|
+
caseId: string;
|
|
37
|
+
passed: boolean;
|
|
38
|
+
score: number;
|
|
39
|
+
actualOutput: string;
|
|
40
|
+
toolsCalled: string[];
|
|
41
|
+
errors?: string[];
|
|
42
|
+
duration: number;
|
|
43
|
+
tokenUsage: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface EvalRunSummary {
|
|
47
|
+
runId: string;
|
|
48
|
+
timestamp: Date;
|
|
49
|
+
totalCases: number;
|
|
50
|
+
passedCases: number;
|
|
51
|
+
failedCases: number;
|
|
52
|
+
averageScore: number;
|
|
53
|
+
totalTokens: number;
|
|
54
|
+
totalDuration: number;
|
|
55
|
+
results: EvalResult[];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface EvolutionConfig {
|
|
59
|
+
evalCases: EvalCase[];
|
|
60
|
+
targetScore: number;
|
|
61
|
+
maxIterations: number;
|
|
62
|
+
budgetPerIteration: {
|
|
63
|
+
maxTokens?: number;
|
|
64
|
+
maxCost?: number;
|
|
65
|
+
maxTime?: number;
|
|
66
|
+
};
|
|
67
|
+
feedbackWindow: number; // Number of recent feedback items to consider
|
|
68
|
+
trainingConfig?: {
|
|
69
|
+
provider: "openai" | "anthropic" | "modal";
|
|
70
|
+
baseModel: string;
|
|
71
|
+
fineTuneMethod: "full" | "lora" | "prompt";
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface EvolutionState {
|
|
76
|
+
iteration: number;
|
|
77
|
+
currentScore: number;
|
|
78
|
+
bestScore: number;
|
|
79
|
+
bestPrompt: string;
|
|
80
|
+
history: EvolutionHistory[];
|
|
81
|
+
totalTokensUsed: number;
|
|
82
|
+
totalCostUsed: number;
|
|
83
|
+
totalTimeUsed: number;
|
|
84
|
+
startTime: Date;
|
|
85
|
+
endTime?: Date;
|
|
86
|
+
status: "running" | "completed" | "failed" | "budget_exhausted";
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface EvolutionHistory {
|
|
90
|
+
iteration: number;
|
|
91
|
+
prompt: string;
|
|
92
|
+
evalScore: number;
|
|
93
|
+
changes: string;
|
|
94
|
+
timestamp: Date;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface TrainingData {
|
|
98
|
+
id: string;
|
|
99
|
+
messages: Array<{
|
|
100
|
+
role: "user" | "assistant" | "system";
|
|
101
|
+
content: string;
|
|
102
|
+
}>;
|
|
103
|
+
toolCalls?: Array<{
|
|
104
|
+
name: string;
|
|
105
|
+
arguments: Record<string, unknown>;
|
|
106
|
+
result: string;
|
|
107
|
+
}>;
|
|
108
|
+
metadata?: Record<string, unknown>;
|
|
109
|
+
}
|
|
110
|
+
|
package/src/index.tsx
CHANGED
|
@@ -14,8 +14,15 @@ import {
|
|
|
14
14
|
getBaseUrl,
|
|
15
15
|
saveConfig,
|
|
16
16
|
clearApiKey,
|
|
17
|
+
getAgentConfig,
|
|
18
|
+
getBudgetConfig,
|
|
19
|
+
getSandboxConfig,
|
|
20
|
+
getMLConfig,
|
|
21
|
+
getSystemPrompt,
|
|
17
22
|
} from "./config.js";
|
|
18
23
|
import * as api from "./api.js";
|
|
24
|
+
import { ChatApp } from "./chat/ChatApp.js";
|
|
25
|
+
import type { AgentConfig } from "./agent/types.js";
|
|
19
26
|
|
|
20
27
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
21
28
|
// ASCII Banner
|
|
@@ -252,6 +259,17 @@ const Help: React.FC = () => {
|
|
|
252
259
|
<Text bold>Usage:</Text>
|
|
253
260
|
<Text> pioneer {"<command>"} {"[options]"}</Text>
|
|
254
261
|
<Text> </Text>
|
|
262
|
+
<Text bold>Chat Commands:</Text>
|
|
263
|
+
<Text> chat Start interactive chat agent</Text>
|
|
264
|
+
<Text> --provider {"<name>"} LLM provider (anthropic, openai)</Text>
|
|
265
|
+
<Text> --model {"<model>"} Model to use</Text>
|
|
266
|
+
<Text> --message {"<msg>"} Initial message to process</Text>
|
|
267
|
+
<Text> --max-tokens {"<n>"} Max tokens (default: 500000, 0=unlimited)</Text>
|
|
268
|
+
<Text> --max-cost {"<n>"} Max cost in USD (default: 5.0, 0=unlimited)</Text>
|
|
269
|
+
<Text> --max-time {"<n>"} Max time in seconds (default: 7200, 0=unlimited)</Text>
|
|
270
|
+
<Text> --max-tools {"<n>"} Max tool calls per turn (default: 50, 0=unlimited)</Text>
|
|
271
|
+
<Text> --no-limit Remove all limits</Text>
|
|
272
|
+
<Text> </Text>
|
|
255
273
|
<Text bold>Auth Commands:</Text>
|
|
256
274
|
<Text> auth login Login with API key</Text>
|
|
257
275
|
<Text> auth logout Clear stored API key</Text>
|
|
@@ -284,12 +302,88 @@ const Help: React.FC = () => {
|
|
|
284
302
|
<Text> --help Show this help</Text>
|
|
285
303
|
<Text> </Text>
|
|
286
304
|
<Text dimColor>Environment:</Text>
|
|
287
|
-
<Text dimColor> PIONEER_API_URL
|
|
288
|
-
<Text dimColor> PIONEER_API_KEY
|
|
305
|
+
<Text dimColor> PIONEER_API_URL API base URL (default: http://localhost:5001)</Text>
|
|
306
|
+
<Text dimColor> PIONEER_API_KEY API key (overrides saved key)</Text>
|
|
307
|
+
<Text dimColor> ANTHROPIC_API_KEY Anthropic API key for chat agent</Text>
|
|
308
|
+
<Text dimColor> OPENAI_API_KEY OpenAI API key for chat agent</Text>
|
|
289
309
|
</Box>
|
|
290
310
|
);
|
|
291
311
|
};
|
|
292
312
|
|
|
313
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
314
|
+
// Chat Wrapper Component
|
|
315
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
316
|
+
|
|
317
|
+
interface ChatWrapperProps {
|
|
318
|
+
flags: Record<string, string>;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const ChatWrapper: React.FC<ChatWrapperProps> = ({ flags }) => {
|
|
322
|
+
const { exit } = useApp();
|
|
323
|
+
|
|
324
|
+
// Build agent config from flags and stored config
|
|
325
|
+
const storedConfig = getAgentConfig();
|
|
326
|
+
const budgetConfig = getBudgetConfig();
|
|
327
|
+
const systemPrompt = getSystemPrompt();
|
|
328
|
+
|
|
329
|
+
// Apply budget overrides from flags
|
|
330
|
+
const budget = { ...budgetConfig };
|
|
331
|
+
let maxToolCalls = 50; // Default
|
|
332
|
+
|
|
333
|
+
if (flags["no-limit"] === "true") {
|
|
334
|
+
// Remove all limits
|
|
335
|
+
budget.maxTokens = undefined;
|
|
336
|
+
budget.maxCost = undefined;
|
|
337
|
+
budget.maxTime = undefined;
|
|
338
|
+
budget.maxIterations = undefined;
|
|
339
|
+
maxToolCalls = 1000; // Effectively unlimited
|
|
340
|
+
} else {
|
|
341
|
+
// Apply individual overrides (0 = unlimited)
|
|
342
|
+
if (flags["max-tokens"]) {
|
|
343
|
+
const val = parseInt(flags["max-tokens"], 10);
|
|
344
|
+
budget.maxTokens = val === 0 ? undefined : val;
|
|
345
|
+
}
|
|
346
|
+
if (flags["max-cost"]) {
|
|
347
|
+
const val = parseFloat(flags["max-cost"]);
|
|
348
|
+
budget.maxCost = val === 0 ? undefined : val;
|
|
349
|
+
}
|
|
350
|
+
if (flags["max-time"]) {
|
|
351
|
+
const val = parseInt(flags["max-time"], 10);
|
|
352
|
+
budget.maxTime = val === 0 ? undefined : val;
|
|
353
|
+
}
|
|
354
|
+
if (flags["max-tools"]) {
|
|
355
|
+
const val = parseInt(flags["max-tools"], 10);
|
|
356
|
+
maxToolCalls = val === 0 ? 1000 : val;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const agentConfig: AgentConfig = {
|
|
361
|
+
provider: (flags.provider as "anthropic" | "openai") || storedConfig.provider,
|
|
362
|
+
model: flags.model || storedConfig.model,
|
|
363
|
+
apiKey: storedConfig.apiKey,
|
|
364
|
+
baseUrl: storedConfig.baseUrl,
|
|
365
|
+
budget,
|
|
366
|
+
systemPrompt,
|
|
367
|
+
maxToolCalls,
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
// Validate we have an API key
|
|
371
|
+
if (!agentConfig.apiKey) {
|
|
372
|
+
return (
|
|
373
|
+
<Box flexDirection="column">
|
|
374
|
+
<ErrorMessage error="No API key configured. Set ANTHROPIC_API_KEY or OPENAI_API_KEY environment variable." />
|
|
375
|
+
<Text dimColor>
|
|
376
|
+
Or run: export ANTHROPIC_API_KEY="your-key"
|
|
377
|
+
</Text>
|
|
378
|
+
</Box>
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const initialMessage = flags.message;
|
|
383
|
+
|
|
384
|
+
return <ChatApp config={agentConfig} initialMessage={initialMessage} />;
|
|
385
|
+
};
|
|
386
|
+
|
|
293
387
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
294
388
|
// Main Router
|
|
295
389
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -307,6 +401,11 @@ const App: React.FC<AppProps> = ({ command, flags }) => {
|
|
|
307
401
|
return <Help />;
|
|
308
402
|
}
|
|
309
403
|
|
|
404
|
+
// Chat command - Interactive agent
|
|
405
|
+
if (group === "chat") {
|
|
406
|
+
return <ChatWrapper flags={flags} />;
|
|
407
|
+
}
|
|
408
|
+
|
|
310
409
|
// Auth commands
|
|
311
410
|
if (group === "auth") {
|
|
312
411
|
if (action === "login") return <AuthLogin />;
|