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>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Flappy Bird Test</title>
|
||||
<title>Minecraft Clone</title>
|
||||
<style>
|
||||
canvas {
|
||||
border: 1px solid black;
|
||||
/* Previous CSS styles remain the same */
|
||||
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>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="gameCanvas" width="320" height="480"></canvas>
|
||||
<div class="crosshair">+</div>
|
||||
<div class="hud">
|
||||
<div id="coordinates"></div>
|
||||
<div id="selected-block"></div>
|
||||
</div>
|
||||
<div class="inventory" id="inventory"></div>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/PointerLockControls.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/simplex-noise/2.4.0/simplex-noise.min.js"></script>
|
||||
<script>
|
||||
const canvas = document.getElementById('gameCanvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
// Test bird
|
||||
const bird = {
|
||||
x: 50,
|
||||
y: 200,
|
||||
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'
|
||||
};
|
||||
|
||||
// Configure texture settings for pixelated look
|
||||
function loadMinecraftTexture(path) {
|
||||
const texture = textureLoader.load(path);
|
||||
texture.magFilter = THREE.NearestFilter;
|
||||
texture.minFilter = THREE.NearestFilter;
|
||||
texture.wrapS = THREE.RepeatWrapping;
|
||||
texture.wrapT = THREE.RepeatWrapping;
|
||||
return texture;
|
||||
}
|
||||
|
||||
// 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
|
||||
};
|
||||
|
||||
// Draw test bird
|
||||
function draw() {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.fillStyle = 'red';
|
||||
ctx.fillRect(bird.x, bird.y, bird.size, bird.size);
|
||||
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);
|
||||
|
||||
draw();
|
||||
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>
|
||||
</body>
|
||||
</html>
|
|
@ -13,7 +13,7 @@ import asyncio
|
|||
import re
|
||||
import uuid
|
||||
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_registry import ToolRegistry
|
||||
|
@ -25,6 +25,15 @@ XmlAddingStrategy = Literal["user_message", "assistant_message", "inline_edit"]
|
|||
# Type alias for tool execution strategy
|
||||
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
|
||||
class ProcessorConfig:
|
||||
"""
|
||||
|
@ -103,6 +112,9 @@ class ResponseProcessor:
|
|||
# Tool execution index counter
|
||||
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"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}")
|
||||
|
@ -110,7 +122,6 @@ class ResponseProcessor:
|
|||
try:
|
||||
async for chunk in llm_response:
|
||||
# Default content to yield
|
||||
content_to_yield = None
|
||||
|
||||
if hasattr(chunk, 'choices') and chunk.choices:
|
||||
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
|
||||
tool_call = self._parse_xml_tool_call(xml_chunk)
|
||||
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
|
||||
if config.execute_tools and config.execute_on_stream:
|
||||
# Yield tool execution start message
|
||||
yield {
|
||||
"type": "tool_status",
|
||||
"status": "started",
|
||||
"name": tool_call["name"],
|
||||
"message": f"Starting execution of {tool_call['name']}",
|
||||
"tool_index": tool_execution_index
|
||||
}
|
||||
yield self._yield_tool_started(context)
|
||||
|
||||
# Start tool execution as a background task
|
||||
execution_task = asyncio.create_task(self._execute_tool(tool_call))
|
||||
|
@ -154,7 +165,8 @@ class ResponseProcessor:
|
|||
pending_tool_executions.append({
|
||||
"task": execution_task,
|
||||
"tool_call": tool_call,
|
||||
"tool_index": tool_execution_index
|
||||
"tool_index": tool_execution_index,
|
||||
"context": context
|
||||
})
|
||||
|
||||
# Increment the tool execution index
|
||||
|
@ -208,14 +220,14 @@ class ResponseProcessor:
|
|||
"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 {
|
||||
"type": "tool_status",
|
||||
"status": "started",
|
||||
"name": tool_call_data["name"],
|
||||
"message": f"Starting execution of {tool_call_data['name']}",
|
||||
"tool_index": tool_execution_index
|
||||
}
|
||||
yield self._yield_tool_started(context)
|
||||
|
||||
# Start tool execution as a background task
|
||||
execution_task = asyncio.create_task(self._execute_tool(tool_call_data))
|
||||
|
@ -224,7 +236,8 @@ class ResponseProcessor:
|
|||
pending_tool_executions.append({
|
||||
"task": execution_task,
|
||||
"tool_call": tool_call_data,
|
||||
"tool_index": tool_execution_index
|
||||
"tool_index": tool_execution_index,
|
||||
"context": context
|
||||
})
|
||||
|
||||
# Increment the tool execution index
|
||||
|
@ -245,22 +258,19 @@ class ResponseProcessor:
|
|||
# Store result for later database updates
|
||||
tool_results_buffer.append((tool_call, result))
|
||||
|
||||
# Yield tool status message first
|
||||
yield {
|
||||
"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
|
||||
}
|
||||
# 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 execution result for client display
|
||||
yield {
|
||||
"type": "tool_result",
|
||||
"name": tool_call["name"],
|
||||
"result": str(result),
|
||||
"tool_index": tool_index
|
||||
}
|
||||
# Yield tool status message first
|
||||
yield self._yield_tool_completed(context)
|
||||
|
||||
# Yield tool execution result
|
||||
yield self._yield_tool_result(context)
|
||||
|
||||
# Mark for removal
|
||||
completed_executions.append(i)
|
||||
|
@ -270,14 +280,16 @@ class ResponseProcessor:
|
|||
tool_call = execution["tool_call"]
|
||||
tool_index = execution.get("tool_index", -1)
|
||||
|
||||
# Yield error status
|
||||
yield {
|
||||
"type": "tool_status",
|
||||
"status": "error",
|
||||
"name": tool_call["name"],
|
||||
"message": f"Error executing tool: {str(e)}",
|
||||
"tool_index": tool_index
|
||||
}
|
||||
# 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
|
||||
|
||||
# Yield error status for the tool
|
||||
yield self._yield_tool_error(context)
|
||||
|
||||
# Mark for removal
|
||||
completed_executions.append(i)
|
||||
|
@ -305,33 +317,37 @@ class ResponseProcessor:
|
|||
# Store result for later
|
||||
tool_results_buffer.append((tool_call, result))
|
||||
|
||||
# Yield tool status message first
|
||||
yield {
|
||||
"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
|
||||
}
|
||||
# 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 self._yield_tool_completed(context)
|
||||
|
||||
# Yield tool execution result
|
||||
yield {
|
||||
"type": "tool_result",
|
||||
"name": tool_call["name"],
|
||||
"result": str(result),
|
||||
"tool_index": tool_index
|
||||
}
|
||||
yield self._yield_tool_result(context)
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing remaining tool execution: {str(e)}")
|
||||
# Yield error status for the tool
|
||||
if "tool_call" in execution:
|
||||
tool_call = execution["tool_call"]
|
||||
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 {
|
||||
"type": "tool_status",
|
||||
"status": "error",
|
||||
"name": tool_call.get("name", "unknown"),
|
||||
"message": f"Error processing tool result: {str(e)}",
|
||||
"type": "tool_result",
|
||||
"name": context.display_name,
|
||||
"result": formatted_result,
|
||||
"tool_index": tool_index
|
||||
}
|
||||
|
||||
|
@ -373,6 +389,16 @@ class ResponseProcessor:
|
|||
result,
|
||||
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
|
||||
if config.execute_tools and not config.execute_on_stream:
|
||||
|
@ -414,12 +440,15 @@ class ResponseProcessor:
|
|||
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 {
|
||||
"type": "tool_result",
|
||||
"name": tool_call["name"],
|
||||
"result": str(result)
|
||||
}
|
||||
yield self._yield_tool_result(context)
|
||||
|
||||
# Increment tool index for next tool
|
||||
tool_index += 1
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing stream: {str(e)}", exc_info=True)
|
||||
|
@ -445,6 +474,8 @@ class ResponseProcessor:
|
|||
# Extract content and tool calls from response
|
||||
content = ""
|
||||
tool_calls = []
|
||||
# Tool execution counter
|
||||
tool_index = 0
|
||||
|
||||
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
|
||||
|
@ -509,12 +540,15 @@ class ResponseProcessor:
|
|||
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 {
|
||||
"type": "tool_result",
|
||||
"name": tool_call["name"],
|
||||
"result": str(result)
|
||||
}
|
||||
yield self._yield_tool_result(context)
|
||||
|
||||
# Increment tool index for next tool
|
||||
tool_index += 1
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing response: {str(e)}", exc_info=True)
|
||||
|
@ -924,18 +958,12 @@ class ResponseProcessor:
|
|||
# Determine message role based on strategy
|
||||
result_role = "user" if strategy == "user_message" else "assistant"
|
||||
|
||||
# Determine if this is an XML tool
|
||||
is_xml_tool = False
|
||||
if hasattr(self.tool_registry, 'xml_tools'):
|
||||
is_xml_tool = tool_call["name"] in self.tool_registry.xml_tools
|
||||
# Create a context for consistent formatting
|
||||
context = self._create_tool_context(tool_call, 0) # Index doesn't matter for DB
|
||||
context.result = result
|
||||
|
||||
# Format the content based on tool type
|
||||
if is_xml_tool:
|
||||
# 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)}"
|
||||
# Format the content using the formatting helper
|
||||
content = self._format_xml_tool_result(tool_call, result)
|
||||
|
||||
# Add the message with the appropriate role
|
||||
result_message = {
|
||||
|
@ -952,4 +980,100 @@ class ResponseProcessor:
|
|||
"content": str(result)
|
||||
})
|
||||
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