Repository: hand-e-fr/OpenHosta (language: python)

Issue:
Dev



Discussion / hints:

Fix patch:
diff --git a/.gitignore b/.gitignore
index d3730eb..a61ab92 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,4 +12,6 @@ dist/*
 __hostacache__/
 .coverage
 *.html
-.pytest_cache
\ No newline at end of file
+.pytest_cache
+src/OpenHosta.egg-info
+build/
\ No newline at end of file
diff --git a/CHAGELOG.md b/CHAGELOG.md
index b5c8c87..e5db66c 100644
--- a/CHAGELOG.md
+++ b/CHAGELOG.md
@@ -4,14 +4,27 @@ All significant changes to this project will be documented in this file.
 
 ---
 
-## **v1.1**
+## **v1.1rc3**
+
+- **Fixes**
+  - `emulate` now works when emulated function is called inside another one
+  - `emulate` works now with `lru_cache` decorator from `functools` module 
+
+- **Internal**
+  - Added custom Exception classes for request and frame errors.
+  - Added a loop to find the frame in `_extend_scope`
+  - Added a Makefile for cleaning and packaging and tests
+---
+
+## **v1.1** 13/09/2024
 
 - **Fixes**
   - the `emulate` function is now decorator-resistant.
   - The function `config.set_default_model` works now w/ `config.set_default_apiKey`
   - `thought` function now accept multiple arguments
-  - `suggest` and `analytics` call now th LLM with `Model` class (`_ai_call`)
+  - `suggest` and `analytics` call now th LLM with `Model` class (`ai_call`)
   - `emulate` works now in a nested function.
+  - Added Flask compatibility
   
 - **Enhancement**
   - the `thought` function guess automatically his return type with LLM
@@ -23,6 +36,7 @@ All significant changes to this project will be documented in this file.
   - `emulate` now includes a verification output that checks and converts the output to the specified type if necessary. Supported types include **Pydantic** models, all types from the **typing** module mentioned above, as well as built-in types such as `dict`, `int`, `float`, `str`, `list`, `set`, `frozenset`, `tuple`, and `bool`.
     - The `complex` type is not supported. If you need to use complex numbers, please pass them as a `tuple` and manually convert them after processing.
   -  The return type predicted by `thought` is now attached as an attribute to the object created: `_return_type`.
+  -  Added a `_last_request` attribute to an emulated function giving access to the prompt sent to the LLM.
 
 - **Features**
   - `example` function that can add some example for a specified hosta-injected function (inside or outside it) and add it to the cache
@@ -37,6 +51,11 @@ All significant changes to this project will be documented in this file.
 - **Internal**
   - Functional tests have been added for each of the library's features
 
+- **Doc**
+  - Documentation now integrates Google Cobal link
+
+---
+
 ## **v1.0** 29/08/2024:
 
 - **Features**
diff --git a/Makefile b/Makefile
index ea3b5c8..ddceaa7 100644
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,41 @@
-PACKAGE_NAME = OpenHosta
-SRC_DIR = src/OpenHosta
-TEST_DIR = tests
-DOC_DIR = Documentation
+PACKAGE_NAME := OpenHosta
+SRC_DIR := src/OpenHosta
+TEST_DIR := tests
+DOC_DIR := doc
+API_KEY =
+TAG := "-==[OpenHosta]==- \>\>\>"
+
+ifeq ($(OS),Windows_NT)
+    DETECTED_OS := Windows
+    NULL_DEVICE := NUL
+    PYTHON := python
+    PIP := pip
+    L_SHELL := powershell -Command
+    PYTEST := & C:\\Users\\Merlin\\AppData\\Roaming\\Python\\Python312\\Scripts\\pytest.exe
+    ENV := $$env:
+    WRITE := "Write-Host
+	COLOR := -ForegroundColor DarkMagenta"
+	FIND := "Get-ChildItem -Path . -Recurse -Directory -Filter
+	RM := Remove-Item -Recurse -Force"
+	CLEAR := "clear"
+	CLEAR_DATA = type NUL > $(REDIRECT)
+	MKDIR := New-Item -ItemType Directory -Force -Path 
+else
+    DETECTED_OS := Linux
+    NULL_DEVICE := /dev/null
+    PYTHON := python3
+    PIP := pip3
+    L_SHELL :=
+    PYTEST := pytest
+    ENV := export
+    WRITE := echo
+	COLOR :=
+	FIND := find
+	RM := rm -rf
+	CLEAR := clear
+	CLEAR_DATA = > $(REDIRECT)
+	MKDIR := mkdir
+endif
 
 all: help
 
@@ -9,35 +43,75 @@ help:
 	@echo Usage:
 	@echo 	make help             Show this help message
 	@echo  	make package          Package the project for pip installation
-	@echo  	make test             Run the tests
+	@echo  	make *tests            Run the tests
 	@echo  	make format           Format the code using black
 	@echo  	make lint             Check code quality using flake8
+	@echo  	make clean            Clean not necessary files
 
-package:
-	python setup.py sdist bdist_wheel
-
-test_light:
-	python -m unittest discover $(TEST_DIR)
+install: all
+	@$(L_SHELL) $(WRITE) '$(TAG) [INSTALL]' $(COLOR)
+	@$(L_SHELL) $(WRITE) '$(TAG) Installing dependencies...' $(COLOR)
+	@$(PIP) install requirements.txt > $(NULL_DEVICE)
+	@$(PIP) install requirements-dev.txt > $(NULL_DEVICE)
+	@$(L_SHELL) $(WRITE) '$(TAG) Installing package: $(PACKAGE_NAME)...' $(COLOR)
+	@$(PIP) install . > $(NULL_DEVICE)
+	@$(L_SHELL) $(WRITE) '$(TAG) Succesfully installed $(PACKAGE_NAME) !' $(COLOR)
 
-test_medium:
-	python -m unittest discover $(TEST_DIR)
+build:
+	$(PYTHON) -m build
+	$(PYTHON) -m twine check dist/*
 
-test_complete:
-	python -m unittest discover $(TEST_DIR)
+upload: build
+	$(PYTHON) -m twine upload dist/*
 
+ftests: clean
+	@$(L_SHELL) $(WRITE) '$(TAG) Installing package: $(PACKAGE_NAME)...' $(COLOR) 
+	@$(PIP) install . > $(NULL_DEVICE)
+	@$(L_SHELL) $(WRITE) '$(TAG) Running functionnal tests...' $(COLOR) 
+	@$(L_SHELL) $(SET_ENV)
+	@-$(L_SHELL) "$(PYTEST) .\\tests\\functionnalTests\\test_mandatory.py $(ARGS)"
+	@$(L_SHELL) $(UNSET_ENV)
+	@$(L_SHELL) $(WRITE) '$(TAG) Uninstalling package: $(PACKAGE_NAME)...' $(COLOR) 
+	@$(PIP) uninstall -y OpenHosta > $(NULL_DEVICE)
+	@$(L_SHELL) $(WRITE) '$(TAG) Succesfully ran functionnal tests !' $(COLOR)
 
-format:
-	black $(SRC_DIR) $(TEST_DIR)
+utests: clean
+	@$(L_SHELL) $(WRITE) '$(TAG) Installing package: $(PACKAGE_NAME)...' $(COLOR) 
+	@$(PIP) install . > $(NULL_DEVICE)
+	@$(L_SHELL) $(WRITE) '$(TAG) Running unit tests...' $(COLOR) 
+	@$(L_SHELL) $(SET_ENV)
+	@-$(L_SHELL) "$(PYTEST) .\\tests\\unitTests\\test_exec.py $(ARGS)"
+	@$(L_SHELL) $(UNSET_ENV)
+	@$(L_SHELL) $(WRITE) '$(TAG) Uninstalling package: $(PACKAGE_NAME)...' $(COLOR) 
+	@$(PIP) uninstall -y OpenHosta > $(NULL_DEVICE)
+	@$(L_SHELL) $(WRITE) '$(TAG) Succesfully ran unit tests !' $(COLOR)
 
-lint:
-	flake8 $(SRC_DIR) $(TEST_DIR)
 
 clean:
-	rm -rf build dist *.egg-info
-	find . -name "__pycache__" -exec rm -rf {} +
-	find . -name ".openhosta" -exec rm -rf {} +
+	@$(L_SHELL) $(WRITE) '$(TAG) [CLEAN]' $(COLOR)
+	@$(L_SHELL) $(WRITE) '$(TAG) Cleaning repository...' $(COLOR)
+	@$(L_SHELL) $(FIND) '__pycache__' | $(RM)
+	@$(L_SHELL) $(FIND) '__hostacache__' | $(RM)
+	@$(L_SHELL) $(FIND) 'build' | $(RM)
+	@$(L_SHELL) $(FIND) 'dist' | $(RM)
+	@$(L_SHELL) $(FIND) 'OpenHosta.egg-info' | $(RM)
+	@$(L_SHELL) $(FIND) '.pytest_cache' | $(RM)
+	@$(L_SHELL) $(WRITE) '$(TAG) Uninstalling package: $(PACKAGE_NAME)...' $(COLOR) 
+	@$(PIP) uninstall -y OpenHosta > $(NULL_DEVICE)
+	@$(L_SHELL) $(CLEAR)
+	@$(L_SHELL) $(WRITE) '$(TAG) Succesfully cleaned repository !' $(COLOR)
+
+format: clean
+	@black $(SRC_DIR) $(ARGS)
+
+lint: format
+	@flake8 $(SRC_DIR) $(ARGS)
 
-fclean: clean
-	rm -rf .pytest_cache .mypy_cache
+re: clean
+	@$(L_SHELL) $(WRITE) '$(TAG) Uninstalling package: $(PACKAGE_NAME)...' $(COLOR) 
+	@$(PIP) uninstall -y OpenHosta > $(NULL_DEVICE)
+	@$(L_SHELL) $(WRITE) '$(TAG) Installing package: $(PACKAGE_NAME)...' $(COLOR) 
+	@$(PIP) install . > $(NULL_DEVICE)
+	@$(L_SHELL) $(WRITE) '$(TAG) Succesfully updated package !' $(COLOR)
 
-.PHONY: all help package test format lint clean fclean
+.PHONY: all help build upload ftests utests format lint clean re install
diff --git a/README.md b/README.md
index fa85df0..1fa9954 100644
--- a/README.md
+++ b/README.md
@@ -75,7 +75,7 @@ pip show openhosta
 1. Clone the **Git repository** to your local machine using the following command:
 
 ```bash
-git clone git@github.com:hand-e-fr/OpenHosta-dev.git
+git clone git@github.com:hand-e-fr/OpenHosta.git
 ```
 
 2. Navigate to the **directory** of the cloned project:
@@ -87,7 +87,13 @@ cd OpenHosta-dev
 3. Ensure you have installed the necessary **dependencies** before starting.
 
 ```bash
-pip install -r requirements.txt
+pip install .
+```
+
+4. Check that you have the correct version. 
+
+```python
+OpenHosta.__version__
 ```
 
 This way you have all the documentation and source code to understand our project
diff --git a/doc/Docs.md b/doc/Docs.md
index 5f364e6..7fd8583 100644
--- a/doc/Docs.md
+++ b/doc/Docs.md
@@ -13,7 +13,7 @@ ___
 
 #### First Step
 
-OpenHosta is a **Python library** designed to facilitate the integration of **LLMs** into the developer's environment, by adding a layer to the Python programming language without distorting it. It is based on the **PMAC** concept, reimagining the **compilation** process in “Just-In-Time” languages. All our functionalities respect the **syntax and paradigm** of this language. 
+OpenHosta is a **Python library** designed to facilitate the integration of **LLMs** into the developer's environment, by adding a layer to the Python programming language without distorting it. It is based on the [**PMAC**](PMAC.md) concept, reimagining the **compilation** process in languages. All our functionalities respect the **syntax and paradigm** of this language. 
 
 The choice of LLM is mostly up to you, depending on your configuration level, moreover the vast majority are compatible. By default, OpenAI's **GPT-4o** is chosen. This has been tested by our team during development and **provides** a satisfaying level of functionality. 
 
@@ -31,7 +31,7 @@ OpenHosta enables you to create **complex functions**, including those that were
 
 - **Python Ecosystem**
 
-OpenHosta integrates **fully** into Python syntax. Our main goal is to push programming to a **higher level**. For example, we send *docstrings*, commonly used in Python, to the **LLMs** context. We also integrate **advanced methods** such as *lambdas* and the compatibility with *Pydantic* typing. 
+OpenHosta integrates **fully** into Python syntax. Our main goal is to push programming to a **higher level**. For example, we send *docstrings*, commonly used in Python, to the **LLMs** context. We also integrate **advanced methods** such as *lambdas* and the compatibility with *Pydantic* and *typing*. 
 
 - **Open-Source**
 
@@ -39,6 +39,25 @@ We are an Open-Source project. We believe this philosophy contributes to the **s
 
 ---
 
+##### *Legal Framework*
+
+The use of AI in a production context raises important **legal** issues. It is essential to take these issues into account to ensure the compliance of your **deployment**.
+
+- **Legal Compliance**
+For any deployment, it is recommended to verify with an **AI expert** the legal compliance of your use of AI. Indeed, AI is subject to specific **regulations**, particularly in terms of **data protection** and **privacy**. In Europe, the use of AI is governed by the **General Data Protection Regulation** (GDPR) and legislated by the **IA act**. Also, the use of AI in production is subject to specific legal obligations. It is important to take these obligations into account.
+
+- **Security**
+The use of AI can also present risks in terms of **cybersecurity**. It is important to take these risks into account to ensure the security of your deployment.
+For example **injection attacks** are a major risk when deploying an application using AI. It is important to take measures to protect your application against injection attacks, such as using data validation mechanisms and content filtering.
+
+For more information, please consult the following links:
+
+- [AI Act](https://artificialintelligenceact.eu)
+- [GDPR](https://gdpr-info.eu)
+- [Prompt Injection Attack](https://www.ibm.com/topics/prompt-injection)
+
+---
+
 Let's **get started**! First here's the **table of contents** to help you navigate through the various sections of the documentation.
 
 ### Table of Content
@@ -47,6 +66,7 @@ Let's **get started**! First here's the **table of contents** to help you naviga
     - [Introduction](#introduction)
       - [First Step](#first-step)
       - [Why use OpenHosta?](#why-use-openhosta)
+        - [*Legal Framework*](#legal-framework)
     - [Table of Content](#table-of-content)
   - [Features](#features)
     - [OpenHosta Example](#openhosta-example)
@@ -54,12 +74,19 @@ Let's **get started**! First here's the **table of contents** to help you naviga
       - [Librairie Import](#librairie-import)
       - [Basic Setup](#basic-setup)
     - ["emulate" Function](#emulate-function)
-    - [Pydantic Return](#pydantic-return)
+      - [Supported types \& Pydantic](#supported-types--pydantic)
       - [Integration Details](#integration-details)
     - ["suggest" Attributs](#suggest-attributs)
       - [Usage](#usage)
       - [Output Examples](#output-examples)
-    - ["thought"](#thought)
+    - ["thought" Function](#thought-function)
+    - ["example" Function](#example-function)
+    - [Advanced configuration](#advanced-configuration)
+      - [Introduction](#introduction-1)
+      - [Inheriting from the Model Class](#inheriting-from-the-model-class)
+      - [Custom LLM Call Function](#custom-llm-call-function)
+      - [Custom Response Handling Function](#custom-response-handling-function)
+      - [Create a new instance](#create-a-new-instance)
     - [References](#references)
 
 ---
@@ -97,7 +124,8 @@ from OpenHosta import *
 
 We recommend this import method, as it gives you all the important and stable features:
   - Emulate function
-  - Thought function 
+  - Thought function
+  - Example function
   - \_\_suggest\_\_ attributes
   - Configuration tools
 
@@ -139,10 +167,11 @@ config.set_default_model(my_model)
 
 The *emulate* function is the main feature of OpenHosta. This is the function that allows you to emulate functions with AI, i.e. the instructions will be executed in an LLM and not directly in your computer. Here's how to use it.
 
-Emulate is used inside a function, after the “return”. What it does is take the function's documentation as a “prompt” to emulate it. The way in which you write the function is therefore crucial to ensure that “emulate” works properly.
+Emulate is used inside a function or a class method, after the “return”. What it does is take the function's documentation as a “prompt” to emulate it. The way in which you write the function is therefore crucial to ensure that “emulate” works properly.
 
 Here's what you need to know:
-  - **The function prototype** is one of the elements sent to LLM. Its different fields must therefore appear clearly. Give a meaningful and precise name to your function. It's also a good idea to specify the type of arguments and the type of return to reduce the uncertainty related to LLMs. 
+  - **The function prototype** is one of the elements sent to LLM. Its different fields must therefore appear clearly. Give a meaningful and precise name to your function. It's also a good idea to specify the type of arguments and the type of return to reduce the uncertainty related to LLM.
+  
 ```python
 def function(a:int, b:dict)->str:
 ```
@@ -172,15 +201,38 @@ def find_name_age(sentence:str, id:dict)->dict:
 
 Note that, as seen above, you can pass a previously configured model as an emulate parameter.
 
-Be careful, you can put regular instructions in your function and they will be executed. However, emulate doesn't take into account any changes to internal variables.
+Be careful, you can put regular instructions in your function and they will be executed. However, `emulate` retrieves all the variables local to the functions and gives them to the LLM as a context.
 
 emulate also accepts two other arguments: `creativity` and `diversity`. It correspond to the "temperature" and "top_p" parameters of LLMs. These values range from 0 to 1 (inclusive). For more information, please refer to the official [OpenAI documentation](https://openai.com/).
 
-### Pydantic Return 
+#### Supported types & Pydantic
+
+`emulate` support for the **typing** module: You can now use specific return types from the typing module, including [`List`, `Dict`, `Tuple`, `Set`, `FrozenSet`, `Deque`, `Iterable`, `Sequence`, `Mapping`, `Union`, `Optional`, `Literal`].
+
+```python
+from OpenHosta import emulate
+
+def analyze_text(text: str) -> Dict[str, List[Tuple[int, str]]]:
+    """Analyze text to map each word to a list of tuples containing word length and word."""
+    return emulate()
+
+# Example usage
+analysis = analyze_text("Hello, World!")
+
+print(analysis)
+print(type(analysis))
+```
+
+It also includes a verification output that checks and converts the output to the specified type if necessary. Supported types include **Pydantic** models, all types from the **typing** module mentioned above, as well as built-in types such as `dict`, `int`, `float`, `str`, `list`, `set`, `frozenset`, `tuple`, and `bool`.
+The `complex` type is not supported. If you need to use complex numbers, please pass them as a `tuple` and manually convert them after processing.
+
+For more information about Typing module, please check the official [Typing documentation](https://docs.python.org/3/library/typing.html)
 
 OpenHosta integrates with Pydantic, a library for data validation and settings management using Python type annotations. This integration allows you to use Pydantic models directly within `emulate`, ensuring data consistency and validation.
 
-Pydantic provides a way to define data models using Python types. It automatically validates and converts input data to match these types, ensuring that your application processes data safely and accurately. For more information, please read the official [Pydantic documentation](https://docs.pydantic.dev/latest/api/base_model/).
+Pydantic provides a way to define data models using Python types. It automatically validates and converts input data to match these types, ensuring that your application processes data safely and accurately.
+
+For more information, please read the official [Pydantic documentation](https://docs.pydantic.dev/latest/api/base_model/).
 
 #### Integration Details
 
@@ -217,6 +269,8 @@ def find_first_name(sentence:str)->Personn:
     return emulate()
 ```
 
+Note that the Pydantic model cannot be defined inside a function, as this will produce an error.
+
 ### "suggest" Attributs
 
 When you use the emulate function, an attribute is automatically attached. This attribute is a function giving you hints on how to improve your prompt, and a diagram visualization tool. This tool uses the default model to operate.
@@ -245,6 +299,10 @@ In this example, you can see that after calling the emulated function, we can ca
   - `advanced`: Similar to `enhanced prompt` but adds an iteration. The AI will then try to solve advanced problems according to context or other factors. Especially useful in the most complex cases.
   - `diagramm`: Gives a Mermaid diagram showing the stages of AI thinking. Useful if you want to try coding the function yourself.
 
+You can also retrieve the entire LLM response by storing the output of the `suggest` function.
+
+Note that this feature uses the default model.
+
 #### Output Examples
 
 - **Enhanced prompt:**
@@ -267,7 +325,7 @@ graph LR
     I --> J[End]
 ```
 
-### "thought"
+### "thought" Function
 
 **Lambda** functions in Python provide a way to create small, anonymous functions. These are defined using the lambda keyword and can have any number of input parameters but only a single expression.
 
@@ -292,7 +350,191 @@ result = thought("Multiply by two")(2)
 print(result)   # 4
 ```
 
-Note that this feature uses the default model.
+In the example above, we can see two distinct ways of using `thought`. In the first example, you can store a lambda function in a variable and then use it. You can also call it directly by enclosing the arguments behind it in brackets. `thought` accepts multiple arguments and all types native to python. However, the content of the first bracket is always a string.
+
+The `thought` function has an initial pre-compilation stage where it predicts the type of the return value by making an initial call to an LLM. Execution time can therefore be increased.
+You can retrieve the predicted return type with the `_return_type` attribute attached to the object:
+
+```python
+from OpenHosta import thought
+
+x = thought("Adds all integers")
+ret = x(2 ,3 ,6)
+print(x._return_type) # int
+```
+
+**Note** : ***this feature uses the default model.***
+
+### "example" Function
+
+The "example" function is designed to enhance the context of a function for a LLM by adding examples to it. This functionality is encapsulated in the `example` function.
+
+**Key Characteristics**
+
+- **Versatile**: The "example" function can be used both inside and outside a function to specify examples.
+- **Save**: The "example" function provides a tool called `save_examples` that can store all the examples added to a specified function in a ***JSONL*** file.
+- **Load**: The function also offers a tool called `load_examples` to load a ***JSONL*** file into the context of the function.
+
+Here's how it works: 
+
+```python
+from OpenHosta import emulate, example
+
+def translate(text:str, language:str)->str:
+    """
+    This function translates the text in the “text” parameter into the language specified in the “language” parameter.
+    """
+    example("Bonjour Monde !", "portuguese", hosta_out="ola mundo" )
+    return emulate()
+
+
+example(text="Hello World !", language="japanese", hosta_out="こんにちは世界!", hosta_func=translate)
+
+print(translate("Hello World !", "French"))
+```
+
+The "example" function will verify the correlation between the specified input and the parameters of the function. The output should be specified only in the *hosta_out* parameter. If the example are used outside a function, please use the *hosta_func* parameter to specify a function.
+
+Now here's how works `save_examples` and `load_examples`
+
+```python
+from OpenHosta import save_examples, load_examples
+
+save_examples(hosta_func=translate, hosta_path="translate_func_example")
+
+#######
+
+def another_translate(text:str, language:str)->str:
+    """
+    This function translates the text in the “text” parameter into the language specified in the “language” parameter.
+    """
+    return emulate()
+
+load_examples(hosta_path="translate_func_example.jsonl", hosta_func=another_translate)
+# add the jsonl at the end of the path !
+```
+
+output of the `translate_func_example.jsonl`
+
+```JsonL
+{"text": "Hello World !", "language": "japanese", "hosta_out": "こんにちは世界!"}
+{"text": "Bonjour Monde !", "language": "portuguese", "hosta_out": "ola mundo"}
+```
+
+
+**Notes**: *All examples provided for a function are stored in a directory at the root of your environment. You can see it as* ***\_\_hostacache__***.
+
+
+
+### Advanced configuration
+
+
+#### Introduction
+
+This section explains how to customize the program to make its own LLM call and response handling functions. This can be useful if you need a specific functionality that is not provided by the standard library, or to enable compatibility with a specific LLM.
+
+#### Inheriting from the Model Class
+
+The first step is to inherit from the Model class to create your own configuration class. The Model class provides a base for the library's configuration, including connection settings to the LLM and response handling functions.
+
+```python
+from OpenHosta import Model
+
+class MyModel(Model):
+    # Your code here
+```
+
+In the example above, we have created a new class `MyModel` that inherits from the Model class. This means that `MyModel` has access to all the methods and attributes of the Model class. You can now add your own functions to this class to customize the OpenHosta's configuration.
+You can also override an existing function to modify its behavior. It is important to keep input/output components identical in order to avoid errors when interacting with calling functions.
+
+#### Custom LLM Call Function
+
+To create your own LLM call function, you need to override the `api_call` method of the Model class. This method is called every time the library needs to communicate with the LLM.
+
+```python
+from OpenHosta import Model
+
+class MyModel(Model):
+    def api_call(self, sys_prompt, user_prompt, creativity, diversity):
+        # Your code here
+        # Call the LLM and return the response
+        return response
+```
+
+In the example above, we have overridden the "api_call" method of the Model class to create our own LLM call function.
+The "api_call" method takes four arguments:
+
+- **sys_prompt**: a string that specifies the system prompt to be sent to the LLM. It's all the basic instructions to give context to the LLM. It is not function-dependent, but feature-dependent.. System prompts provided by OpenHosta are present in the “prompt.json” file.
+- **user_prompt**: a string that specifies the user prompt to be sent to the LLM. It contains all the information relating to the emulated function. They are placed side by side, with sentences to separate each piece of information. This section is therefore unique for each case. 
+- **creativity**: a float that specifies the creativity level of the LLM's response.
+- **diversity**: a float that specifies the diversity level of the LLM's response.
+
+The "api_call" method returns a response object that contains the LLM's response to the user prompt.
+
+#### Custom Response Handling Function
+
+To create your own response handling function, you need to override the "request_handler" method of the Model class. This method is called every time the library receives a response from the LLM.
+
+```python
+from OpenHosta import Model
+
+class MyModel(Model):
+    def request_handler(self, response, return_type, return_caller):
+        # Your code here
+        # Process the LLM response and return the result
+        return result
+```
+
+In the example above, we have overridden the `request_handler` method of the Model class to create our own response handling function. 
+The "request_handler" method takes three arguments:
+
+- **response**: the response object returned by the LLM.
+- **return_caller**: the type of the return value. For an “int” return type, it would be as follows
+  ```<class :'int'>```
+- **return_type**: a JSON describing the type of the return value. This JSON is mainly used for more complex return types such as Pydantic models or classes. For an “int” return type, the JSON would be as follows:
+  ```{'properties': {'return_hosta_type': {'title': 'Return Hosta Type', 'type': 'integer'}}, 'required': ['return_hosta_type'], 'title': 'Hosta_return_shema', 'type': 'object'}```
+
+The "request_handler" method returns the processed return value. This is the value returned by the emulated function.
+
+#### Create a new instance
+
+You can now create an instance of the class you've just created and use it in all OpenHosta's features.
+
+```python
+from OpenHosta import config, Model, emulate, thought
+
+class MyModel(Model):
+
+    def api_call(self, sys_prompt, user_prompt, creativity, diversity):
+        # Your code here
+        # Call the LLM and return the response
+        return response
+
+    def request_handler(self, response, return_type, return_caller):
+        # Your code here
+        # Process the LLM response and return the result
+        return l_ret
+
+new_model = MyModel(
+    model="model-name"
+    base_url="base-url"
+    api_key="put-your-api-key-here"
+)
+
+def capitalize(a:str)->str:
+    """
+    This function capitalize a sentence in parameter.
+    """
+    return emulate(model=new_model)
+
+print(capitalize("hello world!"))
+
+config.set_default_model(new_model)
+
+x = thought("Translate in german")
+ret = x("Hello World")
+print(ret)
+```
 
 ---
 
diff --git a/doc/PMAC.md b/doc/PMAC.md
new file mode 100644
index 0000000..969c7c5
--- /dev/null
+++ b/doc/PMAC.md
@@ -0,0 +1,32 @@
+
+# Compilateur assisté par modèle pré-entraîné (PMAC)
+
+**Un [Compilateur](https://fr.wikipedia.org/wiki/Compilateur) Assisté par Modèle Pré-Entraîné** (Acronyme anglais : PMAC) est un concept qui utilise les [modèles de langage multimodaux](https://fr.wikipedia.org/wiki/Mod%C3%A8les_de_langage_multimodaux) de grande taille (MLLM) pour améliorer le processus de compilation. Ce concept vise à rapprocher le [langage informatique](https://fr.wikipedia.org/wiki/Langage_informatique) compilable du [langage naturel](https://fr.wikipedia.org/wiki/Langage_naturel) tout en respectant la syntaxe et la structure établies des langages de programmation.
+
+## Histoire
+
+L'apparition des [transformeurs](https://fr.wikipedia.org/wiki/Transformeur), introduits par [Google](https://fr.wikipedia.org/wiki/Google) en 2017, a radicalement changé le paysage technologique, en particulier dans le domaine du [traitement du langage naturel](https://fr.wikipedia.org/wiki/Traitement_du_langage_naturel). Ces modèles ont ouvert la voie à des avancées significatives, perturbant les méthodes traditionnelles d'interaction linguistique. En 2022, [OpenAI](https://fr.wikipedia.org/wiki/OpenAI) a lancé [ChatGPT](https://fr.wikipedia.org/wiki/ChatGPT), qui a marqué un tournant majeur en établissant de nouvelles références en matière de dialogue automatisé et d'interaction avec des machines.
+
+Parmi les innovations qui ont suivi, [LangChain](https://www.langchain.com/), introduit en 2023, a permis aux développeurs d'intégrer des modèles de langage avancés dans les applications logicielles. Cette évolution a simplifié le développement d'applications conversationnelles et ouvert la voie à l'utilisation généralisée de l'IA dans des contextes de programmation traditionnels.
+
+En parallèle, 2023 a vu [GitHub](https://fr.wikipedia.org/wiki/GitHub) introduire [Copilot](https://fr.wikipedia.org/wiki/GitHub_Copilot), une assistance IA permettant de suggérer du code contextuel, révolutionnant la productivité et le processus de codage.
+
+Dans ce contexte d'innovation rapide, l'intégration de l'intelligence artificielle dans une multitude de domaines, y compris le processus de compilation, devient évidente. En 2024, le concept de PMAC voit le jour à Lyon, conçu par un équipe de passionées : [Emmanuel Batt](https://fr.wikipedia.org/wiki/Emmanuel_Batt), [Léandre Ramos](https://fr.wikipedia.org/wiki/L%C3%A9andre_Ramos), [William Jolivet](https://fr.wikipedia.org/wiki/William_Jolivet) et [Merlin Devillard](https://fr.wikipedia.org/wiki/Merlin_Devillard). En parallèle, [Meta](https://fr.wikipedia.org/wiki/Meta_(entreprise)) explore une approche similaire avec le développement de son [LLM Compiler](https://arxiv.org/abs/2407.02524).
+
+## Principe de Base
+
+Avec l'émergence de l'intelligence artificielle et l'évolution continue des modèles de langage, en particulier les modèles multimodaux, les capacités de traitement du NLP ([Natural Language Processing](https://fr.wikipedia.org/wiki/Traitement_automatique_des_langues)) se sont considérablement améliorées. Cette avancée permet de réévaluer les étapes traditionnelles de la compilation en intégrant une compréhension plus profonde et contextuelle des langages de programmation.
+
+En programmation, un compilateur est un outil qui traduit un [langage de programmation](https://fr.wikipedia.org/wiki/Langage_de_programmation) en un [langage machine](https://fr.wikipedia.org/wiki/Langage_machine) , permettant aux ordinateurs d'exécuter les instructions. Les langages de programmation varient par leur niveau d'abstraction, allant des [langages de bas niveau](https://fr.wikipedia.org/wiki/Langage_de_programmation_de_bas_niveau), proches de l'architecture matérielle (langage machine), aux [langages de haut niveau](https://fr.wikipedia.org/wiki/Langage_de_programmation_de_haut_niveau), conçus pour être plus abstraits et lisibles pour les humains.
+
+Le concept de PMAC utilise les MLLM pour traduire du code contenant des expressions plus claires et concrètes. Cette approche confie le traitement des ambiguïtés au PMAC, ce qui permet de gérer des aspects que les compilateurs traditionnels ne pouvaient pas traiter auparavant. Cela offre une interprétation plus précise des opérations programmatiques, permettant ainsi l'apparition de fonctions plus nuancées et complexes.
+
+## Avantages et Inconvénients
+
+|**Avantages**|**Inconvénients**|
+|---|---|
+|**Flexibilité des entrées :** Les fonctions peuvent être spécifiées en langage naturel, permettant ainsi l'exécution d'actions complexes qui seraient autrement difficiles, voire impossibles, à réaliser avec des instructions machine traditionnelles.|**Temps de compilation plus lent :** L'utilisation de MLLM peut entraîner des temps de compilation plus longs par rapport aux méthodes traditionnelles.|
+|**Variété des stratégies d'implémentation :** Le compilateur PMAC peut générer divers types de stratégies d'implémentation, facilitant ainsi l'intégration de différents modes d'exécution adaptés aux besoins spécifiques de l'utilisateur.|**Coût plus élevé :** Les appels [API](https://fr.wikipedia.org/wiki/Interface_de_programmation) aux modèles ou leur stockage peut entraîner des coûts supplémentaires significatifs.|
+|**Optimisation du code :** Grâce à la vaste connaissance des [LLM](https://fr.wikipedia.org/wiki/Grand_mod%C3%A8le_de_langage) en matière de code, PMAC intègre efficacement des techniques d'optimisation, améliorant ainsi les performances du code compilé par une compréhension approfondie des structures et logiques sous-jacentes.|**Imperfections des modèles :** Les modèles peuvent présenter des erreurs ou des divergences, posant des risques potentiels pour la fiabilité du code.|
+
+ 
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index 471ba98..0c172e0 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
 
 [project]
 name = "OpenHosta"
-version = "1.0.2"
+version = "1.1.0-rc3"
 description = "Open-Source programming project IA integretion in developement environnement"
 keywords = ["AI", "GPT", "Natural language", "Autommatic", "Easy"]
 authors = [
@@ -28,7 +28,9 @@ classifiers = [
 dependencies = [
   "requests>=2.32.3",
   "pydantic>=2.8.2",
-  "tiktoken>=0.7.0"
+  "tiktoken>=0.7.0",
+  "jsonschema>=4.23.0",
+  "typing-extensions>=4.12.2"
 ]
 
 [project.urls]
diff --git a/requirements-dev.txt b/requirements-dev.txt
deleted file mode 100644
index 3566307..0000000
--- a/requirements-dev.txt
+++ /dev/null
@@ -1,1 +0,0 @@
-pytest==8.3.2
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
deleted file mode 100644
index e8b3378..0000000
--- a/requirements.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-requests==2.32.3
-pydantic==2.8.2
-tiktoken==0.7.0
\ No newline at end of file
diff --git a/src/OpenHosta.egg-info/PKG-INFO b/src/OpenHosta.egg-info/PKG-INFO
deleted file mode 100644
index 1f6b36e..0000000
--- a/src/OpenHosta.egg-info/PKG-INFO
+++ /dev/null
@@ -1,193 +0,0 @@
-Metadata-Version: 2.1
-Name: OpenHosta
-Version: 1.0.2
-Summary: Open-Source programming project IA integretion in developement environnement
-Author: Léandre Ramos, Merlin Devillard, William Jolivet, Emmanuel Batt
-License: MIT License
-        
-        Copyright (c) 2024 hand-e
-        
-        Permission is hereby granted, free of charge, to any person obtaining a copy
-        of this software and associated documentation files (the "Software"), to deal
-        in the Software without restriction, including without limitation the rights
-        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-        copies of the Software, and to permit persons to whom the Software is
-        furnished to do so, subject to the following conditions:
-        
-        The above copyright notice and this permission notice shall be included in all
-        copies or substantial portions of the Software.
-        
-        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-        SOFTWARE.
-        
-Project-URL: Homepage, https://github.com/hand-e-fr/OpenHosta
-Project-URL: Issues, https://github.com/hand-e-fr/OpenHosta/issues
-Keywords: AI,GPT,Natural language,Autommatic,Easy
-Classifier: Development Status :: 5 - Production/Stable
-Classifier: Programming Language :: Python :: 3.8
-Classifier: License :: OSI Approved :: MIT License
-Classifier: Operating System :: OS Independent
-Classifier: Intended Audience :: Developers
-Classifier: Natural Language :: French
-Classifier: Topic :: Software Development :: Code Generators
-Requires-Python: >=3.8
-Description-Content-Type: text/markdown
-License-File: LICENSE
-Requires-Dist: requests>=2.32.3
-Requires-Dist: pydantic>=2.8.2
-Requires-Dist: tiktoken>=0.7.0
-
-# OpenHosta 
-v1.0 - Open-Source Project
-
-**- The future of development is human -**
-
-Welcome to the OpenHosta documentation, a powerful tool that facilitates the integration LLM in the development environnement. OpenHosta is used to emulate functions using AI, while respecting Python's native paradygma and syntax.
-
-For this project, we have adopted a [Code of Conduct](CODE_OF_CONDUCT.md) to ensure a respectful and inclusive environment for all contributors. Please take a moment to read it.
-
-## Table of Content
-
-- [OpenHosta](#openhosta)
-  - [Table of Content](#table-of-content)
-  - [How to install OpenHosta ?](#how-to-install-openhosta-)
-    - [Prerequisites](#prerequisites)
-    - [Installation](#installation)
-      - [Via pip](#via-pip)
-      - [Via git (Developper version)](#via-git-developper-version)
-    - [Example](#example)
-  - [Further information](#further-information)
-    - [Contributing](#contributing)
-    - [License](#license)
-    - [Authors \& Contact](#authors--contact)
-
----
-
-## How to install OpenHosta ?
-
-### Prerequisites
-
-1. **Python 3.8+**
-   - Download and install Python from [python.org](https://www.python.org/downloads/).
-
-2. **pip**
-   - pip is generally included with Python. Verify its installation with:
-     ```sh
-     pip --version
-     ```
-
-3. **Git**
-   - Download and install Git from [git-scm.com](https://git-scm.com/downloads).
-
-4. **Virtual Environment (optional)**
-   - Create and activate a virtual environment:
-     ```bash
-     python -m venv env
-     ```
-   - Activate the virtual environement:
-      ```bash
-      .\env\Scripts\activate # Windows
-      source env/bin/activate # macOS/Linux
-      ```
-
-5. **API Key**
-   - **API Key**: Log in to your OpenAI account from [openai.com](https://openai.com/), then create your API key. For further information, you can check this [tuto](https://help.openai.com/en/articles/4936850-where-do-i-find-my-openai-api-key).
-
-### Installation
-
-#### Via pip
-
-1. Run the following command to install OpenHosta directly:
- 
-```sh
-pip install openhosta
-```
-
-2. After the installation, you can verify that OpenHosta is installed correctly by running:
-
-```sh
-pip show openhosta
-```
-
-#### Via git (Developper version)
-
-1. Clone the **Git repository** to your local machine using the following command:
-
-```bash
-git clone git@github.com:hand-e-fr/OpenHosta-dev.git
-```
-
-2. Navigate to the **directory** of the cloned project:
-
-```bash
-cd OpenHosta-dev
-```
-
-3. Ensure you have installed the necessary **dependencies** before starting.
-
-```bash
-pip install -r requirements.txt
-```
-
-This way you have all the documentation and source code to understand our project
-
-### Example
-
-```python
-from openhosta import *
-
-config.set_default_apiKey("example-apikey")
-
-def my_func(a:int, b:str)->dict:
-   """
-   This Function does something.
-   """
-   return emulate()
-
-my_func(5, "Hello World!")
-
-my_lambda = thought("Do something")
-my_lambda(5)
-```
-You check OpenHosta's [documentation](doc/Docs.md) for more detailled informations or exemple
-
-## Further information
-
-### Contributing
-
-We warmly welcome contributions from the community. Whether you are an experienced developer or a beginner, your contributions are welcome.
-
-If you wish to contribute to this project, please refer to our [Contribution Guide](CONTRIBUTING.md) and our [Code of Conduct](CODE_OF_CONDUCT.md).
-
-Browse the existing [issues](https://github.com/hand-e-fr/OpenHosta/issues) to see if someone is already working on what you have in mind or to find contribution ideas.
-
-### License
-
-This project is licensed under the MIT License. This means you are free to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the software, subject to the following conditions:
-
-  - The text of the license below must be included in all copies or substantial portions of the software.
-
-See the [LICENSE](LICENSE) file for more details.
-
-### Authors & Contact
-
-For further questions or assistance, please refer to partner hand-e or contact us directly via github.
-
-**Authors**:
-   - Emmanuel Batt: Manager and Coordinator, Founder of Hand-e
-   - William Jolivet: DevOps, SysAdmin
-   - Léandre Ramos: MLOps, IA developer
-   - Merlin Devillard: UX designer, Product Owner
-
-GitHub: https://github.com/hand-e-fr/OpenHosta
-
----
-
-Thank you for your interest in our project and your potential contributions!
-
-**The OpenHosta Team**
diff --git a/src/OpenHosta.egg-info/SOURCES.txt b/src/OpenHosta.egg-info/SOURCES.txt
deleted file mode 100644
index 7057834..0000000
--- a/src/OpenHosta.egg-info/SOURCES.txt
+++ /dev/null
@@ -1,18 +0,0 @@
-LICENSE
-README.md
-pyproject.toml
-src/OpenHosta/__init__.py
-src/OpenHosta/analytics.py
-src/OpenHosta/config.py
-src/OpenHosta/emulate.py
-src/OpenHosta/enhancer.py
-src/OpenHosta/errors.py
-src/OpenHosta/exec.py
-src/OpenHosta/openhosta.py
-src/OpenHosta/prompt.json
-src/OpenHosta/prompt.py
-src/OpenHosta.egg-info/PKG-INFO
-src/OpenHosta.egg-info/SOURCES.txt
-src/OpenHosta.egg-info/dependency_links.txt
-src/OpenHosta.egg-info/requires.txt
-src/OpenHosta.egg-info/top_level.txt
\ No newline at end of file
diff --git a/src/OpenHosta.egg-info/dependency_links.txt b/src/OpenHosta.egg-info/dependency_links.txt
deleted file mode 100644
index 8b13789..0000000
--- a/src/OpenHosta.egg-info/dependency_links.txt
+++ /dev/null
@@ -1,1 +0,0 @@
-
diff --git a/src/OpenHosta.egg-info/top_level.txt b/src/OpenHosta.egg-info/top_level.txt
deleted file mode 100644
index 5093da4..0000000
--- a/src/OpenHosta.egg-info/top_level.txt
+++ /dev/null
@@ -1,1 +0,0 @@
-OpenHosta
diff --git a/src/OpenHosta/OpenHosta.py b/src/OpenHosta/OpenHosta.py
index 4273e6c..0168b54 100644
--- a/src/OpenHosta/OpenHosta.py
+++ b/src/OpenHosta/OpenHosta.py
@@ -1,19 +1,26 @@
-from config import Model, DefaultManager
+from .config import Model, DefaultManager
 
-DefaultManager.set_default_model(Model(
-    model="gpt-4o",
-    base_url="https://api.openai.com/v1/chat/completions"
-))
+DefaultManager.set_default_model(
+    Model(model="gpt-4o", base_url="https://api.openai.com/v1/chat/completions")
+)
 
 
-from emulate import _exec_emulate
-import config
-from thought import thought
-from exec import HostaInjector
-from example import example, load_examples, save_examples
+from .emulate import _exec_emulate
+from . import config
+from .thought import thought
+from .exec import HostaInjector
+from .example import example, load_examples, save_examples
 
 emulate = HostaInjector(_exec_emulate)
 
-__all__ = "emulate", "thought",
-"example", "load_examples", "save_examples",
-"config", "Model", "DefaultManager"
+__all__ = (
+    "emulate",
+    "thought",
+    "example", 
+    "load_examples", 
+    "save_examples",
+    "config", 
+    "Model", 
+    "DefaultManager"
+)
+
diff --git a/src/OpenHosta/__init__.py b/src/OpenHosta/__init__.py
index 45237a3..ca6ae98 100644
--- a/src/OpenHosta/__init__.py
+++ b/src/OpenHosta/__init__.py
@@ -1,1 +1,3 @@
-from .OpenHosta import *
\ No newline at end of file
+from .OpenHosta import *
+
+__version__ = "1.1.0"
\ No newline at end of file
diff --git a/src/OpenHosta/analytics.py b/src/OpenHosta/analytics.py
index 60598fd..9b59686 100644
--- a/src/OpenHosta/analytics.py
+++ b/src/OpenHosta/analytics.py
@@ -2,18 +2,18 @@
 from enum import Enum
 import time as t
 import sys
-import requests
 import json
 
-from config import Model, DefaultManager
-
-from prompt import PromptMananger
+from .config import Model, DefaultManager
+from .prompt import PromptMananger
 
 _x = PromptMananger()
 
 _estimate_prompt = _x.get_prompt("estimate")
 
 l_default = DefaultManager.get_default_model()
+
+
 class ModelAnalizer(Model):
 
     _default_input_cost: int = 0.005
@@ -73,7 +73,7 @@ def _estimate_output_token(self, function_doc: str, function_call: str):
             sys_prompt=_estimate_prompt,
             user_prompt=l_user_prompt,
             creativity=0.2,
-            diversity=0.2
+            diversity=0.2,
         )
 
         if response.status_code == 200:
@@ -124,4 +124,4 @@ def wrapper(*args, **kwargs):
         print(f"{g_c}{bold}Execution time of {func.__name__}: {duration:.2f}s{n}")
         return rv
 
-    return wrapper
\ No newline at end of file
+    return wrapper
diff --git a/src/OpenHosta/cache.py b/src/OpenHosta/cache.py
index 413aff6..a0de4a6 100644
--- a/src/OpenHosta/cache.py
+++ b/src/OpenHosta/cache.py
@@ -1,4 +1,3 @@
-
 import pickle
 import os
 import hashlib
@@ -9,7 +8,7 @@
 from pydantic import BaseModel, create_model
 
 
-CACHE_DIR = '__hostacache__'
+CACHE_DIR = "__hostacache__"
 os.makedirs(CACHE_DIR, exist_ok=True)
 
 
@@ -29,25 +28,27 @@ def __init__(self, func, cache_id, value) -> None:
             "ho_example_id": 0,
             "ho_cothougt": [],
             "ho_cothougt_id": 0,
-
         }
 
     def __call__(self):
         func_name = self.func.__name__
         path_name = os.path.join(CACHE_DIR, f"{func_name}.openhc")
 
-
         if os.path.exists(path_name):
             with open(path_name, "rb") as f:
                 cached_data = pickle.load(f)
             assert self.cache_id in cached_data, "Cache ID not found in cache file"
-            
+
             if self._is_value_already_in_example(self.value, cached_data) == False:
                 cached_data[str(self.cache_id)].append(self.value)
-                cached_data[f'{str(self.cache_id)}'+'_id'] = self._get_hashFunction(str(cached_data[str(self.cache_id)]), 0, 0)
-                cached_data["hash_function"] = self._get_hashFunction(cached_data["function_def"],
-                                                                      cached_data["ho_example_id"],
-                                                                      cached_data["ho_cothougt_id"])
+                cached_data[f"{str(self.cache_id)}" + "_id"] = self._get_hashFunction(
+                    str(cached_data[str(self.cache_id)]), 0, 0
+                )
+                cached_data["hash_function"] = self._get_hashFunction(
+                    cached_data["function_def"],
+                    cached_data["ho_example_id"],
+                    cached_data["ho_cothougt_id"],
+                )
                 with open(path_name, "wb") as f:
                     pickle.dump(cached_data, f)
             return
@@ -56,7 +57,6 @@ def __call__(self):
             pickle.dump(hosta_args, f)
         return
 
-
     def _is_value_already_in_example(self, value, cached_data):
         for item in cached_data["ho_example"]:
             if isinstance(item, dict):
@@ -68,21 +68,26 @@ def _is_value_already_in_example(self, value, cached_data):
                         return True
         return False
 
-
     def _get_hashFunction(self, func_def: str, nb_example: int, nb_thought: int) -> str:
         combined = f"{func_def}{nb_example}{nb_thought}"
         return hashlib.md5(combined.encode()).hexdigest()
 
     def _get_argsFunction(self, func_obj):
         self.infos_cache["function_def"], func_prot = self._get_functionDef(func_obj)
-        self.infos_cache["return_type"], self.infos_cache["return_caller"] = self._get_functionReturnType(func_obj)
+        self.infos_cache["return_type"], self.infos_cache["return_caller"] = (
+            self._get_functionReturnType(func_obj)
+        )
         self.infos_cache[self.cache_id].append(self.value)
-        self.infos_cache[f'{str(self.cache_id)}'+'_id'] = self._get_hashFunction(str(self.infos_cache[str(self.cache_id)]), 0, 0)
-        self.infos_cache["hash_function"] = self._get_hashFunction(self.infos_cache["function_def"],
-                                                                   self.infos_cache["ho_example_id"],
-                                                                   self.infos_cache["ho_cothougt_id"])
+        self.infos_cache[f"{str(self.cache_id)}" + "_id"] = self._get_hashFunction(
+            str(self.infos_cache[str(self.cache_id)]), 0, 0
+        )
+        self.infos_cache["hash_function"] = self._get_hashFunction(
+            self.infos_cache["function_def"],
+            self.infos_cache["ho_example_id"],
+            self.infos_cache["ho_cothougt_id"],
+        )
         return self.infos_cache
-    
+
     def _get_functionDef(self, func: Callable) -> str:
         sig = inspect.signature(func)
 
@@ -105,7 +110,7 @@ def _get_functionDef(self, func: Callable) -> str:
         definition = f"def {func_name}({func_params}):{func_return}\n    '''\n    {func.__doc__}\n    '''"
         prototype = f"def {func_name}({func_params}):{func_return}"
         return definition, prototype
-    
+
     def _inspect_returnType(self, func: Callable) -> str:
         sig = inspect.signature(func)
 
@@ -117,19 +122,19 @@ def _inspect_returnType(self, func: Callable) -> str:
     def _get_typingOrigin(self, return_type) -> bool:
         origin = get_origin(return_type)
         return origin in {
-    list,
-    dict,
-    tuple,
-    set,
-    frozenset,
-    typing.Union,
-    typing.Optional,
-    typing.Literal,
-    collections.deque,
-    collections.abc.Iterable,
-    collections.abc.Sequence,
-    collections.abc.Mapping,
-}
+            list,
+            dict,
+            tuple,
+            set,
+            frozenset,
+            typing.Union,
+            typing.Optional,
+            typing.Literal,
+            collections.deque,
+            collections.abc.Iterable,
+            collections.abc.Sequence,
+            collections.abc.Mapping,
+        }
 
     def _get_functionReturnType(self, func: Callable) -> Dict[str, Any]:
         return_caller = self._inspect_returnType(func)
@@ -158,4 +163,3 @@ def _get_functionReturnType(self, func: Callable) -> Dict[str, Any]:
             return_type = No_return_specified.model_json_schema()
 
         return return_type, return_caller
-
diff --git a/src/OpenHosta/config.py b/src/OpenHosta/config.py
index c86c171..6953cbe 100644
--- a/src/OpenHosta/config.py
+++ b/src/OpenHosta/config.py
@@ -6,6 +6,9 @@
 from pydantic import BaseModel
 from typing import get_origin, get_args
 
+from .errors import ApiKeyError
+
+
 def is_valid_url(url):
     regex = re.compile(
         r"^(?:http|ftp)s?://"
@@ -19,6 +22,7 @@ def is_valid_url(url):
     )
     return re.match(regex, url) is not None
 
+
 class Model:
 
     _SYS_PROMPT = ""
@@ -36,7 +40,7 @@ def __init__(self, model: str = None, base_url: str = None, api_key: str = None)
             frozenset: lambda x: frozenset(x),
             tuple: lambda x: tuple(x),
             bool: lambda x: bool(x),
-            }
+        }
 
         if any(var is None for var in (model, base_url)):
             sys.stderr.write(f"[CONFIG_ERROR] Empty values.")
@@ -55,10 +59,9 @@ def __str__(self) -> str:
 
     def api_call(
         self, sys_prompt: str, user_prompt: str, creativity: float, diversity: float
-    ):     
+    )->requests.models.Response:
         if self.api_key is None or not self.api_key:
-            sys.stderr.write(f"[CALL_ERROR] Unknown API key.")
-            return
+            raise ApiKeyError("Empty API key.")
 
         l_body = {
             "model": self.model,
@@ -85,12 +88,15 @@ def api_call(
             response = requests.post(self.base_url, json=l_body, headers=headers)
             response.raise_for_status()
         except requests.exceptions.RequestException as e:
-            sys.stderr.write(f"[CALL_ERROR] Request failed: {e}\n")
+            sys.stderr.write(f"\n[CALL_ERROR] Request failed:\n{e}\n\n")
         if response.status_code != 200:
-            sys.stderr.write(f"[CALL_ERROR] API call the request was unsuccessful. Status code: {response.status_code}:\n{response.text}")
+            if "invalid_api_key" in str(response.text):
+                raise ApiKeyError("Incorrect API key.")
+            else:    
+                sys.stderr.write(f"[CALL_ERROR] API call was unsuccessful.\nStatus code: {response.status_code}:\n{response.text}")
         return response
- 
-    def _request_handler(self, response, return_type, return_caller):
+
+    def request_handler(self, response, return_type, return_caller):
         l_ret = ""
 
         data = response.json()
@@ -98,7 +104,10 @@ def _request_handler(self, response, return_type, return_caller):
 
         try:
             l_ret_data = json.loads(json_string)
-            validate(instance=l_ret_data.get("return", {}), schema=return_type.get("properties", {})) # Here
+            validate(
+                instance=l_ret_data.get("return", {}),
+                schema=return_type.get("properties", {}),
+            )  # Here
 
         except json.JSONDecodeError as e:
             sys.stderr.write(f"JSONDecodeError: {e}")
@@ -114,7 +123,7 @@ def _request_handler(self, response, return_type, return_caller):
             else:
                 l_ret = l_ret_data["return"]
 
-        elif "return_hosta_type_typing"  in return_type["properties"]:
+        elif "return_hosta_type_typing" in return_type["properties"]:
             l_ret = self.convert_to_type(l_ret_data["return"], return_caller)
 
         elif "return_hosta_type_any" in return_type["properties"]:
@@ -127,7 +136,7 @@ def _request_handler(self, response, return_type, return_caller):
             l_ret = l_ret_data["return"]
 
         return l_ret
-    
+
     def convert_to_type(self, data, type):
         origin = get_origin(type)
         args = get_args(type)
@@ -138,7 +147,8 @@ def convert_to_type(self, data, type):
                 return convert_function(self.convert_to_type(d, args[0]) for d in data)
 
         return data
-    
+
+
 class DefaultModel:
     _instance = None
 
@@ -146,9 +156,9 @@ def __new__(cls, *args, **kwargs):
         if cls._instance is None:
             cls._instance = super(DefaultModel, cls).__new__(cls, *args, **kwargs)
         return cls._instance
-    
+
     def __init__(self):
-        if not hasattr(self, 'model'):
+        if not hasattr(self, "model"):
             self.model = None
 
     def set_default_model(self, new):
@@ -159,19 +169,23 @@ def set_default_model(self, new):
 
     def set_default_apiKey(self, api_key=None):
         if api_key is not None or isinstance(api_key, str):
-           self.model.api_key = api_key
+            self.model.api_key = api_key
         else:
             sys.stderr.write("[CONFIG_ERROR] Invalid API key.")
-    
+
     def get_default_model(self):
         return self.model
-        
+
+
 DefaultManager = DefaultModel()
-        
+
+
 def set_default_model(new):
-    DefaultManager.set_default_model(new) 
+    DefaultManager.set_default_model(new)
+
 
 def set_default_apiKey(api_key=None):
     DefaultManager.set_default_apiKey(api_key)
 
-__all__ = Model, set_default_apiKey, set_default_model, DefaultManager
\ No newline at end of file
+
+__all__ = Model, set_default_apiKey, set_default_model, DefaultManager
diff --git a/src/OpenHosta/emulate.py b/src/OpenHosta/emulate.py
index d2c47c1..d911ec1 100644
--- a/src/OpenHosta/emulate.py
+++ b/src/OpenHosta/emulate.py
@@ -1,9 +1,8 @@
 import sys
+import inspect
 
-from analytics import request_timer
-from prompt import PromptMananger
-
-from config import Model, DefaultManager
+from .prompt import PromptMananger
+from .config import Model, DefaultManager
 
 
 _x = PromptMananger()
@@ -12,14 +11,14 @@
 
 l_default = DefaultManager.get_default_model()
 
-def build_user_prompt(_function_infos : dict = None):
-    function_doc = _function_infos["function_def"]
-    function_call = _function_infos["function_call"]
-    function_return = _function_infos["return_type"]
-    function_example = _function_infos["ho_example"]
-    function_locals = _function_infos["function_locals"]
- 
-    
+
+def build_user_prompt(_infos: dict = None):
+    function_doc = _infos["function_def"]
+    function_call = _infos["function_call"]
+    function_return = _infos["return_type"]
+    function_example = _infos["ho_example"]
+    function_locals = _infos["function_locals"]
+
     user_prompt = (
         "Here's the function definition:\n"
         + function_doc
@@ -29,40 +28,40 @@ def build_user_prompt(_function_infos : dict = None):
 
     if function_example != []:
         user_prompt = (
-            user_prompt 
+            user_prompt
             + "\nHere are some examples of expected input and output:\n"
             + str(function_example)
         )
-        
+
     if not function_return is None:
         user_prompt = (
-            user_prompt 
-            + "\nTo fill the “return” value in the output JSON, build your response as defined in the following JSON schema. Do not change the key \"return\"\n"
+            user_prompt
+            + '\nTo fill the “return” value in the output JSON, build your response as defined in the following JSON schema. Do not change the key "return"\n'
             + str(function_return)
         )
-        
-    if function_locals != {}:
+
+    if function_locals is not None:
         user_prompt = (
-            user_prompt 
+            user_prompt
             + "\nHere's the function's locals variables which you can use as additional information to give your answer:\n"
             + str(function_locals)
         )
-        
+
     return user_prompt
 
-def _exec_emulate(
 
-    _function_infos : dict = None,
-    _function_obj: object = None,
+def _exec_emulate(
+    _infos: dict = None,
+    _obj: object = None,
     model: Model = None,
     l_creativity: float = None,
-    l_diversity: float = None
+    l_diversity: float = None,
 ):
     global _emulator_pre_prompt
-    
-    _function_return_caller = _function_infos["return_caller"]
-    _function_return = _function_infos["return_type"]
-   
+
+    _function_return_caller = _infos["return_caller"]
+    _function_return = _infos["return_type"]
+
     if model is None:
         model = DefaultManager.get_default_model()
 
@@ -77,8 +76,8 @@ def _exec_emulate(
         sys.stderr.write(f"[EMULATE_ERROR]: {v}")
         return None
 
-    l_user_prompt = build_user_prompt(_function_infos)
-    
+    l_user_prompt = build_user_prompt(_infos)
+
     response = model.api_call(
         sys_prompt=_emulator_pre_prompt,
         user_prompt=l_user_prompt,
@@ -86,14 +85,20 @@ def _exec_emulate(
         diversity=l_diversity,
     )
 
-    if _function_obj is not None and "bound method" not in str(_function_obj):
-        setattr(_function_obj, "_last_response", response.json())
-
+    if _obj is not None and inspect.isfunction(_obj):
+        setattr(_obj, "_last_response", response.json())
+        
+    
     if response.status_code == 200:
-        l_ret = model._request_handler(response, _function_return, _function_return_caller)
+        l_ret = model.request_handler(
+            response, _function_return, _function_return_caller
+        )
 
     else:
         sys.stderr.write(f"Error {response.status_code}: {response.text}")
         l_ret = None
-    
-    return l_ret
\ No newline at end of file
+        
+    if _obj is not None and inspect.isfunction(_obj):
+        setattr(_obj, "_last_request", f"{_emulator_pre_prompt}\n{l_user_prompt}")
+
+    return l_ret
diff --git a/src/OpenHosta/enhancer.py b/src/OpenHosta/enhancer.py
index aeafd54..cf58c31 100644
--- a/src/OpenHosta/enhancer.py
+++ b/src/OpenHosta/enhancer.py
@@ -1,9 +1,8 @@
-import requests
 import json
 import sys
 
-from prompt import PromptMananger
-from config import DefaultManager
+from .prompt import PromptMananger
+from .config import DefaultManager
 
 
 _x = PromptMananger()
@@ -11,6 +10,7 @@
 
 l_default = DefaultManager.get_default_model()
 
+
 def _ai_call_enh(sys_prompt: str, func_prot: str, func_doc: str):
     global l_default
 
@@ -22,17 +22,13 @@ def _ai_call_enh(sys_prompt: str, func_prot: str, func_doc: str):
         + func_doc
         + "\n---\n"
     )
-    
+
     response = l_default.api_call(
-        sys_prompt=sys_prompt, 
-        user_prompt=l_user_prompt, 
-        creativity=0.8, 
-        diversity=0.8
+        sys_prompt=sys_prompt, user_prompt=l_user_prompt, creativity=0.8, diversity=0.8
     )
 
     response_data = response.json()
     return response_data["choices"][0]["message"]["content"]
-    
 
 
 def _parse_data(response: str, last_enh: dict) -> dict:
@@ -87,7 +83,7 @@ def enhance(func):
     func_name, func_doc = func.__name__, func.__doc__
 
     last_return = _ai_call_enh(_enhancer_pre_prompt, func._prot, func_doc)
-    
+
     last_enh = _parse_data(last_return, last_enh)
 
     _build_attributes(func, last_enh)
diff --git a/src/OpenHosta/errors.py b/src/OpenHosta/errors.py
index 2515fc2..6e5a802 100644
--- a/src/OpenHosta/errors.py
+++ b/src/OpenHosta/errors.py
@@ -1,8 +1,30 @@
-class OhCustomError(Exception):
-    """Base class for other customs exceptions"""
-    pass
+from typing import Literal
+
+__all__ = (
+    "RequestError",
+    "ApiKeyError",
+    "FrameError"
+)
+
+OhErrorCodes = Literal[
+    ""
+]
+
+class OhErrorMixin(Exception):
+    """ Base class for other customs exceptions """
+    def __init__(self, message:str):
+        super().__init__(message)
+        self.message = message
+        
+    def __str__(self)->str:
+        return f"{self.message}\n"
     
-class RequestError(OhCustomError):
-    """Raised when a request to a llm went wrong"""
-    pass
+class RequestError(OhErrorMixin):
+    """ Raised when a request to a llm went wrong """
+
+class ApiKeyError(RequestError):
+    """ Raised when API key is missing or incorrect """
+
+class FrameError(OhErrorMixin):
+    """ Raised when the frame inspection fail """
     
\ No newline at end of file
diff --git a/src/OpenHosta/example.py b/src/OpenHosta/example.py
index 3980cbc..6c13b87 100644
--- a/src/OpenHosta/example.py
+++ b/src/OpenHosta/example.py
@@ -1,33 +1,27 @@
 import inspect
 import pickle
 import os
-
-import ast
-
-from cache import Hostacache
 import json
 
-CACHE_DIR = '__hostacache__'
+from .cache import Hostacache
+
+CACHE_DIR = "__hostacache__"
 os.makedirs(CACHE_DIR, exist_ok=True)
 
-def example(
-    *args,
-    hosta_func = None,
-    hosta_out = None,
-    **kwargs
-    ):
+
+def example(*args, hosta_func=None, hosta_out=None, **kwargs):
     input_type = {}
     output_type = {}
     example_dict = {}
-    
+
     if hosta_func is None:
-        try :
+        try:
             func_frame = inspect.currentframe().f_back
             func_name = func_frame.f_code.co_name
             func = func_frame.f_globals[func_name]
         except:
             raise ValueError(f"Please provide hosta_func for specifying the function")
-  
+
     elif callable(hosta_func):
         func = hosta_func
     else:
@@ -42,24 +36,32 @@ def example(
         raise ValueError(f"Function does not have signature")
     if args != ():
         if len(args) != len(input_type):
-            raise ValueError(f"Too many arguments for function {func.__name__}, please provide {len(input_type)} arguments, use hosta_out for output")
+            raise ValueError(
+                f"Too many arguments for function {func.__name__}, please provide {len(input_type)} arguments, use hosta_out for output"
+            )
 
         for i, arg in enumerate(args):
             param_name = list(input_type.keys())[i]
 
             expected_type = input_type[param_name]
             if not isinstance(arg, expected_type):
-                raise ValueError(f"Argument {arg} does NOT match the expected type {expected_type} for parameter {param_name}. For function {func.__name__}")
+                raise ValueError(
+                    f"Argument {arg} does NOT match the expected type {expected_type} for parameter {param_name}. For function {func.__name__}"
+                )
             example_dict[param_name] = arg
-               
+
     else:
         if len(kwargs) != len(input_type):
-            raise ValueError(f"Too many arguments for function {func.__name__}, please provide {len(input_type)} arguments, use hosta_out for output")
+            raise ValueError(
+                f"Too many arguments for function {func.__name__}, please provide {len(input_type)} arguments, use hosta_out for output"
+            )
 
         for key, value in kwargs.items():
             expected_type = input_type[key]
             if not isinstance(value, expected_type):
-                raise ValueError(f"Argument {value} does NOT match the expected type {expected_type} for parameter {key}. For function {func.__name__}")
+                raise ValueError(
+                    f"Argument {value} does NOT match the expected type {expected_type} for parameter {key}. For function {func.__name__}"
+                )
             example_dict[key] = value
 
     if hosta_out is None:
@@ -67,76 +69,84 @@ def example(
     else:
         expected_type = output_type["hosta_out"]
         if not isinstance(hosta_out, expected_type):
-            raise ValueError(f"Output {hosta_out} does NOT match the expected type {expected_type}. For function {func.__name__}")
+            raise ValueError(
+                f"Output {hosta_out} does NOT match the expected type {expected_type}. For function {func.__name__}"
+            )
         example_dict["hosta_out"] = hosta_out
 
     cache_id = "ho_example"
     cache = Hostacache(func, cache_id, example_dict)
     cache()
 
+
 def save_examples(hosta_func=None, hosta_path=None):
     cached_data = {}
 
     if hosta_func is None:
-        try :
+        try:
             func_frame = inspect.currentframe().f_back
             func_name = func_frame.f_code.co_name
             func = func_frame.f_globals[func_name]
         except:
             raise ValueError(f"Please provide hosta_func for specifying the function")
-  
+
     elif callable(hosta_func):
         func = hosta_func
     else:
         raise ValueError(f"Please provide hosta_func for specifying the function")
 
     if hosta_path is None:
-        raise ValueError(f"Please provide hosta_path for specifying the path to save the cache")
+        raise ValueError(
+            f"Please provide hosta_path for specifying the path to save the cache"
+        )
     total_path = f"{hosta_path}" + ".jsonl"
 
     func_name = func.__name__
     path_name = os.path.join(CACHE_DIR, f"{func_name}.openhc")
 
-    try :
+    try:
         if os.path.exists(path_name):
             with open(path_name, "rb") as f:
                 cached_data = pickle.load(f)
-                with open(total_path, 'w') as t:
+                with open(total_path, "w") as t:
                     for dict in cached_data["ho_example"]:
-                        t.write(json.dumps(dict)+"\n")
+                        t.write(json.dumps(dict) + "\n")
         else:
             raise ValueError(f"Could not found the cache at {path_name}")
     except Exception as e:
         raise ValueError(f"Could not found the cache at {path_name}") from e
-    
+
+
 def load_examples(hosta_func=None, hosta_path=None):
     if hosta_func is None:
-        try :
+        try:
             func_frame = inspect.currentframe().f_back
             func_name = func_frame.f_code.co_name
             func = func_frame.f_globals[func_name]
         except:
             raise ValueError(f"Please provide hosta_func for specifying the function")
-        
+
     elif callable(hosta_func):
         func = hosta_func
     else:
         raise ValueError(f"Please provide hosta_func for specifying the function")
-    
+
     if hosta_path is None:
-        raise ValueError(f"Please provide hosta_path for specifying the path to load the cache")
+        raise ValueError(
+            f"Please provide hosta_path for specifying the path to load the cache"
+        )
 
     list_of_examples = []
 
-    with open(hosta_path, 'r') as file:
-        for line in file:
-            hosta_example = json.loads(line.strip())
-            list_of_examples.append(hosta_example)
+    try:
+        with open(hosta_path, "r") as file:
+            for line in file:
+                hosta_example = json.loads(line.strip())
+                list_of_examples.append(hosta_example)
+    except Exception:
+            raise IOError("Please provide a Json or a JsonL file only.")
 
     cache_id = "ho_example"
     value = list_of_examples
     cache = Hostacache(func, cache_id, value)
     cache()
-    
-
-
diff --git a/src/OpenHosta/exec.py b/src/OpenHosta/exec.py
index 0a4335c..8876229 100644
--- a/src/OpenHosta/exec.py
+++ b/src/OpenHosta/exec.py
@@ -1,4 +1,3 @@
-
 import pickle
 import os
 import hashlib
@@ -10,17 +9,19 @@
 import sys
 import copy
 
-from enhancer import enhance
+from .enhancer import enhance
+from .errors import FrameError
 
 
-CACHE_DIR = '__hostacache__'
+CACHE_DIR = "__hostacache__"
 os.makedirs(CACHE_DIR, exist_ok=True)
 
+
 class HostaInjector:
     def __init__(self, exec):
         if not callable(exec):
             raise TypeError("Executive function must be a function.")
-        
+
         self.exec = exec
         self.infos_cache = {}
 
@@ -44,22 +45,28 @@ def __call__(self, *args, **kwargs):
         if os.path.exists(path_name):
             with open(path_name, "rb") as f:
                 cached_data = pickle.load(f)
-            
+
             function_def, func_prot = self._get_functionDef(func_obj)
-            function_hash = self._get_hashFunction(function_def,
-                                                cached_data["ho_example_id"],
-                                                cached_data["ho_cothougt_id"])
+            function_hash = self._get_hashFunction(
+                function_def,
+                cached_data["ho_example_id"],
+                cached_data["ho_cothougt_id"],
+            )
 
             if function_hash == cached_data["hash_function"]:
-                cached_data["function_call"], cached_data["function_locals"] = self._get_functionCall(func_obj, caller)
+                cached_data["function_call"], cached_data["function_locals"] = (
+                    self._get_functionCall(func_obj, caller)
+                )
                 self._attach_attributs(func_obj, func_prot)
                 return self.exec(cached_data, func_obj, *args, **kwargs)
 
         hosta_args = self._get_argsFunction(func_obj)
         with open(path_name, "wb") as f:
             res = pickle.dump(hosta_args, f)
-# TODO : fix the function locals because he didn't load in the cache
-        hosta_args["function_call"], hosta_args["function_locals"] = self._get_functionCall(func_obj, caller)
+        # TODO : fix the function locals because he didn't load in the cache
+        hosta_args["function_call"], hosta_args["function_locals"] = (
+            self._get_functionCall(func_obj, caller)
+        )
         return self.exec(hosta_args, func_obj, *args, **kwargs)
 
     def _get_hashFunction(self, func_def: str, nb_example: int, nb_thought: int) -> str:
@@ -69,46 +76,65 @@ def _get_hashFunction(self, func_def: str, nb_example: int, nb_thought: int) ->
     def _get_argsFunction(self, func_obj):
 
         self.infos_cache["function_def"], func_prot = self._get_functionDef(func_obj)
-        self.infos_cache["return_type"], self.infos_cache["return_caller"] = self._get_functionReturnType(func_obj)
-        self.infos_cache["hash_function"] = self._get_hashFunction(self.infos_cache["function_def"],
-                                                                   self.infos_cache["ho_example_id"],
-                                                                self.infos_cache["ho_cothougt_id"])
+        self.infos_cache["return_type"], self.infos_cache["return_caller"] = (
+            self._get_functionReturnType(func_obj)
+        )
+        self.infos_cache["hash_function"] = self._get_hashFunction(
+            self.infos_cache["function_def"],
+            self.infos_cache["ho_example_id"],
+            self.infos_cache["ho_cothougt_id"],
+        )
         self._attach_attributs(func_obj, func_prot)
         return self.infos_cache
 
     def _extend_scope(self) -> Callable:
-        func:Callable = None
-    
-        try:
-            current = inspect.currentframe()
-            if current is None:
-                raise Exception("Current frame is None")
-            caller_1 = current.f_back
-            if caller_1 is None:
-                raise Exception("Caller[lvl1] frame is None")
-            caller_2 = caller_1.f_back
-            if caller_2 is None:
-                raise Exception("Caller[lvl2] frame is None")
-            
-            caller_name = caller_2.f_code.co_name
-            
-            if 'self' in caller_2.f_locals:
-                obj = caller_2.f_locals['self']
-                func = getattr(obj, caller_name, None)
-            else:
-                code = caller_2.f_code
-                for obj in caller_2.f_back.f_locals.values():
-                    if hasattr(obj, "__code__"):
-                        if obj.__code__ == code:
-                            func = obj
-                            break
-                        
-            if not callable(func):
-                raise Exception("Larger scope isn't a callable or scope can't be extended.\n")
-        except Exception as e:
-            sys.stderr.write(f"[FRAME_ERROR]: {e}")
-            func, caller_2 = None, None
-        return func, caller_2
+        func: Callable = None
+        current = None
+        step = None
+        caller = None
+
+        current = inspect.currentframe()
+        if current is None:
+            raise FrameError("Current frame is None")
+        step = current.f_back
+        if step is None:
+            raise FrameError("Caller[lvl1] frame is None")
+        caller = step.f_back
+        if caller is None:
+            raise FrameError("Caller[lvl2] frame is None")
+
+        caller_name = caller.f_code.co_name
+        caller_code = caller.f_code
+        l_caller = caller
+        
+        if "self" in caller.f_locals:
+            obj = caller.f_locals["self"]
+            func = getattr(obj, caller_name, None)
+            if func:
+                func = inspect.unwrap(func)
+        else:
+            while func is None and l_caller.f_back is not None:
+                for obj in l_caller.f_back.f_locals.values():
+                    found = False
+                    try:
+                        if hasattr(obj, "__code__"):
+                            found = True
+                    except:
+                        continue       
+                    if found and obj.__code__ == caller_code:
+                        func = obj
+                        break
+                if func is None:
+                    l_caller = l_caller.f_back
+            if func is None:
+                func = caller.f_globals.get(caller_name)
+                if func:
+                    func = inspect.unwrap(func)
+        
+        if func is None or not callable(func):
+            raise FrameError("The emulated function cannot be found.")
+
+        return func, caller
 
     def _get_functionDef(self, func: Callable) -> str:
         sig = inspect.signature(func)
@@ -138,7 +164,7 @@ def _get_functionCall(self, func: Callable, caller) -> str:
         _, _, _, values = inspect.getargvalues(caller)
 
         sig = inspect.signature(func)
-        
+
         values_args = copy.deepcopy(values)
         values_locals = copy.deepcopy(values)
         for values_name in values.keys():
@@ -146,13 +172,13 @@ def _get_functionCall(self, func: Callable, caller) -> str:
                 values_args.pop(values_name)
             else:
                 values_locals.pop(values_name)
-                
+
         if "self" in values_locals.keys():
             values_locals.pop("self")
-            
+
         if values_locals != {}:
             locals = copy.deepcopy(values_locals)
-        
+
         bound_args = sig.bind_partial(**values_args)
         bound_args.apply_defaults()
 
@@ -160,7 +186,7 @@ def _get_functionCall(self, func: Callable, caller) -> str:
             f"{name}={value!r}" if name in bound_args.kwargs else f"{value!r}"
             for name, value in bound_args.arguments.items()
         )
-        
+
         call = f"{func.__name__}({args_str})"
         return call, locals
 
@@ -175,21 +201,21 @@ def _inspect_returnType(self, func: Callable) -> str:
     def _get_typingOrigin(self, return_type) -> bool:
         origin = get_origin(return_type)
         return origin in {
-    list,
-    dict,
-    tuple,
-    set,
-    frozenset,
-    typing.Union,
-    typing.Annotated,
-    typing.Optional,
-    typing.Literal,
-    collections.deque,
-    collections.abc.Iterable,
-    collections.abc.Sequence,
-    collections.abc.Mapping,
-}
-    
+            list,
+            dict,
+            tuple,
+            set,
+            frozenset,
+            typing.Union,
+            typing.Annotated,
+            typing.Optional,
+            typing.Literal,
+            collections.deque,
+            collections.abc.Iterable,
+            collections.abc.Sequence,
+            collections.abc.Mapping,
+        }
+
     def _get_functionReturnType(self, func: Callable) -> Dict[str, Any]:
         return_caller = self._inspect_returnType(func)
         return_type = None
diff --git a/src/OpenHosta/prompt.py b/src/OpenHosta/prompt.py
index 94adf8e..a6be5cd 100644
--- a/src/OpenHosta/prompt.py
+++ b/src/OpenHosta/prompt.py
@@ -39,13 +39,13 @@ def get_prompt_details(self, key):
             return prompt
         sys.stderr.write(f"[JSON_ERROR] Prompt not found\n")
         return None
-    
-    def set_prompt(self, name:str, category:str, version:str, filepath:str):
+
+    def set_prompt(self, name: str, category: str, version: str, filepath: str):
         json_filepath = "prompt.json"
         new = {"key": "", "text": "", "category": "", "version": ""}
-        
+
         try:
-            with open(filepath, 'r', encoding="utf-8") as file:
+            with open(filepath, "r", encoding="utf-8") as file:
                 prompt = file.read()
         except FileNotFoundError:
             sys.stderr.write(f"File {filepath} not found.")
@@ -53,7 +53,7 @@ def set_prompt(self, name:str, category:str, version:str, filepath:str):
             sys.stderr.write(f"File error: {e}")
         else:
             try:
-                with open(json_filepath, 'r', encoding="utf-8") as json_file:
+                with open(json_filepath, "r", encoding="utf-8") as json_file:
                     data = json.load(json_file)
             except FileNotFoundError:
                 sys.stderr.write(f"File {json_filepath} not found.")
@@ -64,12 +64,12 @@ def set_prompt(self, name:str, category:str, version:str, filepath:str):
             else:
                 new["key"], new["category"], new["version"] = name, category, version
                 new["text"] = prompt
-                
+
                 found = False
-                for elem in data['prompts']:
-                    if elem['key'] == new['key']:
-                        elem['text'] = new['text']
-                        elem['version'] = new['version']
+                for elem in data["prompts"]:
+                    if elem["key"] == new["key"]:
+                        elem["text"] = new["text"]
+                        elem["version"] = new["version"]
                         found = True
                         break
 
@@ -77,7 +77,7 @@ def set_prompt(self, name:str, category:str, version:str, filepath:str):
                     data["prompts"].append(new)
 
                 try:
-                    with open(json_filepath, 'w', encoding='utf-8') as json_file:
+                    with open(json_filepath, "w", encoding="utf-8") as json_file:
                         json.dump(data, json_file, ensure_ascii=False, indent=4)
                 except FileNotFoundError:
                     sys.stderr.write(f"File {json_filepath} not found.")
@@ -87,7 +87,7 @@ def set_prompt(self, name:str, category:str, version:str, filepath:str):
                     sys.stderr.write(e)
                 else:
                     print(f"{name} prompt has been added to {json_filepath}")
-                    
+
     def show_prompt(self, key):
         prompt = self.prompts.get(key)
         if prompt:
diff --git a/src/OpenHosta/thought.py b/src/OpenHosta/thought.py
index 4655574..7547f73 100644
--- a/src/OpenHosta/thought.py
+++ b/src/OpenHosta/thought.py
@@ -3,9 +3,9 @@
 from pydoc import locate
 from pydantic import create_model
 
-from emulate import _exec_emulate
-from config import DefaultManager
-from prompt import PromptMananger
+from .emulate import _exec_emulate
+from .config import DefaultManager
+from .prompt import PromptMananger
 
 l_default = DefaultManager.get_default_model()
 _x = PromptMananger()
@@ -20,46 +20,48 @@ def thought(key):
         "return_type": None,
         "ho_example": None,
         "function_locals": None,
-        "return_caller": None
-    }    
+        "return_caller": None,
+    }
+
     def inner_func(*args, **kwargs):
         global l_default, _thought_sys_prompt
-        
+
         l_user_prompt = (
             "Here's the function behavior:\n"
             + f"{key}\n"
             + "Here's the arguments:\n"
             + f"{args}\n"
         )
-        
+
         response = l_default.api_call(
             sys_prompt=_thought_sys_prompt,
             user_prompt=l_user_prompt,
             creativity=0.5,
             diversity=0.5,
         )
-        
+
         data = response.json()
         type_json = data["choices"][0]["message"]["content"]
         type_dict = json.loads(type_json)
         type_str = str(type_dict["type"])
-        
+
         _function_infos["return_caller"] = locate(type_str)
         setattr(inner_func, "_return_type", _function_infos["return_caller"])
-        
+
         new_model = create_model(
-            "Hosta_return_shema", return_hosta_type=(_function_infos["return_caller"], ...)
+            "Hosta_return_shema",
+            return_hosta_type=(_function_infos["return_caller"], ...),
         )
         _function_infos["return_type"] = new_model.model_json_schema()
-        
+
         typed = (
             str(args)
             + "\n"
-            + "Here's the return type that must respect for your response. The python type is in the key \"type\" of this JSON schema:\n"
+            + 'Here\'s the return type that must respect for your response. The python type is in the key "type" of this JSON schema:\n'
             + str(type_dict)
             + "\n"
         )
-        
+
         try:
             _function_infos["function_def"] = key
             _function_infos["function_call"] = typed


Test patch:
diff --git a/tests/data/.gitkeep b/tests/data/.gitkeep
deleted file mode 100644
index e69de29..0000000
diff --git a/tests/functionnalTests/test_mandatory.py b/tests/functionnalTests/test_mandatory.py
index 93d0161..63c6e04 100644
--- a/tests/functionnalTests/test_mandatory.py
+++ b/tests/functionnalTests/test_mandatory.py
@@ -1,11 +1,7 @@
 import pytest
 import time as t
-import sys
-import os
 from pydantic import BaseModel
-from typing import Optional, Callable
-
-sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "src", "OpenHosta")))
+from typing import Callable
 
 from OpenHosta import config, emulate, thought
 
@@ -195,6 +191,20 @@ def multiply(a:int, b:int)->int:
         assert not multiply.enhanced_prompt is None
         assert not multiply.review is None
         assert not multiply.diagramm is None
+        
+    def test_rc2(self):
+        
+        def multiply(a:int, b:int)->int:
+            """
+            This function multiplies two integers in parameter.
+            """
+            return emulate()
+        
+        def main():
+            return multiply(2 ,3)
+            
+        res = main()
+        assert res == 6
 
 class TestThought:
     
@@ -233,8 +243,8 @@ def test_FeaturePredict(self, prompt, args, expected):
 
     def test_FeatureMultiArgs(self):
         x = thought("Combine each word of a sentence in a string")
-        ret = x("Hello", ", how ary you ?", " Nice to meet you !")
-        assert ret == "Hello, how ary you ? Nice to meet you !"
+        ret = x("Hello", ", how are you ?", " Nice to meet you !")
+        assert ret == "Hello, how are you ? Nice to meet you !"
     
     def test_FeatureChainOfThought(self):
         pass
@@ -257,41 +267,5 @@ def test_FeatureDefaultModel(self):
     
     def test_FeatureCachedPrediction(self):
         pass
-
-
 class TestTypingPydantic:
-    pass
-
-"""
-typing (parametrize)
-creativity, diversity
-example
-cache
----
-test en trompant le LLM dans la def ou la doc
-Error:
-quand func n'est pas trouvé, Erreur expliquant la déclaration dans le frame parent
-def test():
-    def a():
-        \"\"\" Give me a ramdom word \"\""
-        return emulate()
-    return a
-
-#Erreur
-print(test()())
-#Valid
-b = test()
-print(b())
-
-CONTRIBUTING
-changelog
-doc
-test
----
-Makefile
-make benchmark
-make functests
-make unittests
-avec redirection de coverage et html dans TestData
-argument makefile apiKey
-"""
\ No newline at end of file
+    pass
\ No newline at end of file
diff --git a/tests/performanceTests/benchmark_GPT4o.py b/tests/performanceTests/benchmark_GPT4o.py
deleted file mode 100644
index d97565f..0000000
--- a/tests/performanceTests/benchmark_GPT4o.py
+++ /dev/null
@@ -1,9 +0,0 @@
-node = {"name": "", "duration": -1, "score": True}
-test_info = []
-
-def add_to_benchmark(name:str, duration:float, score:bool):
-    global node, test_info
-    new = dict(node)
-    new["name"], new["duration"], new["score"] = name, duration, score
-    test_info.append(new)
-    return test_info
\ No newline at end of file
diff --git a/tests/performanceTests/test_emulate_default.py b/tests/performanceTests/test_emulate_default.py
deleted file mode 100644
index 3626c01..0000000
--- a/tests/performanceTests/test_emulate_default.py
+++ /dev/null
@@ -1,152 +0,0 @@
-import pytest
-import time as t
-import sys
-import os
-
-sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
-
-from OpenHosta import emulate, config
-
-config.set_default_apiKey("")
-
-
-def timer_tests(func):
-    def wrapper(*args, **kwargs):
-        g_c = "\033[94m"
-        n = "\033[0m"
-        bold = "\033[1m"
-
-        start = t.time()
-        rv = func(*args, **kwargs)
-        end = t.time()
-
-        duration = end - start
-        print(f"{g_c}{bold}Execution time of {func.__name__}: {duration:.2f}s{n}")
-        return (rv, duration)
-
-    return wrapper
-
-
-@timer_tests
-def emulate_1args_int(arg) -> int:
-    """Docstring"""
-    return emulate()
-
-
-@timer_tests
-def emulate_2args_int(arg1, arg2) -> int:
-    """Docstring"""
-    return emulate()
-
-
-class TestEmulate:
-
-    @pytest.mark.parametrize(
-        "name, doc, int1, int2, expected",
-        [
-            (
-                "multiply",
-                "This function mutliplies two integers in parameters",
-                2,
-                3,
-                6,
-            ),
-            (
-                "add", 
-                "This function adds two integers in parameters", 
-                2, 
-                3, 
-                5
-            ),
-            (
-                "multiply_float",
-                "This function multiplies two floating-point numbers in parameter.",
-                3.8,
-                7.7,
-                29.26,
-            ),
-            (
-                "power",
-                "This function calculates the power of a number given a base (first arg) and an exponent (second arg).",
-                4,
-                5,
-                1024,
-            ),
-            (
-                "sum_list",
-                "This function adds an integers in parameter to all integers in a list in parameter",
-                10,
-                [1, 6, 7, 12, 0],
-                [11, 16, 17, 22, 10],
-            ),
-            (
-                "substract",
-                "This function substracts two integers in parameters",
-                2,
-                3,
-                -1,
-            ),
-            (
-                "divide",
-                "This function divides the first integers in parameter by the second",
-                621,
-                3,
-                207,
-            ),
-        ],
-    )
-    def test_math_basicOperation(self, name, doc, int1, int2, expected):
-        emulate_2args_int.__name__ = name
-        emulate_2args_int.__doc__ = doc
-        result, duration = emulate_2args_int(int1, int2)
-        assert result == expected
-
-    @pytest.mark.parametrize(
-        "name, doc, int1, int2, expected",
-        [
-            (
-                "factorial",
-                "This function calculates the factorial of a non-negative integer in parameter",
-                10,
-                -1,
-                3628800,
-            ),
-            (
-                "common_divisor",
-                "This function returns the greatest common divisor of two integers in parameter.",
-                36,
-                60,
-                12,
-            ),
-            (
-                "square_root",
-                "This function computes the square root of a given non-negative number in parameter.",
-                144,
-                -1,
-                12,
-            ),
-            (
-                "is_prime",
-                "This function determines if a given integer is a prime number. Return 0 if true and 1 if false",
-                199,
-                -1,
-                0,
-            ),
-        ],
-    )
-    def test_math_mediumOperation(self, name, doc, int1, int2, expected):
-        if int2 == -1:
-            emulate_1args_int.__name__ = name
-            emulate_1args_int.__doc__ = doc
-            result, duration = emulate_1args_int(int1)
-        else:
-            emulate_2args_int.__name__ = name
-            emulate_2args_int.__doc__ = doc
-            result, duration = emulate_2args_int(int1, int2)
-        assert result == expected
-
-
-# high number
-# negative
-# hard division
-# duration test
diff --git a/tests/unitTests/test_exec.py b/tests/unitTests/test_exec.py
new file mode 100644
index 0000000..1f310a8
--- /dev/null
+++ b/tests/unitTests/test_exec.py
@@ -0,0 +1,69 @@
+import sys
+import pytest
+from unittest.mock import patch
+import inspect
+
+from OpenHosta import exec
+
+class TestExtendScope:
+    
+    def test_basic(self):
+        def printHello():
+            print("Hello")
+
+        x =  exec.HostaInjector(printHello)
+
+        class Test:
+            def test(self):
+                return x._extend_scope()
+
+        def caller():
+            y = Test()
+            return y.test()
+
+        res = caller()
+        assert res[0] == caller
+        
+    def test_Flask(self):
+        from flask import Flask, request, session
+        
+        def printHello():
+            print("Hello")
+
+        x =  exec.HostaInjector(printHello)
+
+        class Test:
+            def test(self):
+                return x._extend_scope()
+
+        def caller():
+            y = Test()
+            return y.test()
+
+        res = caller()
+        del sys.modules["flask"]
+        assert res[0] == caller
+        
+    def test_ErrorHandling(self):
+        def printHello():
+            print("Hello")
+
+        x =  exec.HostaInjector(printHello)
+
+        class Test:
+            def test(self):
+                return x._extend_scope()
+
+        def caller():
+            y = Test()
+            return y.test()
+
+        current = inspect.currentframe()
+        print(f"INTERNAL: {current.f_locals}")
+        current.f_locals["caller"] = "hello"
+        current.f_locals.pop("caller")
+        print(f"INTERNAL: {current.f_locals}")
+
+        with patch("exec.hasattr", return_value=False):
+            res = caller()
+        assert res == caller
\ No newline at end of file
diff --git a/tests/unitTests/unitTest.py b/tests/unitTests/unitTest.py
deleted file mode 100644
index e69de29..0000000