From d83c22aa4e233568da535a43dce8dc8e6a731770 Mon Sep 17 00:00:00 2001 From: ian_Cin Date: Mon, 2 Oct 2023 16:24:56 +0700 Subject: [PATCH] [AUR-395, AUR-415] Adopt Example1 Injury pipeline; add .flow() for enabling bottom-up pipeline execution (#32) * add example1/injury pipeline example * add dotenv * update various api --- .env.secret | Bin 0 -> 1837 bytes .gitignore | 2 +- .gitsecret/paths/mapping.cfg | 2 +- README.md | 11 +++- credentials.txt.secret | Bin 1848 -> 0 bytes knowledgehub/base.py | 13 +++++ knowledgehub/llms/base.py | 24 ++------ knowledgehub/llms/chats/base.py | 16 ++++- knowledgehub/llms/completions/base.py | 4 +- knowledgehub/pipelines/agents/react/agent.py | 2 +- knowledgehub/pipelines/agents/rewoo/agent.py | 4 +- knowledgehub/post_processing/extractor.py | 19 +++--- knowledgehub/prompt/base.py | 58 +++++++++++++------ setup.py | 2 + tests/test_post_processing.py | 2 + tests/test_prompt.py | 24 ++++---- 16 files changed, 114 insertions(+), 69 deletions(-) create mode 100644 .env.secret delete mode 100644 credentials.txt.secret diff --git a/.env.secret b/.env.secret new file mode 100644 index 0000000000000000000000000000000000000000..3d6cc8e0d0f7fb33d0505cdec6460b4020f99cca GIT binary patch literal 1837 zcmV+|2h#Y30gMB9>i0uNB-FA23;$?X+ZHg0$OH@lirPi40OyYGE#?vYOIuFcdtJpE z7aaHaSLvMd$zjNhu-Bb48RnLdzZ!J5fzBD#Y>{q6|T<1A8@`=X(=W z@)GewY6g}EX<#Q-;-pnBDN0`qs0AXd0ypYwR&Pe>(!nH1f$s>u(w=y9htW)>O%kH- z4m;4NhTU!xsV&e-Z%a7}&S3n==`h+6c=wXyyCR|Sd|sz=BlGg^>QDhBe6#YzR~Gch zQCK3r{_GS^Z~mu*%eSHx1vT@W%11;oUPsOZ_8{F3bq8h!2`SS%h_iQ_mfte5#$*uo zWgG#X<-z)$>3f%BANAVaSCZfq25cof2^)Yv10YV;5nrq5oBit7Y{0nmiNEFak>oq1 z&|@5Fm)I>rx!-lalj%_x>NM98lj?LCN33zy|9qPzytsBoIauZGJv7pIFx$Jze0m!& zEoexhpIaLyYVR#g<`84H(77A4u8hbsYZ6^`<{%5+k0I|FoR0XJ*W+Bq%&PjxnPfXsAWO=NV+O$g1R2T!MCkCBmpqwY7yV~V^_N(At zdfN#X+jO=CFp!Fi|B^tepu9>3&2(oGCfN@9aYf(Puad>9H6f3*lP0?N_Kmd_r`<9p zZKOu!k70mXE;z!o9a2p00#mmao7w@DMFLG#TzqG_x5yY$a3U{aN;_UdEcVmy?8!a< z>pAE7-XG>Y`c8H?m6!@BrTwv_;#d-7V3oxxchel|C~(Zqq4`)T7ygXZKiV0uyuIVu z_5TXCofL~`6-7@jR7~jdOdq4^P#7DWW|k+2RoA>OHwXgOv7*g-aK0JUnDP?&t28%d zNUM2y969*T3>J`xBS*3WUgDUfO4OIcjY^60T3}pgudhpHW{&|DRzuJS9xL8bdx#E4 z{dw=E##GeIP}%$W%h|6ypi*64!tz3Z_0@WDG+%SzrL*c(X~wLuH3<9{K%h z(I+s_?+c1>$=-S+_WtQ_8^w>^%;v`S0}brc0H;-dkii&L%!|YrgR`csUNcQ-)Gfud z{r9V>I&Z?!xJW!G(caCk^W^!Vl0g#nBMw)PfJ7lLub0So_oZ2hKo zSzGi(h39umV!Fj1M9#UNO4Dpvt4ZF6bnc}tbG8uhk^!fY5lV!QPj)4|ylMsVdy5VCKnF!m{Hj?2y5aZ{$TvMVTAZ_P-vsfzp z_~^Sl4_Spj{HFI bHk8>x95yk1y>58U-tpPPfBa@a3c^e92XckK literal 0 HcmV?d00001 diff --git a/.gitignore b/.gitignore index 0e80245..1f9bef6 100644 --- a/.gitignore +++ b/.gitignore @@ -453,7 +453,7 @@ $RECYCLE.BIN/ logs/ .gitsecret/keys/random_seed !*.secret -credentials.txt +.env S.gpg-agent* .vscode/settings.json diff --git a/.gitsecret/paths/mapping.cfg b/.gitsecret/paths/mapping.cfg index bc9d978..5af535f 100644 --- a/.gitsecret/paths/mapping.cfg +++ b/.gitsecret/paths/mapping.cfg @@ -1 +1 @@ -credentials.txt:272c4eb7f422bebcc5d0f1da8bde47016b185ba8cb6ca06639bb2a3e88ea9bc5 +.env:272c4eb7f422bebcc5d0f1da8bde47016b185ba8cb6ca06639bb2a3e88ea9bc5 diff --git a/README.md b/README.md index 3d3c8cc..3291e9a 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,12 @@ pip install kotaemon@git+ssh://git@github.com/Cinnamon/kotaemon.git ### Credential sharing -This repo uses [git-secret](https://sobolevn.me/git-secret/) to share credentials, which internally uses `gpg` to encrypt and decrypt secret files. +This repo uses [git-secret](https://sobolevn.me/git-secret/) to share credentials, which +internally uses `gpg` to encrypt and decrypt secret files. + +This repo uses `python-dotenv` to manage credentials stored as enviroment variable. +Please note that the use of `python-dotenv` and credentials are for development +purposes only. Thus, it should not be used in the main source code (i.e. `kotaemon/` and `tests/`), but can be used in `examples/`. #### Install git-secret @@ -63,13 +68,13 @@ In order to gain access to the secret files, you must provide your gpg public fi #### Decrypt the secret file -The credentials are encrypted in the `credentials.txt.secret` file. To print the decrypted content to stdout, run +The credentials are encrypted in the `.env.secret` file. To print the decrypted content to stdout, run ```shell git-secret cat [filename] ``` -Or to get the decrypted `credentials.txt` file, run +Or to get the decrypted `.env` file, run ```shell git-secret reveal [filename] diff --git a/credentials.txt.secret b/credentials.txt.secret deleted file mode 100644 index 2597a44aa27236c29aa2980d4a3737f001b12a3e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1848 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 diff --git a/knowledgehub/base.py b/knowledgehub/base.py index 4ce4fce..a011af8 100644 --- a/knowledgehub/base.py +++ b/knowledgehub/base.py @@ -16,6 +16,19 @@ class BaseComponent(Compose): - is_batch: check if input is batch """ + inflow = None + + def flow(self): + if self.inflow is None: + raise ValueError("No inflow provided.") + + if not isinstance(self.inflow, BaseComponent): + raise ValueError( + f"inflow must be a BaseComponent, found {type(self.inflow)}" + ) + + return self.__call__(self.inflow.flow()) + @abstractmethod def run_raw(self, *args, **kwargs): ... diff --git a/knowledgehub/llms/base.py b/knowledgehub/llms/base.py index 4fefece..0e47889 100644 --- a/knowledgehub/llms/base.py +++ b/knowledgehub/llms/base.py @@ -1,25 +1,13 @@ -from dataclasses import dataclass, field from typing import List -from ..base import BaseComponent +from pydantic import Field + +from kotaemon.documents.base import Document -@dataclass -class LLMInterface: - text: List[str] +class LLMInterface(Document): + candidates: List[str] completion_tokens: int = -1 total_tokens: int = -1 prompt_tokens: int = -1 - logits: List[List[float]] = field(default_factory=list) - - -class PromptTemplate(BaseComponent): - pass - - -class Extract(BaseComponent): - pass - - -class PromptNode(BaseComponent): - pass + logits: List[List[float]] = Field(default_factory=list) diff --git a/knowledgehub/llms/chats/base.py b/knowledgehub/llms/chats/base.py index 27ac5f7..73d6da1 100644 --- a/knowledgehub/llms/chats/base.py +++ b/knowledgehub/llms/chats/base.py @@ -11,7 +11,17 @@ Message = TypeVar("Message", bound=BaseMessage) class ChatLLM(BaseComponent): - ... + def flow(self): + if self.inflow is None: + raise ValueError("No inflow provided.") + + if not isinstance(self.inflow, BaseComponent): + raise ValueError( + f"inflow must be a BaseComponent, found {type(self.inflow)}" + ) + + text = self.inflow.flow().text + return self.__call__(text) class LangchainChatLLM(ChatLLM): @@ -44,8 +54,10 @@ class LangchainChatLLM(ChatLLM): def run_document(self, text: List[Message], **kwargs) -> LLMInterface: pred = self.agent.generate([text], **kwargs) # type: ignore + all_text = [each.text for each in pred.generations[0]] return LLMInterface( - text=[each.text for each in pred.generations[0]], + text=all_text[0] if len(all_text) > 0 else "", + candidates=all_text, completion_tokens=pred.llm_output["token_usage"]["completion_tokens"], total_tokens=pred.llm_output["token_usage"]["total_tokens"], prompt_tokens=pred.llm_output["token_usage"]["prompt_tokens"], diff --git a/knowledgehub/llms/completions/base.py b/knowledgehub/llms/completions/base.py index 0b03d72..db79809 100644 --- a/knowledgehub/llms/completions/base.py +++ b/knowledgehub/llms/completions/base.py @@ -33,8 +33,10 @@ class LangchainLLM(LLM): def run_raw(self, text: str) -> LLMInterface: pred = self.agent.generate([text]) + all_text = [each.text for each in pred.generations[0]] return LLMInterface( - text=[each.text for each in pred.generations[0]], + text=all_text[0] if len(all_text) > 0 else "", + candidates=all_text, completion_tokens=pred.llm_output["token_usage"]["completion_tokens"], total_tokens=pred.llm_output["token_usage"]["total_tokens"], prompt_tokens=pred.llm_output["token_usage"]["prompt_tokens"], diff --git a/knowledgehub/pipelines/agents/react/agent.py b/knowledgehub/pipelines/agents/react/agent.py index 6fc8ddf..2d8ac53 100644 --- a/knowledgehub/pipelines/agents/react/agent.py +++ b/knowledgehub/pipelines/agents/react/agent.py @@ -162,7 +162,7 @@ class ReactAgent(BaseAgent): prompt = self._compose_prompt(instruction) logging.info(f"Prompt: {prompt}") response = self.llm(prompt, stop=["Observation:"]) # type: ignore - response_text = response.text[0] + response_text = response.text logging.info(f"Response: {response_text}") action_step = self._parse_output(response_text) if action_step is None: diff --git a/knowledgehub/pipelines/agents/rewoo/agent.py b/knowledgehub/pipelines/agents/rewoo/agent.py index 1d47eb9..1247c91 100644 --- a/knowledgehub/pipelines/agents/rewoo/agent.py +++ b/knowledgehub/pipelines/agents/rewoo/agent.py @@ -245,7 +245,7 @@ class RewooAgent(BaseAgent): # Plan planner_output = planner(instruction) - plannner_text_output = planner_output.text[0] + plannner_text_output = planner_output.text plan_to_es, plans = self._parse_plan_map(plannner_text_output) planner_evidences, evidence_level = self._parse_planner_evidences( plannner_text_output @@ -263,7 +263,7 @@ class RewooAgent(BaseAgent): # Solve solver_output = solver(instruction, worker_log) - solver_output_text = solver_output.text[0] + solver_output_text = solver_output.text return AgentOutput( output=solver_output_text, cost=total_cost, token_usage=total_token diff --git a/knowledgehub/post_processing/extractor.py b/knowledgehub/post_processing/extractor.py index 61b6254..c60077a 100644 --- a/knowledgehub/post_processing/extractor.py +++ b/knowledgehub/post_processing/extractor.py @@ -50,9 +50,9 @@ class RegexExtractor(BaseComponent): if not output_map: return text - return output_map.get(text, text) + return str(output_map.get(text, text)) - def run_raw(self, text: str) -> List[str]: + def run_raw(self, text: str) -> List[Document]: """ Runs the raw text through the static pattern and output mapping, returning a list of strings. @@ -66,9 +66,12 @@ class RegexExtractor(BaseComponent): output = self.run_raw_static(self.pattern, text) output = [self.map_output(text, self.output_map) for text in output] - return output + return [ + Document(text=text, metadata={"origin": "RegexExtractor"}) + for text in output + ] - def run_batch_raw(self, text_batch: List[str]) -> List[List[str]]: + def run_batch_raw(self, text_batch: List[str]) -> List[List[Document]]: """ Runs a batch of raw text inputs through the `run_raw()` method and returns the output for each input. @@ -95,13 +98,7 @@ class RegexExtractor(BaseComponent): Returns: List[Document]: A list of extracted documents. """ - texts = self.run_raw(document.text) - output = [ - Document(text=text, metadata={**document.metadata, "RegexExtractor": True}) - for text in texts - ] - - return output + return self.run_raw(document.text) def run_batch_document( self, document_batch: List[Document] diff --git a/knowledgehub/prompt/base.py b/knowledgehub/prompt/base.py index ee40be5..6570cb8 100644 --- a/knowledgehub/prompt/base.py +++ b/knowledgehub/prompt/base.py @@ -1,3 +1,4 @@ +import warnings from typing import Union from kotaemon.base import BaseComponent @@ -5,7 +6,7 @@ from kotaemon.documents.base import Document from kotaemon.prompt.template import PromptTemplate -class BasePrompt(BaseComponent): +class BasePromptComponent(BaseComponent): """ Base class for prompt components. @@ -15,6 +16,16 @@ class BasePrompt(BaseComponent): given template. """ + def __init__(self, template: Union[str, PromptTemplate], **kwargs): + super().__init__() + self.template = ( + template + if isinstance(template, PromptTemplate) + else PromptTemplate(template) + ) + + self.__set(**kwargs) + def __check_redundant_kwargs(self, **kwargs): """ Check for redundant keyword arguments. @@ -33,7 +44,9 @@ class BasePrompt(BaseComponent): redundant_keys = provided_keys - expected_keys if redundant_keys: - raise ValueError(f"\nKeys provided but not in template: {redundant_keys}") + warnings.warn( + f"Keys provided but not in template: {redundant_keys}", UserWarning + ) def __check_unset_placeholders(self): """ @@ -111,27 +124,34 @@ class BasePrompt(BaseComponent): Returns: dict: A dictionary of keyword arguments. """ + + def __prepare(key, value): + if isinstance(value, str): + return value + if isinstance(value, (int, Document)): + return str(value) + + raise ValueError( + f"Unsupported type {type(value)} for template value of key {key}" + ) + kwargs = {} for k in self.template.placeholders: v = getattr(self, k) - if isinstance(v, (int, Document)): - v = str(v) - elif isinstance(v, BaseComponent): - v = str(v()) + if isinstance(v, BaseComponent): + v = v() + if isinstance(v, list): + v = str([__prepare(k, each) for each in v]) + elif isinstance(v, (str, int, Document)): + v = __prepare(k, v) + else: + raise ValueError( + f"Unsupported type {type(v)} for template value of key {k}" + ) kwargs[k] = v return kwargs - def __init__(self, template: Union[str, PromptTemplate], **kwargs): - super().__init__() - self.template = ( - template - if isinstance(template, PromptTemplate) - else PromptTemplate(template) - ) - - self.__set(**kwargs) - def set(self, **kwargs): """ Similar to `__set` but for external use. @@ -163,7 +183,8 @@ class BasePrompt(BaseComponent): self.__check_unset_placeholders() prepared_kwargs = self.__prepare_value() - return self.template.populate(**prepared_kwargs) + text = self.template.populate(**prepared_kwargs) + return Document(text=text, metadata={"origin": "PromptComponent"}) def run_raw(self, *args, **kwargs): pass @@ -182,3 +203,6 @@ class BasePrompt(BaseComponent): def is_batch(self, *args, **kwargs): pass + + def flow(self): + return self.__call__() diff --git a/setup.py b/setup.py index 152d537..7d906d6 100644 --- a/setup.py +++ b/setup.py @@ -56,6 +56,8 @@ setuptools.setup( "chromadb", "wikipedia", "googlesearch-python", + "python-dotenv", + "pytest-mock", ], }, entry_points={"console_scripts": ["kh=kotaemon.cli:main"]}, diff --git a/tests/test_post_processing.py b/tests/test_post_processing.py index 405426b..bd27ad7 100644 --- a/tests/test_post_processing.py +++ b/tests/test_post_processing.py @@ -30,9 +30,11 @@ def test_is_batch(regex_extractor): def test_run_raw(regex_extractor): output = regex_extractor("This is a test. 123") + output = [each.text for each in output] assert output == ["123"] def test_run_batch_raw(regex_extractor): output = regex_extractor(["This is a test. 123", "456"]) + output = [[each.text for each in batch] for batch in output] assert output == [["123"], ["456"]] diff --git a/tests/test_prompt.py b/tests/test_prompt.py index b55d42b..a9e0505 100644 --- a/tests/test_prompt.py +++ b/tests/test_prompt.py @@ -2,7 +2,7 @@ import pytest from kotaemon.documents.base import Document from kotaemon.post_processing.extractor import RegexExtractor -from kotaemon.prompt.base import BasePrompt +from kotaemon.prompt.base import BasePromptComponent from kotaemon.prompt.template import PromptTemplate @@ -14,7 +14,7 @@ def test_set_attributes(): ) comp.set_run(kwargs={"text": "This is a test. 1 2 3"}, temp=True) - prompt = BasePrompt(template=template, s="Alice", i=30, doc=doc, comp=comp) + prompt = BasePromptComponent(template=template, s="Alice", i=30, doc=doc, comp=comp) assert prompt.s == "Alice" assert prompt.i == 30 assert prompt.doc == doc @@ -23,23 +23,23 @@ def test_set_attributes(): def test_check_redundant_kwargs(): template = PromptTemplate("Hello, {name}!") - prompt = BasePrompt(template, name="Alice") - with pytest.raises(ValueError): - prompt._BasePrompt__check_redundant_kwargs(name="Alice", age=30) + prompt = BasePromptComponent(template, name="Alice") + with pytest.warns(UserWarning, match="Keys provided but not in template: {'age'}"): + prompt._BasePromptComponent__check_redundant_kwargs(name="Alice", age=30) def test_check_unset_placeholders(): template = PromptTemplate("Hello, {name}! I'm {age} years old.") - prompt = BasePrompt(template, name="Alice") + prompt = BasePromptComponent(template, name="Alice") with pytest.raises(ValueError): - prompt._BasePrompt__check_unset_placeholders() + prompt._BasePromptComponent__check_unset_placeholders() def test_validate_value_type(): template = PromptTemplate("Hello, {name}!") - prompt = BasePrompt(template) + prompt = BasePromptComponent(template) with pytest.raises(ValueError): - prompt._BasePrompt__validate_value_type(name={}) + prompt._BasePromptComponent__validate_value_type(name={}) def test_run(): @@ -50,18 +50,18 @@ def test_run(): ) comp.set_run(kwargs={"text": "This is a test. 1 2 3"}, temp=True) - prompt = BasePrompt(template=template, s="Alice", i=30, doc=doc, comp=comp) + prompt = BasePromptComponent(template=template, s="Alice", i=30, doc=doc, comp=comp) result = prompt() assert ( - result + result.text == "str = Alice, int = 30, doc = Helloo, Alice!, comp = ['One', 'Two', 'Three']" ) def test_set_method(): template = PromptTemplate("Hello, {name}!") - prompt = BasePrompt(template) + prompt = BasePromptComponent(template) prompt.set(name="Alice") assert prompt.name == "Alice"