mirror of https://github.com/kortix-ai/suna.git
response processor orga refactor
This commit is contained in:
parent
eea33e967c
commit
cf13f3af31
|
@ -3,34 +3,493 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Flappy Bird Test</title>
|
<title>Minecraft Clone</title>
|
||||||
<style>
|
<style>
|
||||||
canvas {
|
/* Previous CSS styles remain the same */
|
||||||
border: 1px solid black;
|
body { margin: 0; overflow: hidden; }
|
||||||
|
canvas { display: block; image-rendering: pixelated; }
|
||||||
|
.crosshair {
|
||||||
|
position: fixed;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
color: white;
|
||||||
|
font-size: 24px;
|
||||||
|
pointer-events: none;
|
||||||
|
text-shadow: 2px 2px #000;
|
||||||
|
}
|
||||||
|
.inventory {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
display: flex;
|
||||||
|
gap: 5px;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
.inventory-slot {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
border: 2px solid #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: white;
|
||||||
|
image-rendering: pixelated;
|
||||||
|
}
|
||||||
|
.inventory-slot.selected {
|
||||||
|
background: rgba(255, 255, 255, 0.4);
|
||||||
|
border-color: #ffff00;
|
||||||
|
}
|
||||||
|
.hud {
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
left: 20px;
|
||||||
|
color: white;
|
||||||
|
font-family: 'Minecraft', monospace;
|
||||||
|
text-shadow: 2px 2px #000;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<canvas id="gameCanvas" width="320" height="480"></canvas>
|
<div class="crosshair">+</div>
|
||||||
<script>
|
<div class="hud">
|
||||||
const canvas = document.getElementById('gameCanvas');
|
<div id="coordinates"></div>
|
||||||
const ctx = canvas.getContext('2d');
|
<div id="selected-block"></div>
|
||||||
|
</div>
|
||||||
|
<div class="inventory" id="inventory"></div>
|
||||||
|
|
||||||
// Test bird
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
|
||||||
const bird = {
|
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/PointerLockControls.js"></script>
|
||||||
x: 50,
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/simplex-noise/2.4.0/simplex-noise.min.js"></script>
|
||||||
y: 200,
|
<script>
|
||||||
size: 20
|
// Initialize Three.js with Minecraft-style settings
|
||||||
|
const scene = new THREE.Scene();
|
||||||
|
scene.fog = new THREE.Fog(0x8CB4DB, 30, 100); // Minecraft-style fog distance
|
||||||
|
scene.background = new THREE.Color(0x8CB4DB); // Minecraft sky blue
|
||||||
|
|
||||||
|
const camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 1000);
|
||||||
|
const renderer = new THREE.WebGLRenderer({
|
||||||
|
antialias: false,
|
||||||
|
logarithmicDepthBuffer: true,
|
||||||
|
physicallyCorrectLights: true
|
||||||
|
});
|
||||||
|
const clock = new THREE.Clock(); // Disable antialiasing for pixelated look
|
||||||
|
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||||
|
renderer.setPixelRatio(1); // Force pixel ratio to 1 for sharp pixels
|
||||||
|
renderer.shadowMap.enabled = true;
|
||||||
|
document.body.appendChild(renderer.domElement);
|
||||||
|
|
||||||
|
// Minecraft-style textures
|
||||||
|
const textureLoader = new THREE.TextureLoader();
|
||||||
|
const texturePaths = {
|
||||||
|
grass_top: 'https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/textures/minecraft/grass_top.png',
|
||||||
|
grass_side: 'https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/textures/minecraft/grass_side.png',
|
||||||
|
dirt: 'https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/textures/minecraft/dirt.png',
|
||||||
|
stone: 'https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/textures/minecraft/stone.png',
|
||||||
|
wood: 'https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/textures/minecraft/wood.png',
|
||||||
|
wood_top: 'https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/textures/minecraft/wood.png',
|
||||||
|
leaves: 'https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/textures/minecraft/leaves.png',
|
||||||
|
bedrock: 'https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/textures/minecraft/stone.png'
|
||||||
};
|
};
|
||||||
|
|
||||||
// Draw test bird
|
// Configure texture settings for pixelated look
|
||||||
function draw() {
|
function loadMinecraftTexture(path) {
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
const texture = textureLoader.load(path);
|
||||||
ctx.fillStyle = 'red';
|
texture.magFilter = THREE.NearestFilter;
|
||||||
ctx.fillRect(bird.x, bird.y, bird.size, bird.size);
|
texture.minFilter = THREE.NearestFilter;
|
||||||
|
texture.wrapS = THREE.RepeatWrapping;
|
||||||
|
texture.wrapT = THREE.RepeatWrapping;
|
||||||
|
return texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
draw();
|
// Load all textures with Minecraft settings
|
||||||
|
const blockTextures = {};
|
||||||
|
for (const [key, path] of Object.entries(texturePaths)) {
|
||||||
|
blockTextures[key] = loadMinecraftTexture(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create materials with Minecraft-style shading
|
||||||
|
const materialSettings = {
|
||||||
|
transparent: false,
|
||||||
|
side: THREE.FrontSide,
|
||||||
|
shadowSide: THREE.FrontSide,
|
||||||
|
depthWrite: true,
|
||||||
|
depthTest: true
|
||||||
|
};
|
||||||
|
|
||||||
|
const blockMaterials = {
|
||||||
|
grass: [
|
||||||
|
new THREE.MeshBasicMaterial({ map: blockTextures.grass_side }),
|
||||||
|
new THREE.MeshBasicMaterial({ map: blockTextures.grass_side }),
|
||||||
|
new THREE.MeshBasicMaterial({ map: blockTextures.grass_top }),
|
||||||
|
new THREE.MeshBasicMaterial({ map: blockTextures.dirt }),
|
||||||
|
new THREE.MeshBasicMaterial({ map: blockTextures.grass_side }),
|
||||||
|
new THREE.MeshBasicMaterial({ map: blockTextures.grass_side })
|
||||||
|
],
|
||||||
|
dirt: Array(6).fill(new THREE.MeshBasicMaterial({ map: blockTextures.dirt })),
|
||||||
|
stone: Array(6).fill(new THREE.MeshBasicMaterial({ map: blockTextures.stone })),
|
||||||
|
wood: [
|
||||||
|
new THREE.MeshBasicMaterial({ map: blockTextures.wood }),
|
||||||
|
new THREE.MeshBasicMaterial({ map: blockTextures.wood }),
|
||||||
|
new THREE.MeshBasicMaterial({ map: blockTextures.wood_top }),
|
||||||
|
new THREE.MeshBasicMaterial({ map: blockTextures.wood_top }),
|
||||||
|
new THREE.MeshBasicMaterial({ map: blockTextures.wood }),
|
||||||
|
new THREE.MeshBasicMaterial({ map: blockTextures.wood })
|
||||||
|
],
|
||||||
|
leaves: Array(6).fill(new THREE.MeshBasicMaterial({
|
||||||
|
map: blockTextures.leaves,
|
||||||
|
transparent: true,
|
||||||
|
alphaTest: 0.5
|
||||||
|
})),
|
||||||
|
bedrock: Array(6).fill(new THREE.MeshBasicMaterial({ map: blockTextures.bedrock }))
|
||||||
|
};
|
||||||
|
|
||||||
|
// Simple lighting for Minecraft-style rendering
|
||||||
|
const ambientLight = new THREE.AmbientLight(0xffffff, 1.0);
|
||||||
|
scene.add(ambientLight);
|
||||||
|
|
||||||
|
// Enhanced block creation with Minecraft materials
|
||||||
|
function createBlock(x, y, z, type = 'grass') {
|
||||||
|
const geometry = new THREE.BoxGeometry(1, 1, 1);
|
||||||
|
const materials = blockMaterials[type];
|
||||||
|
const cube = new THREE.Mesh(geometry, materials);
|
||||||
|
cube.position.set(x, y, z);
|
||||||
|
cube.castShadow = true;
|
||||||
|
cube.receiveShadow = true;
|
||||||
|
cube.userData.type = type;
|
||||||
|
scene.add(cube);
|
||||||
|
blocks.set(`${x},${y},${z}`, cube);
|
||||||
|
return cube;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Improved terrain generation with more Minecraft-like features
|
||||||
|
const noise = new SimplexNoise();
|
||||||
|
const worldSize = 32;
|
||||||
|
const blocks = new Map();
|
||||||
|
|
||||||
|
function generateTerrain() {
|
||||||
|
// Generate bedrock layer
|
||||||
|
for (let x = -worldSize/2; x < worldSize/2; x++) {
|
||||||
|
for (let z = -worldSize/2; z < worldSize/2; z++) {
|
||||||
|
createBlock(x, 0, z, 'bedrock');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate terrain
|
||||||
|
for (let x = -worldSize/2; x < worldSize/2; x++) {
|
||||||
|
for (let z = -worldSize/2; z < worldSize/2; z++) {
|
||||||
|
const height = Math.floor(
|
||||||
|
(noise.noise2D(x * 0.1, z * 0.1) + 1) * 5 + 5
|
||||||
|
);
|
||||||
|
|
||||||
|
// Generate layers
|
||||||
|
for (let y = 1; y < height; y++) {
|
||||||
|
const blockType = y === height - 1 ? 'grass' :
|
||||||
|
y > height - 4 ? 'dirt' : 'stone';
|
||||||
|
createBlock(x, y, z, blockType);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate trees with better distribution
|
||||||
|
if (Math.random() < 0.02 && height > 3) {
|
||||||
|
const treeHeight = 4 + Math.floor(Math.random() * 3);
|
||||||
|
|
||||||
|
// Tree trunk
|
||||||
|
for (let y = height; y < height + treeHeight; y++) {
|
||||||
|
createBlock(x, y, z, 'wood');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tree leaves
|
||||||
|
for (let lx = -2; lx <= 2; lx++) {
|
||||||
|
for (let ly = -2; ly <= 2; ly++) {
|
||||||
|
for (let lz = -2; lz <= 2; lz++) {
|
||||||
|
if (Math.abs(lx) + Math.abs(ly) + Math.abs(lz) <= 4 && Math.random() < 0.7) {
|
||||||
|
createBlock(
|
||||||
|
x + lx,
|
||||||
|
height + treeHeight + ly,
|
||||||
|
z + lz,
|
||||||
|
'leaves'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Player Controls and Physics
|
||||||
|
const controls = new THREE.PointerLockControls(camera, document.body);
|
||||||
|
let canJump = false;
|
||||||
|
let velocity = new THREE.Vector3();
|
||||||
|
let moveForward = false;
|
||||||
|
let moveBackward = false;
|
||||||
|
let moveLeft = false;
|
||||||
|
let moveRight = false;
|
||||||
|
let playerHeight = 1.6;
|
||||||
|
camera.position.set(0, playerHeight + 20, 20); // Move camera back for better initial view
|
||||||
|
|
||||||
|
// Click to start
|
||||||
|
document.addEventListener('click', () => {
|
||||||
|
controls.lock();
|
||||||
|
});
|
||||||
|
|
||||||
|
controls.addEventListener('lock', () => {
|
||||||
|
document.getElementById('inventory').style.display = 'flex';
|
||||||
|
});
|
||||||
|
|
||||||
|
controls.addEventListener('unlock', () => {
|
||||||
|
document.getElementById('inventory').style.display = 'none';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Keyboard controls
|
||||||
|
const onKeyDown = function(event) {
|
||||||
|
switch (event.code) {
|
||||||
|
case 'ArrowUp':
|
||||||
|
case 'KeyW':
|
||||||
|
moveForward = true;
|
||||||
|
break;
|
||||||
|
case 'ArrowDown':
|
||||||
|
case 'KeyS':
|
||||||
|
moveBackward = true;
|
||||||
|
break;
|
||||||
|
case 'ArrowLeft':
|
||||||
|
case 'KeyA':
|
||||||
|
moveLeft = true;
|
||||||
|
break;
|
||||||
|
case 'ArrowRight':
|
||||||
|
case 'KeyD':
|
||||||
|
moveRight = true;
|
||||||
|
break;
|
||||||
|
case 'Space':
|
||||||
|
if (canJump) {
|
||||||
|
velocity.y = 8;
|
||||||
|
canJump = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onKeyUp = function(event) {
|
||||||
|
switch (event.code) {
|
||||||
|
case 'ArrowUp':
|
||||||
|
case 'KeyW':
|
||||||
|
moveForward = false;
|
||||||
|
break;
|
||||||
|
case 'ArrowDown':
|
||||||
|
case 'KeyS':
|
||||||
|
moveBackward = false;
|
||||||
|
break;
|
||||||
|
case 'ArrowLeft':
|
||||||
|
case 'KeyA':
|
||||||
|
moveLeft = false;
|
||||||
|
break;
|
||||||
|
case 'ArrowRight':
|
||||||
|
case 'KeyD':
|
||||||
|
moveRight = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('keydown', onKeyDown);
|
||||||
|
document.addEventListener('keyup', onKeyUp);
|
||||||
|
|
||||||
|
// Inventory system
|
||||||
|
const inventory = {
|
||||||
|
selectedSlot: 0,
|
||||||
|
slots: [
|
||||||
|
{ type: 'grass', count: Infinity },
|
||||||
|
{ type: 'dirt', count: Infinity },
|
||||||
|
{ type: 'stone', count: Infinity },
|
||||||
|
{ type: 'wood', count: Infinity },
|
||||||
|
{ type: 'leaves', count: Infinity },
|
||||||
|
{ type: 'bedrock', count: Infinity }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
function updateInventoryUI() {
|
||||||
|
const inventoryEl = document.getElementById('inventory');
|
||||||
|
inventoryEl.innerHTML = '';
|
||||||
|
inventory.slots.forEach((slot, index) => {
|
||||||
|
const slotEl = document.createElement('div');
|
||||||
|
slotEl.className = `inventory-slot ${index === inventory.selectedSlot ? 'selected' : ''}`;
|
||||||
|
slotEl.textContent = slot.type;
|
||||||
|
slotEl.onclick = () => {
|
||||||
|
inventory.selectedSlot = index;
|
||||||
|
updateInventoryUI();
|
||||||
|
};
|
||||||
|
inventoryEl.appendChild(slotEl);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mouse wheel to change selected slot
|
||||||
|
document.addEventListener('wheel', (event) => {
|
||||||
|
if (controls.isLocked) {
|
||||||
|
inventory.selectedSlot = (inventory.selectedSlot + (event.deltaY > 0 ? 1 : -1) + inventory.slots.length) % inventory.slots.length;
|
||||||
|
updateInventoryUI();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Block interaction
|
||||||
|
const raycaster = new THREE.Raycaster();
|
||||||
|
const mouse = new THREE.Vector2();
|
||||||
|
|
||||||
|
// Place block
|
||||||
|
document.addEventListener('contextmenu', (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
if (controls.isLocked) {
|
||||||
|
raycaster.setFromCamera(mouse, camera);
|
||||||
|
const intersects = raycaster.intersectObjects(scene.children);
|
||||||
|
|
||||||
|
if (intersects.length > 0) {
|
||||||
|
const intersect = intersects[0];
|
||||||
|
const position = intersect.point.add(intersect.face.normal);
|
||||||
|
const roundedPos = position.round();
|
||||||
|
|
||||||
|
// Check if block placement is not too close to player
|
||||||
|
const playerPos = camera.position.clone();
|
||||||
|
if (playerPos.distanceTo(roundedPos) > 2) {
|
||||||
|
const selectedType = inventory.slots[inventory.selectedSlot].type;
|
||||||
|
createBlock(
|
||||||
|
roundedPos.x,
|
||||||
|
roundedPos.y,
|
||||||
|
roundedPos.z,
|
||||||
|
selectedType
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Break block
|
||||||
|
document.addEventListener('click', (event) => {
|
||||||
|
if (controls.isLocked) {
|
||||||
|
raycaster.setFromCamera(mouse, camera);
|
||||||
|
const intersects = raycaster.intersectObjects(scene.children);
|
||||||
|
|
||||||
|
if (intersects.length > 0) {
|
||||||
|
const intersect = intersects[0];
|
||||||
|
const block = intersect.object;
|
||||||
|
|
||||||
|
// Prevent breaking bedrock
|
||||||
|
if (block.userData.type !== 'bedrock') {
|
||||||
|
scene.remove(block);
|
||||||
|
blocks.delete(`${block.position.x},${block.position.y},${block.position.z}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Player physics and movement
|
||||||
|
function movePlayer(delta) {
|
||||||
|
if (controls.isLocked) {
|
||||||
|
// Gravity
|
||||||
|
velocity.y -= 20 * delta; // Minecraft-like gravity
|
||||||
|
|
||||||
|
// Forward/backward movement
|
||||||
|
const direction = new THREE.Vector3();
|
||||||
|
direction.z = Number(moveForward) - Number(moveBackward);
|
||||||
|
direction.x = Number(moveRight) - Number(moveLeft);
|
||||||
|
direction.normalize();
|
||||||
|
|
||||||
|
// Apply movement based on camera direction
|
||||||
|
if (moveForward || moveBackward) {
|
||||||
|
velocity.z -= direction.z * 20 * delta;
|
||||||
|
}
|
||||||
|
if (moveLeft || moveRight) {
|
||||||
|
velocity.x -= direction.x * 20 * delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply friction
|
||||||
|
velocity.x *= 0.9;
|
||||||
|
velocity.z *= 0.9;
|
||||||
|
|
||||||
|
// Collision detection
|
||||||
|
const futurePosition = camera.position.clone();
|
||||||
|
futurePosition.x += velocity.x * delta;
|
||||||
|
futurePosition.y += velocity.y * delta;
|
||||||
|
futurePosition.z += velocity.z * delta;
|
||||||
|
|
||||||
|
// Check for collisions with blocks
|
||||||
|
const playerRadius = 0.3;
|
||||||
|
const checkPositions = [
|
||||||
|
[0, 0, 0], // Center
|
||||||
|
[playerRadius, 0, playerRadius], // Corners
|
||||||
|
[-playerRadius, 0, playerRadius],
|
||||||
|
[playerRadius, 0, -playerRadius],
|
||||||
|
[-playerRadius, 0, -playerRadius]
|
||||||
|
];
|
||||||
|
|
||||||
|
let collision = false;
|
||||||
|
for (const [offsetX, offsetY, offsetZ] of checkPositions) {
|
||||||
|
const checkPos = futurePosition.clone().add(new THREE.Vector3(offsetX, offsetY, offsetZ));
|
||||||
|
const blockPos = checkPos.clone().floor();
|
||||||
|
if (blocks.has(`${blockPos.x},${blockPos.y},${blockPos.z}`)) {
|
||||||
|
collision = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update position if no collision
|
||||||
|
if (!collision) {
|
||||||
|
controls.moveRight(-velocity.x * delta);
|
||||||
|
controls.moveForward(-velocity.z * delta);
|
||||||
|
camera.position.y += velocity.y * delta;
|
||||||
|
} else {
|
||||||
|
velocity.setX(0);
|
||||||
|
velocity.setZ(0);
|
||||||
|
if (velocity.y < 0) {
|
||||||
|
velocity.setY(0);
|
||||||
|
canJump = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent falling through ground
|
||||||
|
if (camera.position.y < playerHeight) {
|
||||||
|
camera.position.y = playerHeight;
|
||||||
|
velocity.y = 0;
|
||||||
|
canJump = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update HUD
|
||||||
|
document.getElementById('coordinates').textContent =
|
||||||
|
`Position: ${Math.round(camera.position.x)}, ${Math.round(camera.position.y)}, ${Math.round(camera.position.z)}`;
|
||||||
|
document.getElementById('selected-block').textContent =
|
||||||
|
`Selected: ${inventory.slots[inventory.selectedSlot].type}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ... (previous code for controls, movement, and interaction remains unchanged)
|
||||||
|
|
||||||
|
// Update animation loop with fog
|
||||||
|
function animate() {
|
||||||
|
requestAnimationFrame(animate);
|
||||||
|
const delta = Math.min(0.1, clock.getDelta());
|
||||||
|
movePlayer(delta);
|
||||||
|
|
||||||
|
// Update fog based on time of day (simplified)
|
||||||
|
const time = Date.now() * 0.001;
|
||||||
|
const fogColor = new THREE.Color(0x8CB4DB);
|
||||||
|
scene.fog.color = fogColor;
|
||||||
|
scene.background = fogColor;
|
||||||
|
|
||||||
|
renderer.render(scene, camera);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the game
|
||||||
|
generateTerrain();
|
||||||
|
animate();
|
||||||
|
|
||||||
|
// Handle window resize
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
camera.aspect = window.innerWidth / window.innerHeight;
|
||||||
|
camera.updateProjectionMatrix();
|
||||||
|
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
|
@ -13,7 +13,7 @@ import asyncio
|
||||||
import re
|
import re
|
||||||
import uuid
|
import uuid
|
||||||
from typing import List, Dict, Any, Optional, Tuple, AsyncGenerator, Callable, Union, Literal
|
from typing import List, Dict, Any, Optional, Tuple, AsyncGenerator, Callable, Union, Literal
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
from agentpress.tool import Tool, ToolResult
|
from agentpress.tool import Tool, ToolResult
|
||||||
from agentpress.tool_registry import ToolRegistry
|
from agentpress.tool_registry import ToolRegistry
|
||||||
|
@ -25,6 +25,15 @@ XmlAddingStrategy = Literal["user_message", "assistant_message", "inline_edit"]
|
||||||
# Type alias for tool execution strategy
|
# Type alias for tool execution strategy
|
||||||
ToolExecutionStrategy = Literal["sequential", "parallel"]
|
ToolExecutionStrategy = Literal["sequential", "parallel"]
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ToolExecutionContext:
|
||||||
|
"""Context for a tool execution including call details, result, and display info."""
|
||||||
|
tool_call: Dict[str, Any]
|
||||||
|
tool_index: int
|
||||||
|
result: Optional[ToolResult] = None
|
||||||
|
display_name: Optional[str] = None
|
||||||
|
error: Optional[Exception] = None
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ProcessorConfig:
|
class ProcessorConfig:
|
||||||
"""
|
"""
|
||||||
|
@ -103,6 +112,9 @@ class ResponseProcessor:
|
||||||
# Tool execution index counter
|
# Tool execution index counter
|
||||||
tool_execution_index = 0
|
tool_execution_index = 0
|
||||||
|
|
||||||
|
# Tool index for remaining tools (used at end of function)
|
||||||
|
tool_index = 0
|
||||||
|
|
||||||
logger.info(f"Starting to process streaming response for thread {thread_id}")
|
logger.info(f"Starting to process streaming response for thread {thread_id}")
|
||||||
logger.info(f"Config: XML={config.xml_tool_calling}, Native={config.native_tool_calling}, "
|
logger.info(f"Config: XML={config.xml_tool_calling}, Native={config.native_tool_calling}, "
|
||||||
f"Execute on stream={config.execute_on_stream}, Execution strategy={config.tool_execution_strategy}")
|
f"Execute on stream={config.execute_on_stream}, Execution strategy={config.tool_execution_strategy}")
|
||||||
|
@ -110,7 +122,6 @@ class ResponseProcessor:
|
||||||
try:
|
try:
|
||||||
async for chunk in llm_response:
|
async for chunk in llm_response:
|
||||||
# Default content to yield
|
# Default content to yield
|
||||||
content_to_yield = None
|
|
||||||
|
|
||||||
if hasattr(chunk, 'choices') and chunk.choices:
|
if hasattr(chunk, 'choices') and chunk.choices:
|
||||||
delta = chunk.choices[0].delta if hasattr(chunk.choices[0], 'delta') else None
|
delta = chunk.choices[0].delta if hasattr(chunk.choices[0], 'delta') else None
|
||||||
|
@ -136,16 +147,16 @@ class ResponseProcessor:
|
||||||
# Parse and extract the tool call
|
# Parse and extract the tool call
|
||||||
tool_call = self._parse_xml_tool_call(xml_chunk)
|
tool_call = self._parse_xml_tool_call(xml_chunk)
|
||||||
if tool_call:
|
if tool_call:
|
||||||
|
# Create a context for this tool execution
|
||||||
|
context = self._create_tool_context(
|
||||||
|
tool_call=tool_call,
|
||||||
|
tool_index=tool_execution_index
|
||||||
|
)
|
||||||
|
|
||||||
# Execute tool if needed, but in background
|
# Execute tool if needed, but in background
|
||||||
if config.execute_tools and config.execute_on_stream:
|
if config.execute_tools and config.execute_on_stream:
|
||||||
# Yield tool execution start message
|
# Yield tool execution start message
|
||||||
yield {
|
yield self._yield_tool_started(context)
|
||||||
"type": "tool_status",
|
|
||||||
"status": "started",
|
|
||||||
"name": tool_call["name"],
|
|
||||||
"message": f"Starting execution of {tool_call['name']}",
|
|
||||||
"tool_index": tool_execution_index
|
|
||||||
}
|
|
||||||
|
|
||||||
# Start tool execution as a background task
|
# Start tool execution as a background task
|
||||||
execution_task = asyncio.create_task(self._execute_tool(tool_call))
|
execution_task = asyncio.create_task(self._execute_tool(tool_call))
|
||||||
|
@ -154,7 +165,8 @@ class ResponseProcessor:
|
||||||
pending_tool_executions.append({
|
pending_tool_executions.append({
|
||||||
"task": execution_task,
|
"task": execution_task,
|
||||||
"tool_call": tool_call,
|
"tool_call": tool_call,
|
||||||
"tool_index": tool_execution_index
|
"tool_index": tool_execution_index,
|
||||||
|
"context": context
|
||||||
})
|
})
|
||||||
|
|
||||||
# Increment the tool execution index
|
# Increment the tool execution index
|
||||||
|
@ -208,14 +220,14 @@ class ResponseProcessor:
|
||||||
"id": current_tool['id']
|
"id": current_tool['id']
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Create a context for this tool execution
|
||||||
|
context = self._create_tool_context(
|
||||||
|
tool_call=tool_call_data,
|
||||||
|
tool_index=tool_execution_index
|
||||||
|
)
|
||||||
|
|
||||||
# Yield tool execution start message
|
# Yield tool execution start message
|
||||||
yield {
|
yield self._yield_tool_started(context)
|
||||||
"type": "tool_status",
|
|
||||||
"status": "started",
|
|
||||||
"name": tool_call_data["name"],
|
|
||||||
"message": f"Starting execution of {tool_call_data['name']}",
|
|
||||||
"tool_index": tool_execution_index
|
|
||||||
}
|
|
||||||
|
|
||||||
# Start tool execution as a background task
|
# Start tool execution as a background task
|
||||||
execution_task = asyncio.create_task(self._execute_tool(tool_call_data))
|
execution_task = asyncio.create_task(self._execute_tool(tool_call_data))
|
||||||
|
@ -224,7 +236,8 @@ class ResponseProcessor:
|
||||||
pending_tool_executions.append({
|
pending_tool_executions.append({
|
||||||
"task": execution_task,
|
"task": execution_task,
|
||||||
"tool_call": tool_call_data,
|
"tool_call": tool_call_data,
|
||||||
"tool_index": tool_execution_index
|
"tool_index": tool_execution_index,
|
||||||
|
"context": context
|
||||||
})
|
})
|
||||||
|
|
||||||
# Increment the tool execution index
|
# Increment the tool execution index
|
||||||
|
@ -245,22 +258,19 @@ class ResponseProcessor:
|
||||||
# Store result for later database updates
|
# Store result for later database updates
|
||||||
tool_results_buffer.append((tool_call, result))
|
tool_results_buffer.append((tool_call, result))
|
||||||
|
|
||||||
# Yield tool status message first
|
# Get or create the context
|
||||||
yield {
|
if "context" in execution:
|
||||||
"type": "tool_status",
|
context = execution["context"]
|
||||||
"status": "completed" if result.success else "failed",
|
context.result = result
|
||||||
"name": tool_call["name"],
|
else:
|
||||||
"message": f"Tool {tool_call['name']} {'completed successfully' if result.success else 'failed'}",
|
context = self._create_tool_context(tool_call, tool_index)
|
||||||
"tool_index": tool_index
|
context.result = result
|
||||||
}
|
|
||||||
|
|
||||||
# Yield tool execution result for client display
|
# Yield tool status message first
|
||||||
yield {
|
yield self._yield_tool_completed(context)
|
||||||
"type": "tool_result",
|
|
||||||
"name": tool_call["name"],
|
# Yield tool execution result
|
||||||
"result": str(result),
|
yield self._yield_tool_result(context)
|
||||||
"tool_index": tool_index
|
|
||||||
}
|
|
||||||
|
|
||||||
# Mark for removal
|
# Mark for removal
|
||||||
completed_executions.append(i)
|
completed_executions.append(i)
|
||||||
|
@ -270,14 +280,16 @@ class ResponseProcessor:
|
||||||
tool_call = execution["tool_call"]
|
tool_call = execution["tool_call"]
|
||||||
tool_index = execution.get("tool_index", -1)
|
tool_index = execution.get("tool_index", -1)
|
||||||
|
|
||||||
# Yield error status
|
# Get or create the context
|
||||||
yield {
|
if "context" in execution:
|
||||||
"type": "tool_status",
|
context = execution["context"]
|
||||||
"status": "error",
|
context.error = e
|
||||||
"name": tool_call["name"],
|
else:
|
||||||
"message": f"Error executing tool: {str(e)}",
|
context = self._create_tool_context(tool_call, tool_index)
|
||||||
"tool_index": tool_index
|
context.error = e
|
||||||
}
|
|
||||||
|
# Yield error status for the tool
|
||||||
|
yield self._yield_tool_error(context)
|
||||||
|
|
||||||
# Mark for removal
|
# Mark for removal
|
||||||
completed_executions.append(i)
|
completed_executions.append(i)
|
||||||
|
@ -305,33 +317,37 @@ class ResponseProcessor:
|
||||||
# Store result for later
|
# Store result for later
|
||||||
tool_results_buffer.append((tool_call, result))
|
tool_results_buffer.append((tool_call, result))
|
||||||
|
|
||||||
|
# Get or create the context
|
||||||
|
if "context" in execution:
|
||||||
|
context = execution["context"]
|
||||||
|
context.result = result
|
||||||
|
else:
|
||||||
|
context = self._create_tool_context(tool_call, tool_index)
|
||||||
|
context.result = result
|
||||||
|
|
||||||
# Yield tool status message first
|
# Yield tool status message first
|
||||||
yield {
|
yield self._yield_tool_completed(context)
|
||||||
"type": "tool_status",
|
|
||||||
"status": "completed" if result.success else "failed",
|
|
||||||
"name": tool_call["name"],
|
|
||||||
"message": f"Tool {tool_call['name']} {'completed successfully' if result.success else 'failed'}",
|
|
||||||
"tool_index": tool_index
|
|
||||||
}
|
|
||||||
|
|
||||||
# Yield tool execution result
|
# Yield tool execution result
|
||||||
yield {
|
yield self._yield_tool_result(context)
|
||||||
"type": "tool_result",
|
|
||||||
"name": tool_call["name"],
|
|
||||||
"result": str(result),
|
|
||||||
"tool_index": tool_index
|
|
||||||
}
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error processing remaining tool execution: {str(e)}")
|
logger.error(f"Error processing remaining tool execution: {str(e)}")
|
||||||
# Yield error status for the tool
|
# Yield error status for the tool
|
||||||
if "tool_call" in execution:
|
if "tool_call" in execution:
|
||||||
tool_call = execution["tool_call"]
|
tool_call = execution["tool_call"]
|
||||||
tool_index = execution.get("tool_index", -1)
|
tool_index = execution.get("tool_index", -1)
|
||||||
|
# Get or create the context
|
||||||
|
if "context" in execution:
|
||||||
|
context = execution["context"]
|
||||||
|
context.error = e
|
||||||
|
else:
|
||||||
|
context = self._create_tool_context(tool_call, tool_index)
|
||||||
|
context.error = e
|
||||||
|
formatted_result = self._format_xml_tool_result(tool_call, result)
|
||||||
yield {
|
yield {
|
||||||
"type": "tool_status",
|
"type": "tool_result",
|
||||||
"status": "error",
|
"name": context.display_name,
|
||||||
"name": tool_call.get("name", "unknown"),
|
"result": formatted_result,
|
||||||
"message": f"Error processing tool result: {str(e)}",
|
|
||||||
"tool_index": tool_index
|
"tool_index": tool_index
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -374,6 +390,16 @@ class ResponseProcessor:
|
||||||
config.xml_adding_strategy
|
config.xml_adding_strategy
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Create context for tool result
|
||||||
|
context = self._create_tool_context(tool_call, tool_index)
|
||||||
|
context.result = result
|
||||||
|
|
||||||
|
# Yield tool execution result
|
||||||
|
yield self._yield_tool_result(context)
|
||||||
|
|
||||||
|
# Increment tool index for next tool
|
||||||
|
tool_index += 1
|
||||||
|
|
||||||
# Execute any remaining tool calls if not done during streaming
|
# Execute any remaining tool calls if not done during streaming
|
||||||
if config.execute_tools and not config.execute_on_stream:
|
if config.execute_tools and not config.execute_on_stream:
|
||||||
tool_calls_to_execute = []
|
tool_calls_to_execute = []
|
||||||
|
@ -414,12 +440,15 @@ class ResponseProcessor:
|
||||||
config.xml_adding_strategy
|
config.xml_adding_strategy
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Create context for tool result
|
||||||
|
context = self._create_tool_context(tool_call, tool_index)
|
||||||
|
context.result = result
|
||||||
|
|
||||||
# Yield tool execution result
|
# Yield tool execution result
|
||||||
yield {
|
yield self._yield_tool_result(context)
|
||||||
"type": "tool_result",
|
|
||||||
"name": tool_call["name"],
|
# Increment tool index for next tool
|
||||||
"result": str(result)
|
tool_index += 1
|
||||||
}
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error processing stream: {str(e)}", exc_info=True)
|
logger.error(f"Error processing stream: {str(e)}", exc_info=True)
|
||||||
|
@ -445,6 +474,8 @@ class ResponseProcessor:
|
||||||
# Extract content and tool calls from response
|
# Extract content and tool calls from response
|
||||||
content = ""
|
content = ""
|
||||||
tool_calls = []
|
tool_calls = []
|
||||||
|
# Tool execution counter
|
||||||
|
tool_index = 0
|
||||||
|
|
||||||
if hasattr(llm_response, 'choices') and llm_response.choices:
|
if hasattr(llm_response, 'choices') and llm_response.choices:
|
||||||
response_message = llm_response.choices[0].message if hasattr(llm_response.choices[0], 'message') else None
|
response_message = llm_response.choices[0].message if hasattr(llm_response.choices[0], 'message') else None
|
||||||
|
@ -509,12 +540,15 @@ class ResponseProcessor:
|
||||||
config.xml_adding_strategy
|
config.xml_adding_strategy
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Create context for tool result
|
||||||
|
context = self._create_tool_context(tool_call, tool_index)
|
||||||
|
context.result = result
|
||||||
|
|
||||||
# Yield tool execution result
|
# Yield tool execution result
|
||||||
yield {
|
yield self._yield_tool_result(context)
|
||||||
"type": "tool_result",
|
|
||||||
"name": tool_call["name"],
|
# Increment tool index for next tool
|
||||||
"result": str(result)
|
tool_index += 1
|
||||||
}
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error processing response: {str(e)}", exc_info=True)
|
logger.error(f"Error processing response: {str(e)}", exc_info=True)
|
||||||
|
@ -924,18 +958,12 @@ class ResponseProcessor:
|
||||||
# Determine message role based on strategy
|
# Determine message role based on strategy
|
||||||
result_role = "user" if strategy == "user_message" else "assistant"
|
result_role = "user" if strategy == "user_message" else "assistant"
|
||||||
|
|
||||||
# Determine if this is an XML tool
|
# Create a context for consistent formatting
|
||||||
is_xml_tool = False
|
context = self._create_tool_context(tool_call, 0) # Index doesn't matter for DB
|
||||||
if hasattr(self.tool_registry, 'xml_tools'):
|
context.result = result
|
||||||
is_xml_tool = tool_call["name"] in self.tool_registry.xml_tools
|
|
||||||
|
|
||||||
# Format the content based on tool type
|
# Format the content using the formatting helper
|
||||||
if is_xml_tool:
|
content = self._format_xml_tool_result(tool_call, result)
|
||||||
# For XML tools, use simple content
|
|
||||||
content = str(result)
|
|
||||||
else:
|
|
||||||
# For native tools, include context about which tool was called
|
|
||||||
content = f"Result for {tool_call['name']}: {str(result)}"
|
|
||||||
|
|
||||||
# Add the message with the appropriate role
|
# Add the message with the appropriate role
|
||||||
result_message = {
|
result_message = {
|
||||||
|
@ -953,3 +981,99 @@ class ResponseProcessor:
|
||||||
})
|
})
|
||||||
except Exception as e2:
|
except Exception as e2:
|
||||||
logger.error(f"Failed even with fallback message: {str(e2)}", exc_info=True)
|
logger.error(f"Failed even with fallback message: {str(e2)}", exc_info=True)
|
||||||
|
|
||||||
|
|
||||||
|
def _format_xml_tool_result(self, tool_call: Dict[str, Any], result: ToolResult) -> str:
|
||||||
|
"""Format a tool result as a simple XML tag.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tool_call: The tool call that was executed
|
||||||
|
result: The result of the tool execution
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
String containing the XML-formatted result or standard format if not XML
|
||||||
|
"""
|
||||||
|
xml_tag_name = self._get_tool_display_name(tool_call["name"])
|
||||||
|
|
||||||
|
# If display name is same as method name, it's not an XML tool
|
||||||
|
if xml_tag_name == tool_call["name"]:
|
||||||
|
return f"Result for {tool_call['name']}: {str(result)}"
|
||||||
|
|
||||||
|
# Format as simple XML tag without attributes
|
||||||
|
xml_output = f"<{xml_tag_name}> {str(result)} </{xml_tag_name}>"
|
||||||
|
return xml_output
|
||||||
|
|
||||||
|
def _get_tool_display_name(self, method_name: str) -> str:
|
||||||
|
"""Get the display name for a tool (XML tag name if applicable, or method name)."""
|
||||||
|
if not hasattr(self.tool_registry, 'xml_tools'):
|
||||||
|
return method_name
|
||||||
|
|
||||||
|
# Check if this method corresponds to an XML tool
|
||||||
|
for tag_name, xml_tool_info in self.tool_registry.xml_tools.items():
|
||||||
|
if xml_tool_info.get('method') == method_name:
|
||||||
|
return tag_name
|
||||||
|
|
||||||
|
# Default to the method name if no XML tag found
|
||||||
|
return method_name
|
||||||
|
|
||||||
|
# At class level, define a method for yielding tool results
|
||||||
|
def _yield_tool_result(self, context: ToolExecutionContext) -> Dict[str, Any]:
|
||||||
|
"""Format and return a tool result message."""
|
||||||
|
if not context.result:
|
||||||
|
return {
|
||||||
|
"type": "tool_result",
|
||||||
|
"name": context.display_name,
|
||||||
|
"result": "No result available",
|
||||||
|
"tool_index": context.tool_index
|
||||||
|
}
|
||||||
|
|
||||||
|
formatted_result = self._format_xml_tool_result(context.tool_call, context.result)
|
||||||
|
return {
|
||||||
|
"type": "tool_result",
|
||||||
|
"name": context.display_name,
|
||||||
|
"result": formatted_result,
|
||||||
|
"tool_index": context.tool_index
|
||||||
|
}
|
||||||
|
|
||||||
|
def _create_tool_context(self, tool_call: Dict[str, Any], tool_index: int) -> ToolExecutionContext:
|
||||||
|
"""Create a tool execution context with display name populated."""
|
||||||
|
context = ToolExecutionContext(
|
||||||
|
tool_call=tool_call,
|
||||||
|
tool_index=tool_index
|
||||||
|
)
|
||||||
|
context.display_name = self._get_tool_display_name(tool_call["name"])
|
||||||
|
return context
|
||||||
|
|
||||||
|
def _yield_tool_started(self, context: ToolExecutionContext) -> Dict[str, Any]:
|
||||||
|
"""Format and return a tool started status message."""
|
||||||
|
return {
|
||||||
|
"type": "tool_status",
|
||||||
|
"status": "started",
|
||||||
|
"name": context.display_name,
|
||||||
|
"message": f"Starting execution of {context.display_name}",
|
||||||
|
"tool_index": context.tool_index
|
||||||
|
}
|
||||||
|
|
||||||
|
def _yield_tool_completed(self, context: ToolExecutionContext) -> Dict[str, Any]:
|
||||||
|
"""Format and return a tool completed/failed status message."""
|
||||||
|
if not context.result:
|
||||||
|
return self._yield_tool_error(context)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"type": "tool_status",
|
||||||
|
"status": "completed" if context.result.success else "failed",
|
||||||
|
"name": context.display_name,
|
||||||
|
"message": f"Tool {context.display_name} {'completed successfully' if context.result.success else 'failed'}",
|
||||||
|
"tool_index": context.tool_index
|
||||||
|
}
|
||||||
|
|
||||||
|
def _yield_tool_error(self, context: ToolExecutionContext) -> Dict[str, Any]:
|
||||||
|
"""Format and return a tool error status message."""
|
||||||
|
error_msg = str(context.error) if context.error else "Unknown error"
|
||||||
|
return {
|
||||||
|
"type": "tool_status",
|
||||||
|
"status": "error",
|
||||||
|
"name": context.display_name,
|
||||||
|
"message": f"Error executing tool: {error_msg}",
|
||||||
|
"tool_index": context.tool_index
|
||||||
|
}
|
|
@ -1,80 +0,0 @@
|
||||||
"""
|
|
||||||
Simplified test for sequential vs parallel tool execution.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import asyncio
|
|
||||||
import sys
|
|
||||||
from dotenv import load_dotenv
|
|
||||||
from agentpress.thread_manager import ThreadManager
|
|
||||||
from agentpress.response_processor import ProcessorConfig
|
|
||||||
from agent.tools.wait_tool import WaitTool
|
|
||||||
from agentpress.tool import ToolResult
|
|
||||||
|
|
||||||
# Load environment variables
|
|
||||||
load_dotenv()
|
|
||||||
|
|
||||||
async def test_execution():
|
|
||||||
"""Directly test sequential vs parallel execution."""
|
|
||||||
print("\n" + "="*80)
|
|
||||||
print("🧪 TESTING PARALLEL VS SEQUENTIAL EXECUTION")
|
|
||||||
print("="*80 + "\n")
|
|
||||||
|
|
||||||
# Initialize ThreadManager and register tools
|
|
||||||
thread_manager = ThreadManager()
|
|
||||||
thread_manager.add_tool(WaitTool)
|
|
||||||
|
|
||||||
# Create wait tool calls
|
|
||||||
wait_tool_calls = [
|
|
||||||
{"name": "wait", "arguments": {"seconds": 2, "message": "Wait tool 1"}},
|
|
||||||
{"name": "wait", "arguments": {"seconds": 2, "message": "Wait tool 2"}},
|
|
||||||
{"name": "wait", "arguments": {"seconds": 2, "message": "Wait tool 3"}}
|
|
||||||
]
|
|
||||||
|
|
||||||
# Test sequential execution
|
|
||||||
print("🔄 Testing Sequential Execution")
|
|
||||||
print("-"*60)
|
|
||||||
sequential_start = asyncio.get_event_loop().time()
|
|
||||||
sequential_results = await thread_manager.response_processor._execute_tools(
|
|
||||||
wait_tool_calls,
|
|
||||||
sequential=True,
|
|
||||||
execution_strategy="sequential"
|
|
||||||
)
|
|
||||||
sequential_end = asyncio.get_event_loop().time()
|
|
||||||
sequential_time = sequential_end - sequential_start
|
|
||||||
|
|
||||||
print(f"Sequential execution completed in {sequential_time:.2f} seconds")
|
|
||||||
print()
|
|
||||||
|
|
||||||
# Test parallel execution
|
|
||||||
print("⚡ Testing Parallel Execution")
|
|
||||||
print("-"*60)
|
|
||||||
parallel_start = asyncio.get_event_loop().time()
|
|
||||||
parallel_results = await thread_manager.response_processor._execute_tools(
|
|
||||||
wait_tool_calls,
|
|
||||||
sequential=False,
|
|
||||||
execution_strategy="parallel"
|
|
||||||
)
|
|
||||||
parallel_end = asyncio.get_event_loop().time()
|
|
||||||
parallel_time = parallel_end - parallel_start
|
|
||||||
|
|
||||||
print(f"Parallel execution completed in {parallel_time:.2f} seconds")
|
|
||||||
print()
|
|
||||||
|
|
||||||
# Report results
|
|
||||||
print("\n" + "="*80)
|
|
||||||
print(f"🧮 RESULTS SUMMARY")
|
|
||||||
print("="*80)
|
|
||||||
print(f"Sequential: {sequential_time:.2f} seconds")
|
|
||||||
print(f"Parallel: {parallel_time:.2f} seconds")
|
|
||||||
print(f"Speedup: {sequential_time/parallel_time:.2f}x faster")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
try:
|
|
||||||
asyncio.run(test_execution())
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print("\n\n❌ Test interrupted by user")
|
|
||||||
sys.exit(1)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"\n\n❌ Error during test: {str(e)}")
|
|
||||||
sys.exit(1)
|
|
|
@ -1,140 +0,0 @@
|
||||||
"""
|
|
||||||
Test script for demonstrating sequential vs parallel tool execution strategies.
|
|
||||||
|
|
||||||
This script creates a conversation thread and tests both execution strategies
|
|
||||||
with multiple wait tool calls to clearly demonstrate the difference.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import asyncio
|
|
||||||
import sys
|
|
||||||
from dotenv import load_dotenv
|
|
||||||
from agentpress.thread_manager import ThreadManager
|
|
||||||
from agentpress.response_processor import ProcessorConfig
|
|
||||||
from agent.tools.wait_tool import WaitTool
|
|
||||||
|
|
||||||
# Load environment variables
|
|
||||||
load_dotenv()
|
|
||||||
|
|
||||||
TOOL_XML_SEQUENTIAL = """
|
|
||||||
Here are some examples of using the wait tool:
|
|
||||||
|
|
||||||
<wait seconds="2">This is sequential wait 1</wait>
|
|
||||||
<wait seconds="2">This is sequential wait 2</wait>
|
|
||||||
<wait seconds="2">This is sequential wait 3</wait>
|
|
||||||
|
|
||||||
Now wait sequence:
|
|
||||||
<wait-sequence count="3" seconds="1" label="Sequential Test" />
|
|
||||||
"""
|
|
||||||
|
|
||||||
TOOL_XML_PARALLEL = """
|
|
||||||
Here are some examples of using the wait tool:
|
|
||||||
|
|
||||||
<wait seconds="2">This is parallel wait 1</wait>
|
|
||||||
<wait seconds="2">This is parallel wait 2</wait>
|
|
||||||
<wait seconds="2">This is parallel wait 3</wait>
|
|
||||||
|
|
||||||
Now wait sequence:
|
|
||||||
<wait-sequence count="3" seconds="1" label="Parallel Test" />
|
|
||||||
"""
|
|
||||||
|
|
||||||
async def test_execution_strategies():
|
|
||||||
"""Test both sequential and parallel execution strategies."""
|
|
||||||
print("\n" + "="*80)
|
|
||||||
print("🧪 TESTING TOOL EXECUTION STRATEGIES")
|
|
||||||
print("="*80 + "\n")
|
|
||||||
|
|
||||||
# Initialize ThreadManager and register tools
|
|
||||||
thread_manager = ThreadManager()
|
|
||||||
thread_manager.add_tool(WaitTool)
|
|
||||||
|
|
||||||
# Create a test thread
|
|
||||||
thread_id = await thread_manager.create_thread()
|
|
||||||
print(f"🧵 Created test thread: {thread_id}\n")
|
|
||||||
|
|
||||||
# Add system message
|
|
||||||
await thread_manager.add_message(
|
|
||||||
thread_id,
|
|
||||||
{
|
|
||||||
"role": "system",
|
|
||||||
"content": "You are a testing assistant that will execute wait commands."
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
# Test both strategies
|
|
||||||
test_cases = [
|
|
||||||
{"name": "Sequential", "strategy": "sequential", "content": TOOL_XML_SEQUENTIAL},
|
|
||||||
{"name": "Parallel", "strategy": "parallel", "content": TOOL_XML_PARALLEL}
|
|
||||||
]
|
|
||||||
|
|
||||||
for test in test_cases:
|
|
||||||
print("\n" + "-"*60)
|
|
||||||
print(f"🔍 Testing {test['name']} Execution Strategy")
|
|
||||||
print("-"*60 + "\n")
|
|
||||||
|
|
||||||
# Add special assistant message with tool calls
|
|
||||||
# This simulates an LLM response with tool calls
|
|
||||||
await thread_manager.add_message(
|
|
||||||
thread_id,
|
|
||||||
{
|
|
||||||
"role": "assistant",
|
|
||||||
"content": test["content"]
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
start_time = asyncio.get_event_loop().time()
|
|
||||||
print(f"⏱️ Starting execution with {test['strategy']} strategy at {start_time:.2f}s")
|
|
||||||
|
|
||||||
# Process the response with appropriate strategy
|
|
||||||
config = ProcessorConfig(
|
|
||||||
xml_tool_calling=True,
|
|
||||||
native_tool_calling=False,
|
|
||||||
execute_tools=True,
|
|
||||||
execute_on_stream=False,
|
|
||||||
tool_execution_strategy=test["strategy"]
|
|
||||||
)
|
|
||||||
|
|
||||||
# Get the last message to process
|
|
||||||
messages = await thread_manager.get_messages(thread_id)
|
|
||||||
last_message = messages[-1]
|
|
||||||
|
|
||||||
# Create a simple non-streaming response object
|
|
||||||
class MockResponse:
|
|
||||||
def __init__(self, content):
|
|
||||||
self.choices = [type('obj', (object,), {
|
|
||||||
'message': type('obj', (object,), {
|
|
||||||
'content': content
|
|
||||||
})
|
|
||||||
})]
|
|
||||||
|
|
||||||
mock_response = MockResponse(last_message["content"])
|
|
||||||
|
|
||||||
# Process using the response processor
|
|
||||||
async for chunk in thread_manager.response_processor.process_non_streaming_response(
|
|
||||||
llm_response=mock_response,
|
|
||||||
thread_id=thread_id,
|
|
||||||
config=config
|
|
||||||
):
|
|
||||||
if chunk.get('type') == 'tool_result':
|
|
||||||
elapsed = asyncio.get_event_loop().time() - start_time
|
|
||||||
print(f"⏱️ [{elapsed:.2f}s] Tool result: {chunk['name']}")
|
|
||||||
print(f" {chunk['result']}")
|
|
||||||
print()
|
|
||||||
|
|
||||||
end_time = asyncio.get_event_loop().time()
|
|
||||||
elapsed = end_time - start_time
|
|
||||||
print(f"\n⏱️ {test['name']} execution completed in {elapsed:.2f} seconds")
|
|
||||||
|
|
||||||
print("\n" + "="*80)
|
|
||||||
print("✅ Testing completed")
|
|
||||||
print("="*80 + "\n")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
try:
|
|
||||||
asyncio.run(test_execution_strategies())
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print("\n\n❌ Test interrupted by user")
|
|
||||||
sys.exit(1)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"\n\n❌ Error during test: {str(e)}")
|
|
||||||
sys.exit(1)
|
|
Loading…
Reference in New Issue