Skip to content

Commit

Permalink
Merge pull request #99 from fractalego/index-cache
Browse files Browse the repository at this point in the history
Index cache
  • Loading branch information
fractalego committed Jul 10, 2024
2 parents eb8e946 + 47b5893 commit 27378a2
Show file tree
Hide file tree
Showing 51 changed files with 490 additions and 109 deletions.
12 changes: 11 additions & 1 deletion documentation/source/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@ A typical configuration file looks like this:
"waking_up_sound": true,
"deactivate_sound": true,
"rules": "rules.yaml",
"index": "indices.yaml",
"cache_filename": "knowledge_cache",
"prompt_filename": "main.prompt",
"functions": "functions.py",
"frontend_port": 8081,
"max_recursion": 2,
"llm_model": {
"model_host": "localhost",
"model_port": 8080,
Expand All @@ -37,6 +40,7 @@ A typical configuration file looks like this:
}
These settings regulate the following:

* "waking_up_word" is the name of the bot, used to wake up the system in the "run-audio" mode.
Expand All @@ -45,6 +49,12 @@ These settings regulate the following:

* "rules" is the file containing the facts and rules that guide the chatbot. The default is "rules.yaml".

* "index" is the file containing the path to the files to index. The default is "indices.yaml".

* "cache_filename" is the file where the indexed knowledge is cached. The default is "knowledge_cache".

* "prompt_filename" is the file containing the main prompt for the chatbot. The default is "main.prompt".

* "functions" is the file containing the functions that can be used in the rules. The default is "functions.py".

* "frontend_port" is the port where the web frontend is running. The default is 8090.
Expand Down
1 change: 1 addition & 0 deletions documentation/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Welcome to WAFL's 0.0.90 documentation!
configuration
running_WAFL
facts_and_rules
modify_the_prompt
examples
testcases
actions
Expand Down
23 changes: 23 additions & 0 deletions documentation/source/modify_the_prompt.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
Modify the original prompt
==========================

The prompt is stored in the file "main.prompt" in the project's root directory.
The name of the file can be changed in the `config.json` file.
The default is:


.. code-block:: text
A user is chatting with a bot. The chat is happening through a web interface. The user is typing the messages and the bot is replying.
This is summary of the bot's knowledge:
{facts}
The rules that *must* be followed are:
{rules}
Create a plausible dialogue based on the aforementioned summary and rules.
Do not repeat yourself. Be friendly but not too servile.
Follow the rules if present and they apply to the dialogue. Do not improvise if rules are present.
The variables `{facts}` and `{rules}` are replaced by the actual facts and rules when the prompt is generated.
5 changes: 4 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,7 @@ sphinx-rtd-theme==1.2.0
bluepy==1.3.0
einops==0.6.1
g2p-en==2.1.0
pyyaml==6.0.1
pyyaml==6.0.1
joblib==1.4.2
pymupdf==1.24.7

3 changes: 3 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"wafl.connectors.remote",
"wafl.events",
"wafl.extractors",
"wafl.handlers",
"wafl.inference",
"wafl.interface",
"wafl.knowledge",
Expand Down Expand Up @@ -61,6 +62,8 @@
"einops==0.6.1",
"g2p-en==2.1.0",
"pyyaml==6.0.1",
"joblib==1.4.2",
"pymupdf==1.24.7",
],
classifiers=[
"License :: OSI Approved :: MIT License",
Expand Down
3 changes: 3 additions & 0 deletions tests/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
"waking_up_sound": true,
"deactivate_sound": true,
"rules": "rules.yaml",
"index": "indices.yaml",
"cache_filename": "knowledge_cache",
"prompt_filename": "main.prompt",
"functions": "functions.py",
"max_recursion": 2,
"llm_model": {
Expand Down
2 changes: 2 additions & 0 deletions tests/indices.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
paths:
- files_to_index/
11 changes: 11 additions & 0 deletions tests/main.prompt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
A user is chatting with a bot. The chat is happening through a web interface. The user is typing the messages and the bot is replying.

This is summary of the bot's knowledge:
{facts}

The rules that *must* be followed are:
{rules}

Create a plausible dialogue based on the aforementioned summary and rules.
Do not repeat yourself. Be friendly but not too servile.
Follow the rules if present and they apply to the dialogue. Do not improvise if rules are present.
49 changes: 49 additions & 0 deletions tests/test_indexing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import asyncio
import os
import yaml

from unittest import TestCase

from wafl.config import Configuration
from wafl.dataclasses.dataclasses import Query
from wafl.knowledge.indexing_implementation import add_to_index, load_knowledge

_path = os.path.dirname(__file__)


class TestIndexing(TestCase):
def test__path_can_be_added_to_index(self):
data = _load_index()
prior_count = len(data["paths"])
add_to_index("files_to_index2")

data = _load_index()
current_count = len(data["paths"])
self.assertEqual(current_count, prior_count + 1)

data["paths"].remove("files_to_index2")
with open("indices.yaml", "w") as file:
file.write(yaml.dump(data))

def test__indexed_files_can_be_retrieved(self):
config = Configuration.load_local_config()
knowledge = asyncio.run(load_knowledge(config))
results = asyncio.run(
knowledge.ask_for_facts(Query.create_from_text("How do I start WAFL"))
)
expected = "WAFL"
self.assertIn(expected, results[0].text)

def test__pdf_can_be_read(self):
config = Configuration.load_local_config()
knowledge = asyncio.run(load_knowledge(config))
results = asyncio.run(
knowledge.ask_for_facts(Query.create_from_text("What color is the sky?"))
)
expected = "green"
self.assertIn(expected, results[0].text)


def _load_index():
with open("indices.yaml", "r") as file:
return yaml.safe_load(file.read())
4 changes: 2 additions & 2 deletions tests/test_rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ def test__rules_can_be_triggered(self):
interface=interface,
)
asyncio.run(conversation_events.process_next())
expected = "The horse is tall"
self.assertIn(expected, interface.get_utterances_list()[-1])
expected = "the horse is tall"
self.assertIn(expected, interface.get_utterances_list()[-1].lower())

def test__rules_are_not_always_triggered(self):
interface = DummyInterface(
Expand Down
36 changes: 35 additions & 1 deletion todo.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,39 @@
* interruptible speech
* dependabot!!!
* use poetry

PharazonE
* upload to hetzner and make it work for some retrieval tasks
* develop more rules + use-cases for voice and other


/* add control over which llm to use from the frontend
/ - add list of models in the backend

/* add quantization of llm to wafl_llm config
/* write docs about it on wafl

/* add option so use llama.cpp from wafl_llm
/* add option to have None as a model setting in wafl_llm

/* add pdf to indexing
* add json to indexing
/* add metadata to indexing items


/make backend it run with ollama as well (no too slow)


/None of the knowledge is loaded from the web interface. Why?
/- you have just changed the load_knowledge function to make it async.


wafl:
- create indices
/- create indices
- allow files/folders to be indexed (modify rules.yaml and then re-index)
- add keywords in retrieval from tfidf
- silence output when someone speak
- multiple models in wafl-llm, with selection from frontend

training:
- retrain phi3
Expand Down
12 changes: 10 additions & 2 deletions wafl/answerer/answerer_implementation.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from typing import List, Tuple

from wafl.exceptions import CloseConversation
from wafl.facts import Fact
from wafl.dataclasses.facts import Fact
from wafl.interface.conversation import Conversation, Utterance


Expand Down Expand Up @@ -116,7 +116,15 @@ async def _run_code(to_execute: str, module, functions) -> str:
def get_text_from_facts_and_thresholds(
facts_and_thresholds: List[Tuple[Fact, float]], memory: str
) -> List[str]:
return [item[0].text for item in facts_and_thresholds if item[0].text not in memory]
text_list = []
for item in facts_and_thresholds:
if item[0].text not in memory:
text = item[0].text
if item[0].metadata:
text = f"Metadata for the following text: {str(item[0].metadata)}" + "\n" + text
text_list.append(text)

return text_list


def add_dummy_utterances_to_continue_generation(
Expand Down
28 changes: 6 additions & 22 deletions wafl/answerer/dialogue_answerer.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from inspect import getmembers, isfunction
from typing import List, Tuple
from wafl.answerer.answerer_implementation import (
is_executable,
substitute_memory_in_answer_and_get_memories_if_present,
create_one_liner,
get_text_from_facts_and_thresholds,
Expand All @@ -12,17 +11,16 @@
)
from wafl.answerer.base_answerer import BaseAnswerer
from wafl.answerer.rule_maker import RuleMaker
from wafl.connectors.clients.llm_chitchat_answer_client import LLMChitChatAnswerClient
from wafl.extractors.dataclasses import Query, Answer
from wafl.interface.conversation import Conversation, Utterance
from wafl.connectors.clients.llm_chat_client import LLMChatClient
from wafl.dataclasses.dataclasses import Query, Answer
from wafl.interface.conversation import Conversation
from wafl.simple_text_processing.questions import is_question


class DialogueAnswerer(BaseAnswerer):
def __init__(self, config, knowledge, interface, code_path, logger):
self._threshold_for_facts = 0.85
self._delete_current_rule = "[delete_rule]"
self._client = LLMChitChatAnswerClient(config)
self._client = LLMChatClient(config)
self._knowledge = knowledge
self._logger = logger
self._interface = interface
Expand All @@ -38,7 +36,6 @@ def __init__(self, config, knowledge, interface, code_path, logger):
config,
interface,
max_num_rules=1,
delete_current_rule=self._delete_current_rule,
)

async def answer(self, query_text: str) -> Answer:
Expand All @@ -51,10 +48,6 @@ async def answer(self, query_text: str) -> Answer:
rules_text = await self._get_relevant_rules(conversation)
if not conversation:
conversation = create_one_liner(query_text)
last_bot_utterances = conversation.get_last_speaker_utterances("bot", 3)
last_user_utterance = conversation.get_last_speaker_utterances("user", 1)
if not last_user_utterance:
last_user_utterance = query_text
conversational_timestamp = len(conversation)
facts = await self._get_relevant_facts(
query,
Expand All @@ -73,18 +66,9 @@ async def answer(self, query_text: str) -> Answer:
answer_text, memories = await self._apply_substitutions(
original_answer_text
)
if answer_text in last_bot_utterances and not is_executable(
original_answer_text
):
conversation = create_one_liner(last_user_utterance[-1])
continue

if self._delete_current_rule in answer_text:
self._prior_rules = []
final_answer_text += answer_text
break

final_answer_text += answer_text

if not memories:
break

Expand Down Expand Up @@ -130,7 +114,7 @@ async def _get_relevant_rules(self, conversation: Conversation) -> List[str]:
for rule in rules:
if rule not in self._prior_rules:
self._prior_rules.insert(0, rule)
self._prior_rules = self._prior_rules[:self._max_num_past_utterances_for_rules]
self._prior_rules = self._prior_rules[: self._max_num_past_utterances_for_rules]
return self._prior_rules

def _init_python_module(self, module_name):
Expand Down
10 changes: 3 additions & 7 deletions wafl/answerer/rule_maker.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from typing import List

from wafl.extractors.dataclasses import Query
from wafl.rules import Rule
from wafl.dataclasses.dataclasses import Query
from wafl.dataclasses.rules import Rule


class RuleMaker:
Expand All @@ -11,14 +11,12 @@ def __init__(
config: "BaseConfig",
interface: "BaseInterface",
max_num_rules: int,
delete_current_rule: str,
max_recursion: int = 3,
):
self._knowledge = knowledge
self._config = config
self._interface = interface
self._max_num_rules = max_num_rules
self._delete_current_rule = delete_current_rule
if not config.get_value("max_recursion"):
self._max_indentation = max_recursion
else:
Expand All @@ -29,9 +27,7 @@ async def create_from_query(self, conversation: "Conversation") -> List[str]:
rules_texts = []
for rule in rules:
rules_text = rule.get_string_using_template(
"- {effect}:\n{clauses}\n"
+ rule.indent_str
+ f'- After you completed all the steps output "{self._delete_current_rule}".\n'
"- {effect}:\n{clauses}\n" + rule.indent_str
)
rules_texts.append(rules_text)
await self._interface.add_fact(f"The bot remembers the rule:\n{rules_text}")
Expand Down
Loading

0 comments on commit 27378a2

Please sign in to comment.