diff --git a/backend/agent/prompt.py b/backend/agent/prompt.py index b63dd451..d8a2046b 100644 --- a/backend/agent/prompt.py +++ b/backend/agent/prompt.py @@ -82,4 +82,5 @@ def get_system_prompt(): ''' Returns the system prompt with XML tool usage instructions. ''' - return SYSTEM_PROMPT + RESPONSE_FORMAT \ No newline at end of file + return SYSTEM_PROMPT +#+ RESPONSE_FORMAT \ No newline at end of file diff --git a/backend/agent/run.py b/backend/agent/run.py index 672a6329..b0655b10 100644 --- a/backend/agent/run.py +++ b/backend/agent/run.py @@ -56,8 +56,8 @@ Current development environment workspace state: llm_temperature=0.1, llm_max_tokens=8000, processor_config=ProcessorConfig( - xml_tool_calling=True, - native_tool_calling=False, + xml_tool_calling=False, + native_tool_calling=True, execute_tools=True, execute_on_stream=True, tool_execution_strategy="sequential", @@ -126,6 +126,7 @@ async def test_agent(): chunk_counter = 0 current_response = "" + tool_call_counter = 0 # Track number of tool calls async for chunk in run_agent(thread_id=thread_id, stream=True, thread_manager=thread_manager): chunk_counter += 1 @@ -139,9 +140,47 @@ async def test_agent(): print(f"🛠️ Tool Result: {chunk.get('function_name', 'Unknown Tool')}") print(f"📝 {chunk.get('result', chunk)}") print("="*50 + "\n") + elif chunk.get('type') == 'tool_call_chunk': + # Display native tool call chunks as they arrive + tool_call = chunk.get('tool_call', {}) + + # Check if it's a meaningful part of the tool call to display + if tool_call.get('function', {}).get('arguments'): + args = tool_call.get('function', {}).get('arguments', '') + + # Only show when we have substantial arguments or a function name + should_display = ( + len(args) > 3 or # More than just '{}' + tool_call.get('function', {}).get('name') # Or we have a name + ) + + if should_display: + tool_call_counter += 1 + print("\n" + "-"*50) + print(f"🔧 Tool Call #{tool_call_counter}: {tool_call.get('function', {}).get('name', 'Building...')}") + + # Try to parse and pretty print the arguments if they're JSON + try: + # Check if it's complete JSON or just a fragment + if args.strip().startswith('{') and args.strip().endswith('}'): + args_obj = json.loads(args) + print(f"📋 Arguments: {json.dumps(args_obj, indent=2)}") + else: + print(f"📋 Arguments (partial): {args}") + except json.JSONDecodeError: + print(f"📋 Arguments (building): {args}") + + print("-"*50) + + # Return to the current content display + if current_response: + print("\nContinuing response:", flush=True) + print(current_response, end='', flush=True) print("\n" + "="*50) print(f"✅ Agent completed. Processed {chunk_counter} chunks.") + if tool_call_counter > 0: + print(f"🔧 Found {tool_call_counter} native tool calls.") print("="*50 + "\n") print("\n" + "="*50) diff --git a/backend/agentpress/response_processor.py b/backend/agentpress/response_processor.py index e03d12dc..33e9291e 100644 --- a/backend/agentpress/response_processor.py +++ b/backend/agentpress/response_processor.py @@ -175,6 +175,37 @@ class ResponseProcessor: # Process native tool calls if config.native_tool_calling and delta and hasattr(delta, 'tool_calls') and delta.tool_calls: for tool_call in delta.tool_calls: + # Yield the raw tool call chunk directly to the stream + # Safely extract tool call data even if model_dump isn't available + tool_call_data = {} + + if hasattr(tool_call, 'model_dump'): + # Use model_dump if available (OpenAI client) + tool_call_data = tool_call.model_dump() + else: + # Manual extraction if model_dump not available + if hasattr(tool_call, 'id'): + tool_call_data['id'] = tool_call.id + if hasattr(tool_call, 'index'): + tool_call_data['index'] = tool_call.index + if hasattr(tool_call, 'type'): + tool_call_data['type'] = tool_call.type + if hasattr(tool_call, 'function'): + tool_call_data['function'] = {} + if hasattr(tool_call.function, 'name'): + tool_call_data['function']['name'] = tool_call.function.name + if hasattr(tool_call.function, 'arguments'): + tool_call_data['function']['arguments'] = tool_call.function.arguments + + # Yield the chunk data + yield { + "type": "tool_call_chunk", + "tool_call": tool_call_data + } + + # Log the tool call chunk for debugging + logger.debug(f"Yielded native tool call chunk: {tool_call_data}") + if not hasattr(tool_call, 'function'): continue @@ -988,9 +1019,9 @@ class ResponseProcessor: } await self.add_message( thread_id=thread_id, - type=result_role if result_role == "assistant" else "user", + type="tool", content=result_message, - is_llm_message=(result_role == "assistant") + is_llm_message=True ) except Exception as e: logger.error(f"Error adding tool result: {str(e)}", exc_info=True) @@ -1002,7 +1033,7 @@ class ResponseProcessor: } await self.add_message( thread_id=thread_id, - type="user", + type="tool", content=fallback_message, is_llm_message=True ) diff --git a/backend/agentpress/thread_manager.py b/backend/agentpress/thread_manager.py index 85e90a7f..6952eb42 100644 --- a/backend/agentpress/thread_manager.py +++ b/backend/agentpress/thread_manager.py @@ -187,14 +187,14 @@ class ThreadManager: logger.debug(f"Processor config: XML={processor_config.xml_tool_calling}, Native={processor_config.native_tool_calling}, " f"Execute tools={processor_config.execute_tools}, Strategy={processor_config.tool_execution_strategy}") - # Check if native_tool_calling is enabled and throw an error if it is - if processor_config.native_tool_calling: - error_message = "Native tool calling is not supported in this version" - logger.error(error_message) - return { - "status": "error", - "message": error_message - } + # # Check if native_tool_calling is enabled and throw an error if it is + # if processor_config.native_tool_calling: + # error_message = "Native tool calling is not supported in this version" + # logger.error(error_message) + # return { + # "status": "error", + # "message": error_message + # } # 4. Prepare tools for LLM call openapi_tool_schemas = None