|
12 | 12 | )
|
13 | 13 | from openhands.events.event import Event, EventSource, FileEditSource, FileReadSource
|
14 | 14 | from openhands.events.observation import CmdOutputObservation
|
15 |
| -from openhands.events.observation.agent import RecallObservation, RecallType |
16 | 15 | from openhands.events.observation.browse import BrowserOutputObservation
|
17 | 16 | from openhands.events.observation.commands import (
|
18 | 17 | CmdOutputMetadata,
|
|
24 | 23 | from openhands.events.observation.reject import UserRejectObservation
|
25 | 24 | from openhands.events.tool import ToolCallMetadata
|
26 | 25 | from openhands.memory.conversation_memory import ConversationMemory
|
27 |
| -from openhands.utils.prompt import PromptManager, RepositoryInfo, RuntimeInfo |
| 26 | +from openhands.utils.prompt import PromptManager |
28 | 27 |
|
29 | 28 |
|
30 | 29 | @pytest.fixture
|
@@ -468,334 +467,3 @@ def test_apply_prompt_caching(conversation_memory):
|
468 | 467 | assert messages[1].content[0].cache_prompt is False
|
469 | 468 | assert messages[2].content[0].cache_prompt is False
|
470 | 469 | assert messages[3].content[0].cache_prompt is True
|
471 |
| - |
472 |
| - |
473 |
| -def test_process_events_with_environment_info_recall_observation(conversation_memory): |
474 |
| - """Test processing a RecallObservation with ENVIRONMENT_INFO type.""" |
475 |
| - obs = RecallObservation( |
476 |
| - recall_type=RecallType.ENVIRONMENT_INFO, |
477 |
| - repo_name='test-repo', |
478 |
| - repo_directory='/path/to/repo', |
479 |
| - repo_instructions='# Test Repository\nThis is a test repository.', |
480 |
| - runtime_hosts={'localhost': 8080}, |
481 |
| - content='Recalled environment info', |
482 |
| - ) |
483 |
| - |
484 |
| - initial_messages = [ |
485 |
| - Message(role='system', content=[TextContent(text='System message')]) |
486 |
| - ] |
487 |
| - |
488 |
| - messages = conversation_memory.process_events( |
489 |
| - condensed_history=[obs], |
490 |
| - initial_messages=initial_messages, |
491 |
| - max_message_chars=None, |
492 |
| - vision_is_active=False, |
493 |
| - ) |
494 |
| - |
495 |
| - assert len(messages) == 2 |
496 |
| - result = messages[1] |
497 |
| - assert result.role == 'user' |
498 |
| - assert len(result.content) == 1 |
499 |
| - assert isinstance(result.content[0], TextContent) |
500 |
| - assert result.content[0].text == 'Formatted repository and runtime info' |
501 |
| - |
502 |
| - # Verify the prompt_manager was called with the correct parameters |
503 |
| - conversation_memory.prompt_manager.build_additional_info.assert_called_once() |
504 |
| - call_args = conversation_memory.prompt_manager.build_additional_info.call_args[1] |
505 |
| - assert isinstance(call_args['repository_info'], RepositoryInfo) |
506 |
| - assert call_args['repository_info'].repo_name == 'test-repo' |
507 |
| - assert call_args['repository_info'].repo_directory == '/path/to/repo' |
508 |
| - assert isinstance(call_args['runtime_info'], RuntimeInfo) |
509 |
| - assert call_args['runtime_info'].available_hosts == {'localhost': 8080} |
510 |
| - assert ( |
511 |
| - call_args['repo_instructions'] |
512 |
| - == '# Test Repository\nThis is a test repository.' |
513 |
| - ) |
514 |
| - |
515 |
| - |
516 |
| -def test_process_events_with_knowledge_microagent_recall_observation( |
517 |
| - conversation_memory, |
518 |
| -): |
519 |
| - """Test processing a RecallObservation with KNOWLEDGE_MICROAGENT type.""" |
520 |
| - microagent_knowledge = [ |
521 |
| - { |
522 |
| - 'agent_name': 'test_agent', |
523 |
| - 'trigger_word': 'test', |
524 |
| - 'content': 'This is test agent content', |
525 |
| - }, |
526 |
| - { |
527 |
| - 'agent_name': 'another_agent', |
528 |
| - 'trigger_word': 'another', |
529 |
| - 'content': 'This is another agent content', |
530 |
| - }, |
531 |
| - { |
532 |
| - 'agent_name': 'disabled_agent', |
533 |
| - 'trigger_word': 'disabled', |
534 |
| - 'content': 'This is disabled agent content', |
535 |
| - }, |
536 |
| - ] |
537 |
| - |
538 |
| - obs = RecallObservation( |
539 |
| - recall_type=RecallType.KNOWLEDGE_MICROAGENT, |
540 |
| - microagent_knowledge=microagent_knowledge, |
541 |
| - content='Recalled knowledge from microagents', |
542 |
| - ) |
543 |
| - |
544 |
| - initial_messages = [ |
545 |
| - Message(role='system', content=[TextContent(text='System message')]) |
546 |
| - ] |
547 |
| - |
548 |
| - messages = conversation_memory.process_events( |
549 |
| - condensed_history=[obs], |
550 |
| - initial_messages=initial_messages, |
551 |
| - max_message_chars=None, |
552 |
| - vision_is_active=False, |
553 |
| - ) |
554 |
| - |
555 |
| - assert len(messages) == 2 |
556 |
| - result = messages[1] |
557 |
| - assert result.role == 'user' |
558 |
| - assert len(result.content) == 1 |
559 |
| - assert isinstance(result.content[0], TextContent) |
560 |
| - # Verify that disabled_agent is filtered out and enabled agents are included |
561 |
| - assert 'This is test agent content' in result.content[0].text |
562 |
| - assert 'This is another agent content' in result.content[0].text |
563 |
| - assert 'This is disabled agent content' not in result.content[0].text |
564 |
| - |
565 |
| - # Verify the prompt_manager was called with the correct parameters |
566 |
| - conversation_memory.prompt_manager.build_microagent_info.assert_called_once() |
567 |
| - call_args = conversation_memory.prompt_manager.build_microagent_info.call_args[1] |
568 |
| - |
569 |
| - # Check that disabled_agent was filtered out |
570 |
| - triggered_agents = call_args['triggered_agents'] |
571 |
| - assert len(triggered_agents) == 2 |
572 |
| - agent_names = [agent['agent_name'] for agent in triggered_agents] |
573 |
| - assert 'test_agent' in agent_names |
574 |
| - assert 'another_agent' in agent_names |
575 |
| - assert 'disabled_agent' not in agent_names |
576 |
| - |
577 |
| - |
578 |
| -def test_process_events_with_recall_observation_extensions_disabled( |
579 |
| - agent_config, conversation_memory |
580 |
| -): |
581 |
| - """Test processing a RecallObservation when prompt extensions are disabled.""" |
582 |
| - # Modify the agent config to disable prompt extensions |
583 |
| - agent_config.enable_prompt_extensions = False |
584 |
| - |
585 |
| - obs = RecallObservation( |
586 |
| - recall_type=RecallType.ENVIRONMENT_INFO, |
587 |
| - repo_name='test-repo', |
588 |
| - repo_directory='/path/to/repo', |
589 |
| - content='Recalled environment info', |
590 |
| - ) |
591 |
| - |
592 |
| - initial_messages = [ |
593 |
| - Message(role='system', content=[TextContent(text='System message')]) |
594 |
| - ] |
595 |
| - |
596 |
| - messages = conversation_memory.process_events( |
597 |
| - condensed_history=[obs], |
598 |
| - initial_messages=initial_messages, |
599 |
| - max_message_chars=None, |
600 |
| - vision_is_active=False, |
601 |
| - ) |
602 |
| - |
603 |
| - # When prompt extensions are disabled, the RecallObservation should be ignored |
604 |
| - assert len(messages) == 1 # Only the initial system message |
605 |
| - assert messages[0].role == 'system' |
606 |
| - |
607 |
| - # Verify the prompt_manager was not called |
608 |
| - conversation_memory.prompt_manager.build_additional_info.assert_not_called() |
609 |
| - conversation_memory.prompt_manager.build_microagent_info.assert_not_called() |
610 |
| - |
611 |
| - |
612 |
| -def test_process_events_with_empty_microagent_knowledge(conversation_memory): |
613 |
| - """Test processing a RecallObservation with empty microagent knowledge.""" |
614 |
| - obs = RecallObservation( |
615 |
| - recall_type=RecallType.KNOWLEDGE_MICROAGENT, |
616 |
| - microagent_knowledge=[], |
617 |
| - content='Recalled knowledge from microagents', |
618 |
| - ) |
619 |
| - |
620 |
| - initial_messages = [ |
621 |
| - Message(role='system', content=[TextContent(text='System message')]) |
622 |
| - ] |
623 |
| - |
624 |
| - messages = conversation_memory.process_events( |
625 |
| - condensed_history=[obs], |
626 |
| - initial_messages=initial_messages, |
627 |
| - max_message_chars=None, |
628 |
| - vision_is_active=False, |
629 |
| - ) |
630 |
| - |
631 |
| - # The implementation returns an empty string but still creates a message |
632 |
| - assert len(messages) == 2 |
633 |
| - assert messages[0].role == 'system' |
634 |
| - assert messages[1].role == 'user' |
635 |
| - assert len(messages[1].content) == 1 |
636 |
| - assert isinstance(messages[1].content[0], TextContent) |
637 |
| - assert messages[1].content[0].text == '' |
638 |
| - |
639 |
| - # When there are no triggered agents, build_microagent_info is not called |
640 |
| - conversation_memory.prompt_manager.build_microagent_info.assert_not_called() |
641 |
| - |
642 |
| - |
643 |
| -def test_process_events_with_recall_observation_deduplication(conversation_memory): |
644 |
| - """Test that RecallObservations are properly deduplicated based on agent_name.""" |
645 |
| - # Create a sequence of RecallObservations with overlapping agents |
646 |
| - obs1 = RecallObservation( |
647 |
| - recall_type=RecallType.KNOWLEDGE_MICROAGENT, |
648 |
| - microagent_knowledge=[ |
649 |
| - { |
650 |
| - 'agent_name': 'python_agent', |
651 |
| - 'trigger_word': 'python', |
652 |
| - 'content': 'Python best practices v1', |
653 |
| - }, |
654 |
| - { |
655 |
| - 'agent_name': 'git_agent', |
656 |
| - 'trigger_word': 'git', |
657 |
| - 'content': 'Git best practices v1', |
658 |
| - }, |
659 |
| - { |
660 |
| - 'agent_name': 'image_agent', |
661 |
| - 'trigger_word': 'image', |
662 |
| - 'content': 'Image best practices v1', |
663 |
| - }, |
664 |
| - ], |
665 |
| - content='First recall', |
666 |
| - ) |
667 |
| - |
668 |
| - obs2 = RecallObservation( |
669 |
| - recall_type=RecallType.KNOWLEDGE_MICROAGENT, |
670 |
| - microagent_knowledge=[ |
671 |
| - { |
672 |
| - 'agent_name': 'python_agent', |
673 |
| - 'trigger_word': 'python', |
674 |
| - 'content': 'Python best practices v2', |
675 |
| - }, |
676 |
| - ], |
677 |
| - content='Second recall', |
678 |
| - ) |
679 |
| - |
680 |
| - obs3 = RecallObservation( |
681 |
| - recall_type=RecallType.KNOWLEDGE_MICROAGENT, |
682 |
| - microagent_knowledge=[ |
683 |
| - { |
684 |
| - 'agent_name': 'git_agent', |
685 |
| - 'trigger_word': 'git', |
686 |
| - 'content': 'Git best practices v3', |
687 |
| - }, |
688 |
| - ], |
689 |
| - content='Third recall', |
690 |
| - ) |
691 |
| - |
692 |
| - initial_messages = [ |
693 |
| - Message(role='system', content=[TextContent(text='System message')]) |
694 |
| - ] |
695 |
| - |
696 |
| - messages = conversation_memory.process_events( |
697 |
| - condensed_history=[obs1, obs2, obs3], |
698 |
| - initial_messages=initial_messages, |
699 |
| - max_message_chars=None, |
700 |
| - vision_is_active=False, |
701 |
| - ) |
702 |
| - |
703 |
| - # Verify that only the most recent content for each agent is included |
704 |
| - assert len(messages) == 4 # system + 3 recalls |
705 |
| - recall_messages = messages[1:] # Skip system message |
706 |
| - |
707 |
| - # First recall should only include image_agent (git_agent and python_agent appears later) |
708 |
| - assert 'Image best practices v1' in recall_messages[0].content[0].text |
709 |
| - assert 'Git best practices v1' not in recall_messages[0].content[0].text |
710 |
| - assert 'Python best practices v1' not in recall_messages[0].content[0].text |
711 |
| - |
712 |
| - # Second recall should include python_agent (it's the most recent for that agent) |
713 |
| - assert 'Python best practices v2' in recall_messages[1].content[0].text |
714 |
| - |
715 |
| - # Third recall should include git_agent (it's the most recent for that agent) |
716 |
| - assert 'Git best practices v3' in recall_messages[2].content[0].text |
717 |
| - |
718 |
| - |
719 |
| -def test_process_events_with_recall_observation_deduplication_disabled_agents( |
720 |
| - conversation_memory, |
721 |
| -): |
722 |
| - """Test that disabled agents are filtered out after deduplication.""" |
723 |
| - # Create a sequence of RecallObservations with disabled agents |
724 |
| - obs1 = RecallObservation( |
725 |
| - recall_type=RecallType.KNOWLEDGE_MICROAGENT, |
726 |
| - microagent_knowledge=[ |
727 |
| - { |
728 |
| - 'agent_name': 'disabled_agent', |
729 |
| - 'trigger_word': 'disabled', |
730 |
| - 'content': 'Disabled agent content', |
731 |
| - }, |
732 |
| - { |
733 |
| - 'agent_name': 'enabled_agent', |
734 |
| - 'trigger_word': 'enabled', |
735 |
| - 'content': 'Enabled agent content v1', |
736 |
| - }, |
737 |
| - ], |
738 |
| - content='First recall', |
739 |
| - ) |
740 |
| - |
741 |
| - obs2 = RecallObservation( |
742 |
| - recall_type=RecallType.KNOWLEDGE_MICROAGENT, |
743 |
| - microagent_knowledge=[ |
744 |
| - { |
745 |
| - 'agent_name': 'enabled_agent', |
746 |
| - 'trigger_word': 'enabled', |
747 |
| - 'content': 'Enabled agent content v2', |
748 |
| - }, |
749 |
| - ], |
750 |
| - content='Second recall', |
751 |
| - ) |
752 |
| - |
753 |
| - initial_messages = [ |
754 |
| - Message(role='system', content=[TextContent(text='System message')]) |
755 |
| - ] |
756 |
| - |
757 |
| - messages = conversation_memory.process_events( |
758 |
| - condensed_history=[obs1, obs2], |
759 |
| - initial_messages=initial_messages, |
760 |
| - max_message_chars=None, |
761 |
| - vision_is_active=False, |
762 |
| - ) |
763 |
| - |
764 |
| - # Verify that disabled agents are filtered out and only the most recent content for enabled agents is included |
765 |
| - assert len(messages) == 3 # system + 2 recalls |
766 |
| - recall_messages = messages[1:] # Skip system message |
767 |
| - |
768 |
| - # First recall should not include disabled_agent |
769 |
| - assert 'Disabled agent content' not in recall_messages[0].content[0].text |
770 |
| - assert ( |
771 |
| - 'Enabled agent content v1' not in recall_messages[0].content[0].text |
772 |
| - ) # Because it appears later |
773 |
| - |
774 |
| - # Second recall should include enabled_agent (it's the most recent) |
775 |
| - assert 'Enabled agent content v2' in recall_messages[1].content[0].text |
776 |
| - |
777 |
| - |
778 |
| -def test_process_events_with_recall_observation_deduplication_empty( |
779 |
| - conversation_memory, |
780 |
| -): |
781 |
| - """Test that empty RecallObservations are handled correctly.""" |
782 |
| - obs = RecallObservation( |
783 |
| - recall_type=RecallType.KNOWLEDGE_MICROAGENT, |
784 |
| - microagent_knowledge=[], |
785 |
| - content='Empty recall', |
786 |
| - ) |
787 |
| - |
788 |
| - initial_messages = [ |
789 |
| - Message(role='system', content=[TextContent(text='System message')]) |
790 |
| - ] |
791 |
| - |
792 |
| - messages = conversation_memory.process_events( |
793 |
| - condensed_history=[obs], |
794 |
| - initial_messages=initial_messages, |
795 |
| - max_message_chars=None, |
796 |
| - vision_is_active=False, |
797 |
| - ) |
798 |
| - |
799 |
| - # Verify that empty RecallObservations are handled gracefully |
800 |
| - assert len(messages) == 2 # system + empty recall |
801 |
| - assert messages[1].content[0].text == '' # Empty string for empty recall |
0 commit comments