|  | @@ -1,5 +1,6 @@
 | 
	
		
			
				|  |  |  import json
 | 
	
		
			
				|  |  |  import os
 | 
	
		
			
				|  |  | +from typing import Optional
 | 
	
		
			
				|  |  |  from unittest.mock import MagicMock
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  import pytest
 | 
	
	
		
			
				|  | @@ -7,6 +8,7 @@ import pytest
 | 
	
		
			
				|  |  |  from core.app.entities.app_invoke_entities import InvokeFrom, ModelConfigWithCredentialsEntity
 | 
	
		
			
				|  |  |  from core.entities.provider_configuration import ProviderConfiguration, ProviderModelBundle
 | 
	
		
			
				|  |  |  from core.entities.provider_entities import CustomConfiguration, CustomProviderConfiguration, SystemConfiguration
 | 
	
		
			
				|  |  | +from core.memory.token_buffer_memory import TokenBufferMemory
 | 
	
		
			
				|  |  |  from core.model_manager import ModelInstance
 | 
	
		
			
				|  |  |  from core.model_runtime.entities.model_entities import ModelType
 | 
	
		
			
				|  |  |  from core.model_runtime.model_providers.model_provider_factory import ModelProviderFactory
 | 
	
	
		
			
				|  | @@ -61,6 +63,16 @@ def get_mocked_fetch_model_config(
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      return MagicMock(return_value=(model_instance, model_config))
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +def get_mocked_fetch_memory(memory_text: str):
 | 
	
		
			
				|  |  | +    class MemoryMock:
 | 
	
		
			
				|  |  | +        def get_history_prompt_text(self, human_prefix: str = "Human",
 | 
	
		
			
				|  |  | +                                ai_prefix: str = "Assistant",
 | 
	
		
			
				|  |  | +                                max_token_limit: int = 2000,
 | 
	
		
			
				|  |  | +                                message_limit: Optional[int] = None):
 | 
	
		
			
				|  |  | +            return memory_text
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    return MagicMock(return_value=MemoryMock())
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  @pytest.mark.parametrize('setup_openai_mock', [['chat']], indirect=True)
 | 
	
		
			
				|  |  |  def test_function_calling_parameter_extractor(setup_openai_mock):
 | 
	
		
			
				|  |  |      """
 | 
	
	
		
			
				|  | @@ -354,4 +366,83 @@ def test_extract_json_response():
 | 
	
		
			
				|  |  |          hello world.                          
 | 
	
		
			
				|  |  |      """)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    assert result['location'] == 'kawaii'
 | 
	
		
			
				|  |  | +    assert result['location'] == 'kawaii'
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +@pytest.mark.parametrize('setup_anthropic_mock', [['none']], indirect=True)
 | 
	
		
			
				|  |  | +def test_chat_parameter_extractor_with_memory(setup_anthropic_mock):
 | 
	
		
			
				|  |  | +    """
 | 
	
		
			
				|  |  | +    Test chat parameter extractor with memory.
 | 
	
		
			
				|  |  | +    """
 | 
	
		
			
				|  |  | +    node = ParameterExtractorNode(
 | 
	
		
			
				|  |  | +        tenant_id='1',
 | 
	
		
			
				|  |  | +        app_id='1',
 | 
	
		
			
				|  |  | +        workflow_id='1',
 | 
	
		
			
				|  |  | +        user_id='1',
 | 
	
		
			
				|  |  | +        invoke_from=InvokeFrom.WEB_APP,
 | 
	
		
			
				|  |  | +        user_from=UserFrom.ACCOUNT,
 | 
	
		
			
				|  |  | +        config={
 | 
	
		
			
				|  |  | +            'id': 'llm',
 | 
	
		
			
				|  |  | +            'data': {
 | 
	
		
			
				|  |  | +                'title': '123',
 | 
	
		
			
				|  |  | +                'type': 'parameter-extractor',
 | 
	
		
			
				|  |  | +                'model': {
 | 
	
		
			
				|  |  | +                    'provider': 'anthropic',
 | 
	
		
			
				|  |  | +                    'name': 'claude-2',
 | 
	
		
			
				|  |  | +                    'mode': 'chat',
 | 
	
		
			
				|  |  | +                    'completion_params': {}
 | 
	
		
			
				|  |  | +                },
 | 
	
		
			
				|  |  | +                'query': ['sys', 'query'],
 | 
	
		
			
				|  |  | +                'parameters': [{
 | 
	
		
			
				|  |  | +                    'name': 'location',
 | 
	
		
			
				|  |  | +                    'type': 'string',
 | 
	
		
			
				|  |  | +                    'description': 'location',
 | 
	
		
			
				|  |  | +                    'required': True
 | 
	
		
			
				|  |  | +                }],
 | 
	
		
			
				|  |  | +                'reasoning_mode': 'prompt',
 | 
	
		
			
				|  |  | +                'instruction': '',
 | 
	
		
			
				|  |  | +                'memory': {
 | 
	
		
			
				|  |  | +                    'window': {
 | 
	
		
			
				|  |  | +                        'enabled': True,
 | 
	
		
			
				|  |  | +                        'size': 50
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +                },
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    )
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    node._fetch_model_config = get_mocked_fetch_model_config(
 | 
	
		
			
				|  |  | +        provider='anthropic', model='claude-2', mode='chat', credentials={
 | 
	
		
			
				|  |  | +            'anthropic_api_key': os.environ.get('ANTHROPIC_API_KEY')
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    )
 | 
	
		
			
				|  |  | +    node._fetch_memory = get_mocked_fetch_memory('customized memory')
 | 
	
		
			
				|  |  | +    db.session.close = MagicMock()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    # construct variable pool
 | 
	
		
			
				|  |  | +    pool = VariablePool(system_variables={
 | 
	
		
			
				|  |  | +        SystemVariable.QUERY: 'what\'s the weather in SF',
 | 
	
		
			
				|  |  | +        SystemVariable.FILES: [],
 | 
	
		
			
				|  |  | +        SystemVariable.CONVERSATION_ID: 'abababa',
 | 
	
		
			
				|  |  | +        SystemVariable.USER_ID: 'aaa'
 | 
	
		
			
				|  |  | +    }, user_inputs={})
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    result = node.run(pool)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    assert result.status == WorkflowNodeExecutionStatus.SUCCEEDED
 | 
	
		
			
				|  |  | +    assert result.outputs.get('location') == ''
 | 
	
		
			
				|  |  | +    assert result.outputs.get('__reason') == 'Failed to extract result from function call or text response, using empty result.'
 | 
	
		
			
				|  |  | +    prompts = result.process_data.get('prompts')
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    latest_role = None
 | 
	
		
			
				|  |  | +    for prompt in prompts:
 | 
	
		
			
				|  |  | +        if prompt.get('role') == 'user':
 | 
	
		
			
				|  |  | +            if '<structure>' in prompt.get('text'):
 | 
	
		
			
				|  |  | +                assert '<structure>\n{"type": "object"' in prompt.get('text')
 | 
	
		
			
				|  |  | +        elif prompt.get('role') == 'system':
 | 
	
		
			
				|  |  | +            assert 'customized memory' in prompt.get('text')
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        if latest_role is not None:
 | 
	
		
			
				|  |  | +            assert latest_role != prompt.get('role')
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        if prompt.get('role') in ['user', 'assistant']:
 | 
	
		
			
				|  |  | +            latest_role = prompt.get('role')
 |