mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
## Summary Resolves [REQ-78](https://linear.app/autogpt/issue/REQ-78): The `placeholder_values` field on `AgentDropdownInputBlock` is misleadingly named. In every major UI framework "placeholder" means non-binding hint text that disappears on focus, but this field actually creates a dropdown selector that restricts the user to only those values. ## Changes ### Core rename (`autogpt_platform/backend/backend/blocks/io.py`) - Renamed `placeholder_values` → `options` on `AgentDropdownInputBlock.Input` - Added clear field description: *"If provided, renders the input as a dropdown selector restricted to these values. Leave empty for free-text input."* - Updated class docstring to describe actual behavior - Overrode `model_construct()` to remap legacy `placeholder_values` → `options` for **backward compatibility** with existing persisted agent JSON ### Tests (`autogpt_platform/backend/backend/blocks/test/test_block.py`) - Updated existing tests to use canonical `options` field name - Added 2 new backward-compat tests verifying legacy `placeholder_values` still works through both `model_construct()` and `Graph._generate_schema()` paths ### Documentation - Updated `autogpt_platform/backend/backend/copilot/sdk/agent_generation_guide.md` — changed field name in CoPilot SDK guide - Updated `docs/integrations/block-integrations/basic.md` — changed field name and description in public docs ### Load tests (`autogpt_platform/backend/load-tests/tests/api/graph-execution-test.js`) - Removed spurious `placeholder_values: {}` from AgentInputBlock node (this field never existed on AgentInputBlock) - Fixed execution input to use `value` instead of `placeholder_values` ## Backward Compatibility Existing agents with `placeholder_values` in their persisted `input_default` JSON will continue to work — the `model_construct()` override transparently remaps the old key to `options`. No database migration needed since the field is stored inside a JSON blob, not as a dedicated column. ## Testing - All existing tests updated and passing - 2 new backward-compat tests added - No frontend changes needed (frontend reads `enum` from generated JSON Schema, not the field name directly) --------- Co-authored-by: Zamil Majdy <zamil.majdy@agpt.co>
245 lines
7.4 KiB
JavaScript
245 lines
7.4 KiB
JavaScript
// Dedicated graph execution load testing
|
|
import http from "k6/http";
|
|
import { check, sleep, group } from "k6";
|
|
import { Rate, Trend, Counter } from "k6/metrics";
|
|
import { getEnvironmentConfig } from "../../configs/environment.js";
|
|
import { getPreAuthenticatedHeaders } from "../../configs/pre-authenticated-tokens.js";
|
|
// Test data generation functions
|
|
function generateTestGraph(name = null) {
|
|
const graphName =
|
|
name || `Load Test Graph ${Math.random().toString(36).substr(2, 9)}`;
|
|
return {
|
|
name: graphName,
|
|
description: "Generated graph for load testing purposes",
|
|
graph: {
|
|
name: graphName,
|
|
description: "Load testing graph",
|
|
nodes: [
|
|
{
|
|
id: "input_node",
|
|
name: "Agent Input",
|
|
block_id: "c0a8e994-ebf1-4a9c-a4d8-89d09c86741b",
|
|
input_default: {
|
|
name: "Load Test Input",
|
|
description: "Test input for load testing",
|
|
},
|
|
input_nodes: [],
|
|
output_nodes: ["output_node"],
|
|
metadata: { position: { x: 100, y: 100 } },
|
|
},
|
|
{
|
|
id: "output_node",
|
|
name: "Agent Output",
|
|
block_id: "363ae599-353e-4804-937e-b2ee3cef3da4",
|
|
input_default: {
|
|
name: "Load Test Output",
|
|
description: "Test output for load testing",
|
|
value: "Test output value",
|
|
},
|
|
input_nodes: ["input_node"],
|
|
output_nodes: [],
|
|
metadata: { position: { x: 300, y: 100 } },
|
|
},
|
|
],
|
|
links: [
|
|
{
|
|
source_id: "input_node",
|
|
sink_id: "output_node",
|
|
source_name: "result",
|
|
sink_name: "value",
|
|
},
|
|
],
|
|
},
|
|
};
|
|
}
|
|
|
|
function generateExecutionInputs() {
|
|
return {
|
|
"Load Test Input": {
|
|
name: "Load Test Input",
|
|
description: "Test input for load testing",
|
|
value: `Test execution at ${new Date().toISOString()}`,
|
|
},
|
|
};
|
|
}
|
|
|
|
const config = getEnvironmentConfig();
|
|
|
|
// Custom metrics for graph execution testing
|
|
const graphCreations = new Counter("graph_creations_total");
|
|
const graphExecutions = new Counter("graph_executions_total");
|
|
const graphExecutionTime = new Trend("graph_execution_duration");
|
|
const graphCreationTime = new Trend("graph_creation_duration");
|
|
const executionErrors = new Rate("execution_errors");
|
|
|
|
// Configurable options for easy load adjustment
|
|
export const options = {
|
|
stages: [
|
|
{ duration: __ENV.RAMP_UP || "1m", target: parseInt(__ENV.VUS) || 5 },
|
|
{ duration: __ENV.DURATION || "5m", target: parseInt(__ENV.VUS) || 5 },
|
|
{ duration: __ENV.RAMP_DOWN || "1m", target: 0 },
|
|
],
|
|
// Thresholds disabled to prevent test abortion - collect all performance data
|
|
// thresholds: {
|
|
// checks: ['rate>0.60'],
|
|
// http_req_duration: ['p(95)<45000', 'p(99)<60000'],
|
|
// http_req_failed: ['rate<0.4'],
|
|
// graph_execution_duration: ['p(95)<45000'],
|
|
// graph_creation_duration: ['p(95)<30000'],
|
|
// },
|
|
cloud: {
|
|
projectID: __ENV.K6_CLOUD_PROJECT_ID,
|
|
name: "AutoGPT Platform - Graph Creation & Execution Test",
|
|
},
|
|
// Timeout configurations to prevent early termination
|
|
setupTimeout: "60s",
|
|
teardownTimeout: "60s",
|
|
noConnectionReuse: false,
|
|
userAgent: "k6-load-test/1.0",
|
|
};
|
|
|
|
export function setup() {
|
|
console.log("🎯 Setting up graph execution load test...");
|
|
console.log(
|
|
`Configuration: VUs=${parseInt(__ENV.VUS) || 5}, Duration=${__ENV.DURATION || "2m"}`,
|
|
);
|
|
return {
|
|
timestamp: Date.now(),
|
|
};
|
|
}
|
|
|
|
export default function (data) {
|
|
// Get load multiplier - how many concurrent operations each VU should perform
|
|
const requestsPerVU = parseInt(__ENV.REQUESTS_PER_VU) || 1;
|
|
|
|
// Get pre-authenticated headers (no auth API calls during test)
|
|
const headers = getPreAuthenticatedHeaders(__VU);
|
|
|
|
// Handle missing token gracefully
|
|
if (!headers || !headers.Authorization) {
|
|
console.log(
|
|
`⚠️ VU ${__VU} has no valid pre-authenticated token - skipping graph execution`,
|
|
);
|
|
check(null, {
|
|
"Graph Execution: Failed gracefully without crashing VU": () => true,
|
|
});
|
|
return; // Exit iteration gracefully without crashing
|
|
}
|
|
|
|
console.log(
|
|
`🚀 VU ${__VU} performing ${requestsPerVU} concurrent graph operations...`,
|
|
);
|
|
|
|
// Create requests for concurrent execution
|
|
const graphRequests = [];
|
|
|
|
for (let i = 0; i < requestsPerVU; i++) {
|
|
// Generate graph data
|
|
const graphData = generateTestGraph();
|
|
|
|
// Add graph creation request
|
|
graphRequests.push({
|
|
method: "POST",
|
|
url: `${config.API_BASE_URL}/api/graphs`,
|
|
body: JSON.stringify(graphData),
|
|
params: { headers },
|
|
});
|
|
}
|
|
|
|
// Execute all graph creations concurrently
|
|
console.log(`📊 Creating ${requestsPerVU} graphs concurrently...`);
|
|
const responses = http.batch(graphRequests);
|
|
|
|
// Process results
|
|
let successCount = 0;
|
|
const createdGraphs = [];
|
|
|
|
for (let i = 0; i < responses.length; i++) {
|
|
const response = responses[i];
|
|
|
|
const success = check(response, {
|
|
[`Graph ${i + 1} created successfully`]: (r) => r.status === 200,
|
|
});
|
|
|
|
if (success && response.status === 200) {
|
|
successCount++;
|
|
try {
|
|
const graph = JSON.parse(response.body);
|
|
createdGraphs.push(graph);
|
|
graphCreations.add(1);
|
|
} catch (e) {
|
|
console.error(`Error parsing graph ${i + 1} response:`, e);
|
|
}
|
|
} else {
|
|
console.log(`❌ Graph ${i + 1} creation failed: ${response.status}`);
|
|
}
|
|
}
|
|
|
|
console.log(
|
|
`✅ VU ${__VU} created ${successCount}/${requestsPerVU} graphs concurrently`,
|
|
);
|
|
|
|
// Execute a subset of created graphs (to avoid overloading execution)
|
|
const graphsToExecute = createdGraphs.slice(
|
|
0,
|
|
Math.min(5, createdGraphs.length),
|
|
);
|
|
|
|
if (graphsToExecute.length > 0) {
|
|
console.log(`⚡ Executing ${graphsToExecute.length} graphs...`);
|
|
|
|
const executionRequests = [];
|
|
|
|
for (const graph of graphsToExecute) {
|
|
const executionInputs = generateExecutionInputs();
|
|
|
|
executionRequests.push({
|
|
method: "POST",
|
|
url: `${config.API_BASE_URL}/api/graphs/${graph.id}/execute/${graph.version}`,
|
|
body: JSON.stringify({
|
|
inputs: executionInputs,
|
|
credentials_inputs: {},
|
|
}),
|
|
params: { headers },
|
|
});
|
|
}
|
|
|
|
// Execute graphs concurrently
|
|
const executionResponses = http.batch(executionRequests);
|
|
|
|
let executionSuccessCount = 0;
|
|
for (let i = 0; i < executionResponses.length; i++) {
|
|
const response = executionResponses[i];
|
|
|
|
const success = check(response, {
|
|
[`Graph ${i + 1} execution initiated`]: (r) =>
|
|
r.status === 200 || r.status === 402,
|
|
});
|
|
|
|
if (success) {
|
|
executionSuccessCount++;
|
|
graphExecutions.add(1);
|
|
}
|
|
}
|
|
|
|
console.log(
|
|
`✅ VU ${__VU} executed ${executionSuccessCount}/${graphsToExecute.length} graphs`,
|
|
);
|
|
}
|
|
|
|
// Think time between iterations
|
|
sleep(Math.random() * 2 + 1); // 1-3 seconds
|
|
}
|
|
|
|
// Legacy functions removed - replaced by concurrent execution in main function
|
|
// These functions are no longer used since implementing http.batch() for true concurrency
|
|
|
|
export function teardown(data) {
|
|
console.log("🧹 Cleaning up graph execution load test...");
|
|
console.log(`Total graph creations: ${graphCreations.value || 0}`);
|
|
console.log(`Total graph executions: ${graphExecutions.value || 0}`);
|
|
|
|
const testDuration = Date.now() - data.timestamp;
|
|
console.log(`Test completed in ${testDuration}ms`);
|
|
}
|