CrewAI-yhteensopiva projektioutput: agents.yaml, tasks.yaml, crew.py, prompts/
Pipeline kerää promptLog-listan jokaisesta agenttikutsusta (system prompt + syöte + tulos) ja generoi lopuksi CrewAI-rakenteen files-objektiin. Korjattu myös template.order.length-kaatuminen vapaassa tilassa.
This commit is contained in:
@@ -793,8 +793,92 @@ OUTPUT FORMAT:
|
|||||||
termLog(` <span style="color:#8b949e;font-size:12px">${esc(explanation)}</span>`);
|
termLog(` <span style="color:#8b949e;font-size:12px">${esc(explanation)}</span>`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- CrewAI-yhteensopiva output ---
|
||||||
|
function generateCrewAIFiles(promptLog, task) {
|
||||||
|
const crewFiles = {};
|
||||||
|
|
||||||
|
// Kerätään uniikit agentit
|
||||||
|
const agentMap = {};
|
||||||
|
for (const entry of promptLog) {
|
||||||
|
if (!agentMap[entry.agentKey]) {
|
||||||
|
const firstSentence = (entry.systemPrompt || '').split(/\.\s/)[0].trim();
|
||||||
|
agentMap[entry.agentKey] = {
|
||||||
|
name: entry.agentName,
|
||||||
|
model: entry.model,
|
||||||
|
systemPrompt: entry.systemPrompt || '',
|
||||||
|
goal: firstSentence.replace(/^You are (a |an )?/i, '').trim() || entry.label,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// agents.yaml
|
||||||
|
let ay = '# Agents — Kipinä Agentic Studio → CrewAI\n';
|
||||||
|
for (const [key, a] of Object.entries(agentMap)) {
|
||||||
|
ay += `\n${key}:\n`;
|
||||||
|
ay += ` role: >-\n ${a.name}\n`;
|
||||||
|
ay += ` goal: >-\n ${a.goal}\n`;
|
||||||
|
ay += ` backstory: |\n`;
|
||||||
|
for (const line of a.systemPrompt.split('\n')) {
|
||||||
|
ay += ` ${line}\n`;
|
||||||
|
}
|
||||||
|
ay += ` llm: ${a.model}\n`;
|
||||||
|
}
|
||||||
|
crewFiles['agents.yaml'] = ay;
|
||||||
|
|
||||||
|
// tasks.yaml
|
||||||
|
let ty = '# Tasks — Kipinä Agentic Studio → CrewAI\n';
|
||||||
|
for (let i = 0; i < promptLog.length; i++) {
|
||||||
|
const e = promptLog[i];
|
||||||
|
const name = `step_${i}_${e.label.replace(/[^a-z0-9]/gi, '_').toLowerCase()}`;
|
||||||
|
ty += `\n${name}:\n`;
|
||||||
|
ty += ` description: |\n`;
|
||||||
|
const descLines = e.userPrompt.split('\n').slice(0, 20);
|
||||||
|
for (const line of descLines) {
|
||||||
|
ty += ` ${line}\n`;
|
||||||
|
}
|
||||||
|
if (e.userPrompt.split('\n').length > 20) ty += ` # ... (truncated)\n`;
|
||||||
|
ty += ` expected_output: >-\n ${e.label}\n`;
|
||||||
|
ty += ` agent: ${e.agentKey}\n`;
|
||||||
|
}
|
||||||
|
crewFiles['tasks.yaml'] = ty;
|
||||||
|
|
||||||
|
// crew.py
|
||||||
|
const agentKeys = Object.keys(agentMap);
|
||||||
|
const taskNames = promptLog.map((e, i) => `step_${i}_${e.label.replace(/[^a-z0-9]/gi, '_').toLowerCase()}`);
|
||||||
|
let py = `"""${task}\n\nCrewAI crew — generated by Kipinä Agentic Studio.\nRun: crewai run\n"""\n\n`;
|
||||||
|
py += `from crewai import Agent, Crew, Process, Task\n`;
|
||||||
|
py += `from crewai.project import CrewBase, agent, crew, task\n\n\n`;
|
||||||
|
py += `@CrewBase\nclass ProjectCrew:\n """${task}"""\n\n`;
|
||||||
|
py += ` agents_config = "agents.yaml"\n tasks_config = "tasks.yaml"\n\n`;
|
||||||
|
for (const key of agentKeys) {
|
||||||
|
py += ` @agent\n def ${key}(self) -> Agent:\n return Agent(config=self.agents_config["${key}"])\n\n`;
|
||||||
|
}
|
||||||
|
for (const name of taskNames) {
|
||||||
|
py += ` @task\n def ${name}(self) -> Task:\n return Task(config=self.tasks_config["${name}"])\n\n`;
|
||||||
|
}
|
||||||
|
py += ` @crew\n def crew(self) -> Crew:\n return Crew(\n`;
|
||||||
|
py += ` agents=self.agents,\n tasks=self.tasks,\n`;
|
||||||
|
py += ` process=Process.sequential,\n verbose=True,\n )\n`;
|
||||||
|
crewFiles['crew.py'] = py;
|
||||||
|
|
||||||
|
// prompts/*.md — jokaisen vaiheen system prompt + syöte + tulos
|
||||||
|
for (let i = 0; i < promptLog.length; i++) {
|
||||||
|
const e = promptLog[i];
|
||||||
|
let md = `# ${i} — ${e.agentName} (${e.agentKey}) — ${e.label}\n\n`;
|
||||||
|
md += `**Malli:** \`${e.model}\`\n\n`;
|
||||||
|
md += `## System Prompt\n\n\`\`\`\n${e.systemPrompt || '(ei system promptia)'}\n\`\`\`\n\n`;
|
||||||
|
md += `## Syöte\n\n\`\`\`\n${e.userPrompt}\n\`\`\`\n\n`;
|
||||||
|
md += `## Tulos\n\n\`\`\`\n${e.response || '(ei tulosta)'}\n\`\`\`\n`;
|
||||||
|
const safe = e.label.replace(/[^a-z0-9._-]/gi, '_').toLowerCase();
|
||||||
|
crewFiles[`prompts/${i}_${e.agentKey}_${safe}.md`] = md;
|
||||||
|
}
|
||||||
|
|
||||||
|
return crewFiles;
|
||||||
|
}
|
||||||
|
|
||||||
async function kpnProject(task) {
|
async function kpnProject(task) {
|
||||||
pipelineAbort = new AbortController();
|
pipelineAbort = new AbortController();
|
||||||
|
const promptLog = [];
|
||||||
const cli = agents.client || Object.values(agents)[0];
|
const cli = agents.client || Object.values(agents)[0];
|
||||||
const mgr = agents.manager || Object.values(agents)[1];
|
const mgr = agents.manager || Object.values(agents)[1];
|
||||||
const cdr = agents.coder || Object.values(agents)[2];
|
const cdr = agents.coder || Object.values(agents)[2];
|
||||||
@@ -806,6 +890,7 @@ OUTPUT FORMAT:
|
|||||||
explainStep('Vaatimusmäärittely', `${cli.name} muotoilee idean selkeiksi vaatimuksiksi: ominaisuudet, datamallit, rajapinnat.`);
|
explainStep('Vaatimusmäärittely', `${cli.name} muotoilee idean selkeiksi vaatimuksiksi: ominaisuudet, datamallit, rajapinnat.`);
|
||||||
const brief = await kpnRun(cli.model, `${task}`, false, cli);
|
const brief = await kpnRun(cli.model, `${task}`, false, cli);
|
||||||
if (!brief) { termLog(' ✗ Vaatimusmäärittely epäonnistui', '#f85149'); return; }
|
if (!brief) { termLog(' ✗ Vaatimusmäärittely epäonnistui', '#f85149'); return; }
|
||||||
|
promptLog.push({ step: 0, agentKey: 'client', agentName: cli.name, model: cli.model, label: 'vaatimusmäärittely', systemPrompt: cli.prompt || '', userPrompt: task, response: brief });
|
||||||
termLog(` <span style="color:#8b949e">Vaatimukset valmiit → Manageri</span>`);
|
termLog(` <span style="color:#8b949e">Vaatimukset valmiit → Manageri</span>`);
|
||||||
|
|
||||||
// Valitaan mallipohja automaattisesti briefin perusteella
|
// Valitaan mallipohja automaattisesti briefin perusteella
|
||||||
@@ -843,6 +928,7 @@ OUTPUT FORMAT:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
explainStep('Suunnitelma', `${fileOrder.length} tiedostoa: ${fileOrder.join(', ')}`);
|
explainStep('Suunnitelma', `${fileOrder.length} tiedostoa: ${fileOrder.join(', ')}`);
|
||||||
|
promptLog.push({ step: 1, agentKey: 'manager', agentName: mgr.name, model: mgr.model, label: 'tiedostorakenne', systemPrompt: mgr.prompt || '', userPrompt: planPrompt, response: plan });
|
||||||
}
|
}
|
||||||
|
|
||||||
const files = {};
|
const files = {};
|
||||||
@@ -898,10 +984,11 @@ OUTPUT FORMAT:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
files[fileName] = code;
|
files[fileName] = code;
|
||||||
|
promptLog.push({ step: promptLog.length, agentKey: fileAgentKey, agentName: fileAgent.name, model: fileAgent.model, label: fileName, systemPrompt: fileAgent.prompt || '', userPrompt: prompt, response: code });
|
||||||
}
|
}
|
||||||
|
|
||||||
const allCode = Object.entries(files).map(([n,c]) => `--- ${n} ---\n${c}`).join('\n\n');
|
const allCode = Object.entries(files).map(([n,c]) => `--- ${n} ---\n${c}`).join('\n\n');
|
||||||
let stepN = template.order.length + 1;
|
let stepN = fileOrder.length + 1;
|
||||||
|
|
||||||
// Review-korjausluuppi: max 2 kierrosta
|
// Review-korjausluuppi: max 2 kierrosta
|
||||||
const tst = agents.tester || Object.values(agents)[5];
|
const tst = agents.tester || Object.values(agents)[5];
|
||||||
@@ -918,6 +1005,7 @@ OUTPUT FORMAT:
|
|||||||
|
|
||||||
const reviewPrompt = (tst.prompt ? tst.prompt+'\n\n' : '') + `Review this project:\n\n${currentCode}`;
|
const reviewPrompt = (tst.prompt ? tst.prompt+'\n\n' : '') + `Review this project:\n\n${currentCode}`;
|
||||||
const review = await kpnRun(tst.model, reviewPrompt, false, tst);
|
const review = await kpnRun(tst.model, reviewPrompt, false, tst);
|
||||||
|
promptLog.push({ step: promptLog.length, agentKey: 'tester', agentName: tst.name, model: tst.model, label: 'review' + (round > 0 ? '_r'+(round+1) : ''), systemPrompt: tst.prompt || '', userPrompt: reviewPrompt, response: review || '' });
|
||||||
stepN++;
|
stepN++;
|
||||||
|
|
||||||
// LGTM → ei korjauksia tarvita
|
// LGTM → ei korjauksia tarvita
|
||||||
@@ -936,6 +1024,7 @@ OUTPUT FORMAT:
|
|||||||
|
|
||||||
// Parsitaan korjatut tiedostot takaisin files-objektiin
|
// Parsitaan korjatut tiedostot takaisin files-objektiin
|
||||||
if (fixedCode) {
|
if (fixedCode) {
|
||||||
|
promptLog.push({ step: promptLog.length, agentKey: 'coder', agentName: cdr.name, model: cdr.model, label: 'korjaus' + (round > 0 ? '_r'+(round+1) : ''), systemPrompt: cdr.prompt || '', userPrompt: fixPrompt, response: fixedCode });
|
||||||
const fixedParts = fixedCode.split(/^---\s*(\S+)\s*---$/m);
|
const fixedParts = fixedCode.split(/^---\s*(\S+)\s*---$/m);
|
||||||
for (let j = 1; j < fixedParts.length; j += 2) {
|
for (let j = 1; j < fixedParts.length; j += 2) {
|
||||||
const fname = fixedParts[j].trim();
|
const fname = fixedParts[j].trim();
|
||||||
@@ -959,7 +1048,10 @@ OUTPUT FORMAT:
|
|||||||
explainStep('Testit', `${qaAgent.name} kirjoittaa pytest-testit korjatulle koodille.`);
|
explainStep('Testit', `${qaAgent.name} kirjoittaa pytest-testit korjatulle koodille.`);
|
||||||
const qaPrompt = (qaAgent.prompt ? qaAgent.prompt+'\n\n' : '') + `Write pytest tests for this project:\n\n${updatedCode}\n\nWrite a complete test_main.py file with TestClient.`;
|
const qaPrompt = (qaAgent.prompt ? qaAgent.prompt+'\n\n' : '') + `Write pytest tests for this project:\n\n${updatedCode}\n\nWrite a complete test_main.py file with TestClient.`;
|
||||||
const tests = await kpnRun(qaAgent.model, qaPrompt, false, qaAgent);
|
const tests = await kpnRun(qaAgent.model, qaPrompt, false, qaAgent);
|
||||||
if (tests) files['test_main.py'] = tests;
|
if (tests) {
|
||||||
|
files['test_main.py'] = tests;
|
||||||
|
promptLog.push({ step: promptLog.length, agentKey: 'qa', agentName: qaAgent.name, model: qaAgent.model, label: 'test_main.py', systemPrompt: qaAgent.prompt || '', userPrompt: qaPrompt, response: tests });
|
||||||
|
}
|
||||||
stepN++;
|
stepN++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -979,7 +1071,10 @@ OUTPUT FORMAT:
|
|||||||
`- CMD: uv run uvicorn main:app --host 0.0.0.0 --port 8000\n` +
|
`- CMD: uv run uvicorn main:app --host 0.0.0.0 --port 8000\n` +
|
||||||
`\nWrite ONLY the Dockerfile, no explanations.`;
|
`\nWrite ONLY the Dockerfile, no explanations.`;
|
||||||
const dockerfile = await kpnRun(tst.model, dockerPrompt, false, tst);
|
const dockerfile = await kpnRun(tst.model, dockerPrompt, false, tst);
|
||||||
if (dockerfile) files['Dockerfile'] = dockerfile;
|
if (dockerfile) {
|
||||||
|
files['Dockerfile'] = dockerfile;
|
||||||
|
promptLog.push({ step: promptLog.length, agentKey: 'tester', agentName: tst.name, model: tst.model, label: 'Dockerfile', systemPrompt: tst.prompt || '', userPrompt: dockerPrompt, response: dockerfile });
|
||||||
|
}
|
||||||
stepN++;
|
stepN++;
|
||||||
|
|
||||||
// Tarkkailija: yhteenveto + raportti + arvosana
|
// Tarkkailija: yhteenveto + raportti + arvosana
|
||||||
@@ -1018,6 +1113,7 @@ OUTPUT FORMAT:
|
|||||||
files['README.md'] = readme;
|
files['README.md'] = readme;
|
||||||
// Tallennetaan raportti globaalisti jotta tarkkailija-klikkaus avaa sen
|
// Tallennetaan raportti globaalisti jotta tarkkailija-klikkaus avaa sen
|
||||||
window._lastReport = readme;
|
window._lastReport = readme;
|
||||||
|
promptLog.push({ step: promptLog.length, agentKey: 'observer', agentName: obs.name, model: obs.model, label: 'README.md', systemPrompt: obs.prompt || '', userPrompt: obsPrompt, response: readme });
|
||||||
// Parsitaan arvosana → tarkkailijan kehäväri
|
// Parsitaan arvosana → tarkkailijan kehäväri
|
||||||
const firstLine = readme.split('\n')[0].toUpperCase();
|
const firstLine = readme.split('\n')[0].toUpperCase();
|
||||||
let verdictColor = '#3fb950'; // oletus: vihreä
|
let verdictColor = '#3fb950'; // oletus: vihreä
|
||||||
@@ -1040,6 +1136,10 @@ OUTPUT FORMAT:
|
|||||||
|
|
||||||
termLog(`\n<span style="color:var(--purple);font-weight:bold">━━━ Valmis (${Object.keys(files).length} tiedostoa) ━━━</span>`);
|
termLog(`\n<span style="color:var(--purple);font-weight:bold">━━━ Valmis (${Object.keys(files).length} tiedostoa) ━━━</span>`);
|
||||||
explainStep('Tulos', `Projekti "${task}" generoitu ${Object.keys(files).length} tiedostoon. Klikkaa "Avaa editorissa" tutkiaksesi koodia ja README:tä.`);
|
explainStep('Tulos', `Projekti "${task}" generoitu ${Object.keys(files).length} tiedostoon. Klikkaa "Avaa editorissa" tutkiaksesi koodia ja README:tä.`);
|
||||||
|
// CrewAI-yhteensopiva output
|
||||||
|
const crewFiles = generateCrewAIFiles(promptLog, task);
|
||||||
|
Object.assign(files, crewFiles);
|
||||||
|
|
||||||
renderProjectCard(files, task);
|
renderProjectCard(files, task);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user