mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-04-29 03:00:45 -04:00
Compare commits
5 Commits
feat/gemin
...
feature/ad
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f486028530 | ||
|
|
77f2d9b0d4 | ||
|
|
396e4b07f3 | ||
|
|
7c3c5a2e95 | ||
|
|
09a4c1604e |
@@ -1,13 +1,16 @@
|
||||
import os
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from browsergym.core.action.highlevel import HighLevelActionSet
|
||||
from browsergym.utils.obs import flatten_axtree_to_str
|
||||
|
||||
from openhands.agenthub.browsing_agent.response_parser import BrowsingResponseParser
|
||||
from openhands.controller.agent import Agent
|
||||
from openhands.controller.state.state import State
|
||||
from openhands.core.config import AgentConfig
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
|
||||
# Import Agent for both type checking and runtime
|
||||
from openhands.controller.agent import Agent
|
||||
from openhands.core.message import Message, TextContent
|
||||
from openhands.events.action import (
|
||||
Action,
|
||||
@@ -91,6 +94,8 @@ In order to accomplish my goal I need to click on the button with bid 12
|
||||
return prompt
|
||||
|
||||
|
||||
|
||||
|
||||
class BrowsingAgent(Agent):
|
||||
VERSION = '1.0'
|
||||
"""
|
||||
|
||||
6
openhands/core/__init__.py
Normal file
6
openhands/core/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
"""Core components of the OpenHands system."""
|
||||
|
||||
from openhands.core.conversation import Conversation
|
||||
from openhands.core.openhands import OpenHands
|
||||
|
||||
__all__ = ['Conversation', 'OpenHands']
|
||||
32
openhands/core/conversation.py
Normal file
32
openhands/core/conversation.py
Normal file
@@ -0,0 +1,32 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from openhands.controller.agent_controller import AgentController
|
||||
from openhands.events.stream import EventStream
|
||||
from openhands.llm.llm import LLM
|
||||
from openhands.runtime.base import Runtime
|
||||
|
||||
|
||||
@dataclass
|
||||
class Conversation:
|
||||
"""Main interface for conversations in OpenHands.
|
||||
|
||||
This class serves as a container for all the components needed for a conversation
|
||||
between a user and an OpenHands agent.
|
||||
|
||||
Attributes:
|
||||
conversation_id: Unique identifier for the conversation
|
||||
runtime: Runtime environment where the agent operates
|
||||
llm: Language model used by the agent
|
||||
event_stream: Stream of events (actions and observations) in the conversation
|
||||
agent_controller: Controller that manages the agent's behavior and state
|
||||
"""
|
||||
|
||||
conversation_id: str
|
||||
runtime: 'Runtime'
|
||||
llm: 'LLM'
|
||||
event_stream: 'EventStream'
|
||||
agent_controller: 'AgentController'
|
||||
93
openhands/core/openhands.py
Normal file
93
openhands/core/openhands.py
Normal file
@@ -0,0 +1,93 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from openhands.core.config.openhands_config import OpenHandsConfig
|
||||
from openhands.core.setup import create_agent, create_runtime
|
||||
from openhands.events.stream import EventStream
|
||||
from openhands.llm.llm import LLM
|
||||
from openhands.storage import get_file_store
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from openhands.core.conversation import Conversation
|
||||
|
||||
|
||||
class OpenHands:
|
||||
"""Main class for creating and managing OpenHands conversations.
|
||||
|
||||
This class is responsible for creating new conversations based on the provided
|
||||
configuration. It serves as the primary entry point for interacting with the
|
||||
OpenHands system.
|
||||
|
||||
Attributes:
|
||||
config: Configuration for the OpenHands system
|
||||
"""
|
||||
|
||||
def __init__(self, config: OpenHandsConfig):
|
||||
"""Initialize the OpenHands instance with the provided configuration.
|
||||
|
||||
Args:
|
||||
config: Configuration for the OpenHands system
|
||||
"""
|
||||
self.config = config
|
||||
|
||||
def create_conversation(self, conversation_id: str | None = None) -> 'Conversation':
|
||||
"""Create a new conversation with all necessary components.
|
||||
|
||||
This method creates a Runtime, LLM, EventStream, and AgentController according
|
||||
to the configuration provided to the constructor, and returns a Conversation
|
||||
object containing all these components.
|
||||
|
||||
Args:
|
||||
conversation_id: Optional identifier for the conversation. If not provided,
|
||||
a unique ID will be generated.
|
||||
|
||||
Returns:
|
||||
A Conversation object containing all the components needed for interaction.
|
||||
"""
|
||||
# Create a runtime based on the configuration
|
||||
runtime = create_runtime(self.config)
|
||||
|
||||
# Create a file store
|
||||
file_store = get_file_store(self.config.file_store, self.config.file_store_path)
|
||||
|
||||
# Create an event stream for the conversation
|
||||
# Generate a unique ID if none is provided
|
||||
sid = (
|
||||
conversation_id
|
||||
if conversation_id is not None
|
||||
else f'conversation-{uuid.uuid4()}'
|
||||
)
|
||||
event_stream = EventStream(sid=sid, file_store=file_store)
|
||||
|
||||
# Get the default LLM configuration and create an LLM instance
|
||||
llm_config = self.config.get_llm_config()
|
||||
llm = LLM(llm_config)
|
||||
|
||||
# Create an agent using the factory function
|
||||
agent = create_agent(self.config)
|
||||
|
||||
# Create an agent controller
|
||||
from openhands.controller.agent_controller import AgentController
|
||||
|
||||
agent_controller = AgentController(
|
||||
agent=agent,
|
||||
event_stream=event_stream,
|
||||
max_iterations=self.config.max_iterations,
|
||||
max_budget_per_task=self.config.max_budget_per_task,
|
||||
agent_to_llm_config=self.config.get_agent_to_llm_config_map(),
|
||||
agent_configs=self.config.get_agent_configs(),
|
||||
sid=conversation_id,
|
||||
)
|
||||
|
||||
# Create and return a Conversation object
|
||||
from openhands.core.conversation import Conversation
|
||||
|
||||
return Conversation(
|
||||
conversation_id=conversation_id or event_stream.sid,
|
||||
runtime=runtime,
|
||||
llm=llm,
|
||||
event_stream=event_stream,
|
||||
agent_controller=agent_controller,
|
||||
)
|
||||
@@ -1,3 +1,4 @@
|
||||
import logging
|
||||
from abc import ABC, abstractmethod
|
||||
from enum import Enum
|
||||
from typing import Any, Protocol
|
||||
@@ -6,9 +7,11 @@ from httpx import AsyncClient, HTTPError, HTTPStatusError
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
from pydantic import BaseModel, SecretStr
|
||||
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.server.types import AppMode
|
||||
|
||||
# Use standard logging instead of importing from core.logger
|
||||
logger = logging.getLogger('openhands')
|
||||
|
||||
|
||||
class ProviderType(Enum):
|
||||
GITHUB = 'github'
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import urllib.parse
|
||||
from typing import Union
|
||||
@@ -6,9 +7,11 @@ from typing import Union
|
||||
import httpx
|
||||
from requests.exceptions import RequestException
|
||||
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.storage.files import FileStore
|
||||
|
||||
# Use standard logging instead of importing from core.logger
|
||||
logger = logging.getLogger('openhands')
|
||||
|
||||
|
||||
class HTTPFileStore(FileStore):
|
||||
"""
|
||||
|
||||
141
tests/unit/test_core_conversation.py
Normal file
141
tests/unit/test_core_conversation.py
Normal file
@@ -0,0 +1,141 @@
|
||||
import unittest
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from openhands.core import Conversation, OpenHands
|
||||
from openhands.core.config.openhands_config import OpenHandsConfig
|
||||
|
||||
|
||||
class TestConversation(unittest.TestCase):
|
||||
def test_conversation_dataclass(self):
|
||||
"""Test that the Conversation class is a dataclass with the expected fields."""
|
||||
# Create mock objects for the constructor
|
||||
conversation_id = 'test-conversation-id'
|
||||
runtime = MagicMock()
|
||||
llm = MagicMock()
|
||||
event_stream = MagicMock()
|
||||
agent_controller = MagicMock()
|
||||
|
||||
# Create a Conversation instance
|
||||
conversation = Conversation(
|
||||
conversation_id=conversation_id,
|
||||
runtime=runtime,
|
||||
llm=llm,
|
||||
event_stream=event_stream,
|
||||
agent_controller=agent_controller,
|
||||
)
|
||||
|
||||
# Verify that the fields are set correctly
|
||||
self.assertEqual(conversation.conversation_id, conversation_id)
|
||||
self.assertEqual(conversation.runtime, runtime)
|
||||
self.assertEqual(conversation.llm, llm)
|
||||
self.assertEqual(conversation.event_stream, event_stream)
|
||||
self.assertEqual(conversation.agent_controller, agent_controller)
|
||||
|
||||
|
||||
class TestOpenHands(unittest.TestCase):
|
||||
@patch('openhands.core.openhands.create_runtime')
|
||||
@patch('openhands.core.openhands.EventStream')
|
||||
@patch('openhands.core.openhands.LLM')
|
||||
@patch('openhands.core.openhands.create_agent')
|
||||
@patch('openhands.controller.agent_controller.AgentController')
|
||||
def test_create_conversation(
|
||||
self,
|
||||
mock_agent_controller,
|
||||
mock_create_agent,
|
||||
mock_llm,
|
||||
mock_event_stream,
|
||||
mock_create_runtime,
|
||||
):
|
||||
"""Test that the create_conversation method creates all the expected components."""
|
||||
# Create mock objects
|
||||
config = OpenHandsConfig()
|
||||
mock_runtime = MagicMock()
|
||||
mock_create_runtime.return_value = mock_runtime
|
||||
|
||||
mock_event_stream_instance = MagicMock()
|
||||
mock_event_stream_instance.sid = 'generated-id'
|
||||
mock_event_stream.return_value = mock_event_stream_instance
|
||||
|
||||
mock_llm_instance = MagicMock()
|
||||
mock_llm.return_value = mock_llm_instance
|
||||
|
||||
mock_agent_instance = MagicMock()
|
||||
mock_create_agent.return_value = mock_agent_instance
|
||||
|
||||
mock_agent_controller_instance = MagicMock()
|
||||
mock_agent_controller.return_value = mock_agent_controller_instance
|
||||
|
||||
# Create an OpenHands instance and call create_conversation
|
||||
openhands = OpenHands(config)
|
||||
conversation = openhands.create_conversation()
|
||||
|
||||
# Verify that all the expected methods were called
|
||||
mock_create_runtime.assert_called_with(config)
|
||||
mock_event_stream.assert_called_once()
|
||||
mock_llm.assert_called_once()
|
||||
mock_create_agent.assert_called_once()
|
||||
mock_agent_controller.assert_called_once()
|
||||
|
||||
# Verify that the returned Conversation has the expected attributes
|
||||
self.assertEqual(conversation.conversation_id, 'generated-id')
|
||||
self.assertEqual(conversation.runtime, mock_runtime)
|
||||
self.assertEqual(conversation.llm, mock_llm_instance)
|
||||
self.assertEqual(conversation.event_stream, mock_event_stream_instance)
|
||||
self.assertEqual(conversation.agent_controller, mock_agent_controller_instance)
|
||||
|
||||
@patch('openhands.core.openhands.create_runtime')
|
||||
@patch('openhands.core.openhands.EventStream')
|
||||
@patch('openhands.core.openhands.LLM')
|
||||
@patch('openhands.core.openhands.create_agent')
|
||||
@patch('openhands.controller.agent_controller.AgentController')
|
||||
def test_create_conversation_with_id(
|
||||
self,
|
||||
mock_agent_controller,
|
||||
mock_create_agent,
|
||||
mock_llm,
|
||||
mock_event_stream,
|
||||
mock_create_runtime,
|
||||
):
|
||||
"""Test that the create_conversation method uses the provided conversation_id."""
|
||||
# Create mock objects
|
||||
config = OpenHandsConfig()
|
||||
conversation_id = 'custom-conversation-id'
|
||||
|
||||
mock_runtime = MagicMock()
|
||||
mock_create_runtime.return_value = mock_runtime
|
||||
|
||||
mock_event_stream_instance = MagicMock()
|
||||
mock_event_stream.return_value = mock_event_stream_instance
|
||||
|
||||
mock_llm_instance = MagicMock()
|
||||
mock_llm.return_value = mock_llm_instance
|
||||
|
||||
mock_agent_instance = MagicMock()
|
||||
mock_create_agent.return_value = mock_agent_instance
|
||||
|
||||
mock_agent_controller_instance = MagicMock()
|
||||
mock_agent_controller.return_value = mock_agent_controller_instance
|
||||
|
||||
# Create an OpenHands instance and call create_conversation with a custom ID
|
||||
openhands = OpenHands(config)
|
||||
conversation = openhands.create_conversation(conversation_id=conversation_id)
|
||||
|
||||
# Verify that the EventStream and AgentController were created with the custom ID
|
||||
# The EventStream is called with both sid and file_store
|
||||
self.assertEqual(mock_event_stream.call_args.kwargs['sid'], conversation_id)
|
||||
mock_agent_controller.assert_called_once_with(
|
||||
agent=mock_agent_instance,
|
||||
event_stream=mock_event_stream_instance,
|
||||
max_iterations=config.max_iterations,
|
||||
max_budget_per_task=config.max_budget_per_task,
|
||||
agent_to_llm_config=config.get_agent_to_llm_config_map(),
|
||||
agent_configs=config.get_agent_configs(),
|
||||
sid=conversation_id,
|
||||
)
|
||||
|
||||
# Verify that the returned Conversation has the custom ID
|
||||
self.assertEqual(conversation.conversation_id, conversation_id)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user