diff --git a/.gitignore b/.gitignore index 32d450d9..b84da9fd 100644 --- a/.gitignore +++ b/.gitignore @@ -29,11 +29,9 @@ storage/ *.pkl # /*.png /*.dot -/*.svg /*.csv index.faiss *.json -*.svg # ignore the softlink to adalflow cache *.adalflow .idea diff --git a/README.md b/README.md index a8c365d2..4bc2c9a8 100644 --- a/README.md +++ b/README.md @@ -11,26 +11,27 @@

- ⚡ The Library to Build and Auto-optimize LLM Applications ⚡ + ⚡ Say Goodbye to Manual Prompting and Vendor Lock-In ⚡

+

- + Try Quickstart in Colab

- All Documentation | - Models | + View Documentation +

@@ -57,14 +58,21 @@

-

+ + +

+

+AdalFlow is a PyTorch-like library to build and auto-optimize any LM workflows, from Chatbots, RAG, to Agents. +

+ @@ -73,7 +81,24 @@ For AI researchers, product teams, and software engineers who want to learn the PyPI Downloads --> +# Why AdalFlow + +1. **Say goodbye to manual prompting**: AdalFlow provides a unified auto-differentiative framework for both zero-shot optimization and few-shot prompt optimization. Our research, ``LLM-AutoDiff`` and ``Learn-to-Reason Few-shot In Context Learning``, achieve the highest accuracy among all auto-prompt optimization libraries. +2. **Switch your LLM app to any model via a config**: AdalFlow provides `Model-agnostic` building blocks for LLM task pipelines, ranging from RAG, Agents to classical NLP tasks. + +

+ AdalFlow Auto-optimization +

+ +

+ AdalFlow Optimized Prompt +

+ + +View [Documentation](https://adalflow.sylph.ai) # Quick Start @@ -85,12 +110,13 @@ Install AdalFlow with pip: pip install adalflow ``` -Please refer to the [full installation guide](https://adalflow.sylph.ai/get_started/installation.html) for more details. -[Package changelog](https://github.com/SylphAI-Inc/AdalFlow/blob/main/adalflow/CHANGELOG.md). + +View [Quickstart](https://colab.research.google.com/drive/1_YnD4HshzPRARvishoU4IA-qQuX9jHrT?usp=sharing): Learn AdalFlow end-to-end experience in 15 mins. -* Try the [Building Quickstart](https://colab.research.google.com/drive/1TKw_JHE42Z_AWo8UuRYZCO2iuMgyslTZ?usp=sharing) in Colab to see how AdalFlow can build the task pipeline, including Chatbot, RAG, agent, and structured output. -* Try the [Optimization Quickstart](https://colab.research.google.com/github/SylphAI-Inc/AdalFlow/blob/main/notebooks/qas/adalflow_object_count_auto_optimization.ipynb) to see how AdalFlow can optimize the task pipeline. + @@ -107,47 +133,9 @@ We work closely with the [**VITA Group** at University of Texas at Austin](https For collaboration, contact [Li Yin](https://www.linkedin.com/in/li-yin-ai/). -# Why AdalFlow - -1. **Say goodbye to manual prompting**: AdalFlow provides a unified auto-differentiative framework for both zero-shot optimization and few-shot prompt optimization. Our research, ``LLM-AutoDiff`` and ``Learn-to-Reason Few-shot In Context Learning``, achieve the highest accuracy among all auto-prompt optimization libraries. -2. **Switch your LLM app to any model via a config**: AdalFlow provides `Model-agnostic` building blocks for LLM task pipelines, ranging from RAG, Agents to classical NLP tasks. - - - - - - - - - - - - -

- AdalFlow Auto-optimization -

- -

- AdalFlow Optimized Prompt -

- - - -Further reading: [Use Cases](https://adalflow.sylph.ai/use_cases/) - -## Light, Modular, and Model-Agnostic Task Pipeline + - +Further reading: [How We Started](https://www.linkedin.com/posts/li-yin-ai_both-ai-research-and-engineering-use-pytorch-activity-7189366364694892544-Uk1U?utm_source=share&utm_medium=member_desktop),[Design Philosophy](https://adalflow.sylph.ai/tutorials/lightrag_design_philosophy.html) and [Class hierarchy](https://adalflow.sylph.ai/tutorials/class_hierarchy.html). -Further reading: [How We Started](https://www.linkedin.com/posts/li-yin-ai_both-ai-research-and-engineering-use-pytorch-activity-7189366364694892544-Uk1U?utm_source=share&utm_medium=member_desktop), [Design Philosophy](https://adalflow.sylph.ai/tutorials/lightrag_design_philosophy.html) and [Class hierarchy](https://adalflow.sylph.ai/tutorials/class_hierarchy.html). - ## Unified Framework for Auto-Optimization - + To optimize your pipeline, simply define a ``Parameter`` and pass it to AdalFlow's ``Generator``. You use `PROMPT` for prompt tuning via textual gradient descent and `DEMO` for few-shot demonstrations. - We let you **diagnose**, **visualize**, **debug**, and **train** your pipeline. - ### **Trainable Task Pipeline** @@ -234,15 +188,13 @@ Just define it as a ``Parameter`` and pass it to AdalFlow's ``Generator``. AdalFlow AdalComponent & Trainer

+ --> - -# - # Documentation AdalFlow full documentation available at [adalflow.sylph.ai](https://adalflow.sylph.ai/): -- [How We Started](https://www.linkedin.com/posts/li-yin-ai_both-ai-research-and-engineering-use-pytorch-activity-7189366364694892544-Uk1U?utm_source=share&utm_medium=member_desktop) + # AdalFlow: A Tribute to Ada Lovelace diff --git a/adalflow/CHANGELOG.md b/adalflow/CHANGELOG.md index 408f1da0..9d00ef54 100644 --- a/adalflow/CHANGELOG.md +++ b/adalflow/CHANGELOG.md @@ -1,3 +1,11 @@ + +## [1.0.4] - 2025-02-13 + +### Modified +- `Embedder` and `BatchEmbedder` changed to `DataComponent`. + +### model_client (added) +- `list_models` method. ## [1.0.2] - 2025-02-02 ### Added - Added `TogetherClient` to support the Together API. diff --git a/adalflow/adalflow/__init__.py b/adalflow/adalflow/__init__.py index 2e8513fa..e3065464 100644 --- a/adalflow/adalflow/__init__.py +++ b/adalflow/adalflow/__init__.py @@ -1,4 +1,4 @@ -__version__ = "1.0.3" +__version__ = "1.0.4" from adalflow.core.component import ( Component, @@ -21,7 +21,7 @@ Document, ) from adalflow.core.model_client import ModelClient -from adalflow.core.embedder import Embedder +from adalflow.core.embedder import Embedder, BatchEmbedder # parser from adalflow.core.string_parser import ( @@ -65,16 +65,15 @@ GroqAPIClient, OllamaClient, TransformersClient, - AnthropicAPIClient, CohereAPIClient, BedrockAPIClient, DeepSeekClient, TogetherClient, + AnthropicAPIClient, ) # data pipeline from adalflow.components.data_process.text_splitter import TextSplitter -from adalflow.components.data_process.data_components import ToEmbeddings __all__ = [ "Component", @@ -96,6 +95,7 @@ "ModelClient", "Generator", "Embedder", + "BatchEmbedder", "Retriever", "Parameter", "AdalComponent", @@ -146,4 +146,5 @@ "CohereAPIClient", "BedrockAPIClient", "TogetherClient", + "AnthropicAPIClient", ] diff --git a/adalflow/adalflow/components/agent/react.py b/adalflow/adalflow/components/agent/react.py index 7d2bb72e..9a302383 100644 --- a/adalflow/adalflow/components/agent/react.py +++ b/adalflow/adalflow/components/agent/react.py @@ -111,7 +111,6 @@ "kwargs": {{history.action.kwargs}}", {% endif %} "Observation": "{{history.observation}}" - ------------------------ {% endfor %} diff --git a/adalflow/adalflow/components/data_process/__init__.py b/adalflow/adalflow/components/data_process/__init__.py index 4e8948e1..bfa78e3e 100644 --- a/adalflow/adalflow/components/data_process/__init__.py +++ b/adalflow/adalflow/components/data_process/__init__.py @@ -1,11 +1,11 @@ """Components here are used for data processing/transformation.""" from .text_splitter import TextSplitter -from .data_components import ToEmbeddings, RetrieverOutputToContextStr +from .data_components import RetrieverOutputToContextStr, ToEmbeddings from adalflow.utils.registry import EntityMapping -__all__ = ["TextSplitter", "ToEmbeddings", "RetrieverOutputToContextStr"] +__all__ = ["TextSplitter", "RetrieverOutputToContextStr", "ToEmbeddings"] for name in __all__: EntityMapping.register(name, globals()[name]) diff --git a/adalflow/adalflow/components/data_process/data_components.py b/adalflow/adalflow/components/data_process/data_components.py index 700c4e5e..be8a906b 100644 --- a/adalflow/adalflow/components/data_process/data_components.py +++ b/adalflow/adalflow/components/data_process/data_components.py @@ -88,7 +88,7 @@ def __call__(self, input: ToEmbeddingsInputType) -> ToEmbeddingsOutputType: # convert documents to a list of strings embedder_input: BatchEmbedderInputType = [chunk.text for chunk in output] outputs: BatchEmbedderOutputType = self.batch_embedder(input=embedder_input) - # n them back to the original order along with its query + # put them back to the original order along with its query for batch_idx, batch_output in tqdm( enumerate(outputs), desc="Adding embeddings to documents from batch" ): diff --git a/adalflow/adalflow/components/model_client/__init__.py b/adalflow/adalflow/components/model_client/__init__.py index 224c9557..4fdd07c8 100644 --- a/adalflow/adalflow/components/model_client/__init__.py +++ b/adalflow/adalflow/components/model_client/__init__.py @@ -81,6 +81,10 @@ "adalflow.components.model_client.together_client.TogetherClient", OptionalPackages.TOGETHER, ) +AzureAIClient = LazyImport( + "adalflow.components.model_client.azureai_client.AzureAIClient", + OptionalPackages.AZURE, +) get_first_message_content = LazyImport( "adalflow.components.model_client.openai_client.get_first_message_content", OptionalPackages.OPENAI, diff --git a/adalflow/adalflow/components/model_client/groq_client.py b/adalflow/adalflow/components/model_client/groq_client.py index 9f0e723c..10e07b8a 100644 --- a/adalflow/adalflow/components/model_client/groq_client.py +++ b/adalflow/adalflow/components/model_client/groq_client.py @@ -163,3 +163,16 @@ def to_dict(self) -> Dict[str, Any]: ] # unserializable object output = super().to_dict(exclude=exclude) return output + + def list_models(self): + return self.sync_client.models.list() + + +if __name__ == "__main__": + + from adalflow.utils import setup_env + + setup_env() + + client = GroqAPIClient() + print(client.list_models()) diff --git a/adalflow/adalflow/components/model_client/ollama_client.py b/adalflow/adalflow/components/model_client/ollama_client.py index bd0f7571..c39a3d18 100644 --- a/adalflow/adalflow/components/model_client/ollama_client.py +++ b/adalflow/adalflow/components/model_client/ollama_client.py @@ -64,6 +64,13 @@ class OllamaClient(ModelClient): - [Download Ollama app] Go to https://github.com/ollama/ollama?tab=readme-ov-file to download the Ollama app (command line tool). Choose the appropriate version for your operating system. + One way to do is to run the following command: + + .. code-block:: shell + + curl -fsSL https://ollama.com/install.sh | sh + ollama serve + - [Pull a model] Run the following command to pull a model: diff --git a/adalflow/adalflow/components/model_client/openai_client.py b/adalflow/adalflow/components/model_client/openai_client.py index b483e666..0e77b1f7 100644 --- a/adalflow/adalflow/components/model_client/openai_client.py +++ b/adalflow/adalflow/components/model_client/openai_client.py @@ -574,3 +574,18 @@ def _prepare_image_content( # model_client_dict = model_client.to_dict() # from_dict_model_client = OpenAIClient.from_dict(model_client_dict) # assert model_client_dict == from_dict_model_client.to_dict() + + +if __name__ == "__main__": + import adalflow as adal + + # setup env or pass the api_key + from adalflow.utils import setup_env + + setup_env() + + openai_llm = adal.Generator( + model_client=adal.OpenAIClient(), model_kwargs={"model": "gpt-3.5-turbo"} + ) + resopnse = openai_llm(prompt_kwargs={"input_str": "What is LLM?"}) + print(resopnse) diff --git a/adalflow/adalflow/core/embedder.py b/adalflow/adalflow/core/embedder.py index ca6d5cac..d968a045 100644 --- a/adalflow/adalflow/core/embedder.py +++ b/adalflow/adalflow/core/embedder.py @@ -12,7 +12,7 @@ BatchEmbedderInputType, BatchEmbedderOutputType, ) -from adalflow.core.component import Component +from adalflow.core.component import DataComponent import adalflow.core.functional as F __all__ = ["Embedder", "BatchEmbedder"] @@ -20,7 +20,7 @@ log = logging.getLogger(__name__) -class Embedder(Component): +class Embedder(DataComponent): r""" A user-facing component that orchestrates an embedder model via the model client and output processors. @@ -39,14 +39,14 @@ class Embedder(Component): model_type: ModelType = ModelType.EMBEDDER model_client: ModelClient - output_processors: Optional[Component] + output_processors: Optional[DataComponent] def __init__( self, *, model_client: ModelClient, model_kwargs: Dict[str, Any] = {}, - output_processors: Optional[Component] = None, + output_processors: Optional[DataComponent] = None, ) -> None: super().__init__(model_kwargs=model_kwargs) @@ -192,7 +192,7 @@ def _extra_repr(self) -> str: return s -class BatchEmbedder(Component): +class BatchEmbedder(DataComponent): __doc__ = r"""Adds batching to the embedder component. Args: diff --git a/adalflow/adalflow/core/generator.py b/adalflow/adalflow/core/generator.py index 3bd4ae95..863f47bd 100644 --- a/adalflow/adalflow/core/generator.py +++ b/adalflow/adalflow/core/generator.py @@ -94,8 +94,9 @@ class Generator(GradComponent, CachedEngine, CallbackManager): trainable_params (Optional[List[str]], optional): The list of trainable parameters. Defaults to []. Note: - The output_processors will be applied to the string output of the model completion. And the result will be stored in the data field of the output. + 1. The output_processors will be applied to the string output of the model completion. And the result will be stored in the data field of the output. And we encourage you to only use it to parse the response to data format you will use later. + 2. For structured output, you should avoid using `stream` as the output_processors can only be run after all the data is available. """ model_type: ModelType = ModelType.LLM @@ -335,7 +336,7 @@ def _post_call(self, completion: Any) -> GeneratorOutput: r"""Get string completion and process it with the output_processors.""" # parse chat completion will only fill the raw_response output: GeneratorOutput = self.model_client.parse_chat_completion(completion) - # Now adding the data filed to the output + # Now adding the data field to the output data = output.raw_response if self.output_processors: if data: @@ -1187,7 +1188,6 @@ def _extra_repr(self) -> str: ] s += f"trainable_prompt_kwargs={prompt_kwargs_repr}" - s += f", prompt={self.prompt}" return s def to_dict(self) -> Dict[str, Any]: diff --git a/adalflow/adalflow/core/model_client.py b/adalflow/adalflow/core/model_client.py index 320c34d7..a967ca67 100644 --- a/adalflow/adalflow/core/model_client.py +++ b/adalflow/adalflow/core/model_client.py @@ -119,3 +119,9 @@ def _track_usage(self, **kwargs): def __call__(self, *args, **kwargs): return super().__call__(*args, **kwargs) + + def list_models(self): + """List all available models from this provider""" + raise NotImplementedError( + f"{type(self).__name__} must implement list_models method" + ) diff --git a/adalflow/adalflow/core/prompt_builder.py b/adalflow/adalflow/core/prompt_builder.py index 71590205..87e8536f 100644 --- a/adalflow/adalflow/core/prompt_builder.py +++ b/adalflow/adalflow/core/prompt_builder.py @@ -223,3 +223,39 @@ def get_jinja2_environment(): return default_environment except Exception as e: raise ValueError(f"Invalid Jinja2 environment: {e}") + + +if __name__ == "__main__": + + import adalflow as adal + + template = r"""{{ task_desc_str }} +{# tools #} +{% if tools %} + +{% for tool in tools %} +{{loop.index}}. {{ tool }} +{% endfor %} +{% endif %} +{{ input_str }} """ + + task_desc_str = "You are a helpful assitant" + + tools = ["google", "wikipedia", "wikidata"] + + prompt = adal.Prompt( + template=template, + prompt_kwargs={ + "task_desc_str": task_desc_str, + "tools": tools, + }, + ) + + print(prompt(input_str="What is the capital of France?")) + + to_dict = prompt.to_dict() + + prompt_restructured = adal.Prompt.from_dict(to_dict) + + print(to_dict) + print(prompt_restructured) diff --git a/adalflow/adalflow/core/types.py b/adalflow/adalflow/core/types.py index 88e473ea..a0718658 100644 --- a/adalflow/adalflow/core/types.py +++ b/adalflow/adalflow/core/types.py @@ -141,7 +141,7 @@ class Usage: @dataclass class EmbedderOutput(DataClass): - __doc__ = r"""Container to hold the response from an Embedder model. Only Per-batch. + __doc__ = r"""Container to hold the response from an Embedder datacomponent for a single batch of input. Data standard for Embedder model output to interact with other components. Batch processing is often available, thus we need a list of Embedding objects. diff --git a/adalflow/adalflow/optim/trainer/trainer.py b/adalflow/adalflow/optim/trainer/trainer.py index 7e548406..db11b5ad 100644 --- a/adalflow/adalflow/optim/trainer/trainer.py +++ b/adalflow/adalflow/optim/trainer/trainer.py @@ -460,6 +460,9 @@ def fit( train_loader = train_loader or self.train_loader train_dataset = train_dataset or self.train_dataset + if not train_loader and not train_dataset: + raise ValueError("train_loader or train_dataset should be provided") + if not train_loader and train_dataset: batch_size = self.train_batch_size @@ -470,7 +473,11 @@ def fit( seed=self.random_seed, ) val_dataset = val_dataset or self.val_dataset + test_dataset = test_dataset or self.test_dataset + + if not val_dataset: + raise ValueError("val_dataset should be provided") # check train_loader and val_dataset and test_dataset, reject tuple if train_loader: exam_batch = next(iter(train_loader)) diff --git a/adalflow/pyproject.toml b/adalflow/pyproject.toml index a7dc9cff..298d6194 100644 --- a/adalflow/pyproject.toml +++ b/adalflow/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "adalflow" -version = "1.0.3" +version = "1.0.4" description = "The Library to Build and Auto-optimize LLM Applications" authors = ["Li Yin "] readme = "README.md" @@ -36,7 +36,7 @@ jsonlines = "^4.0.0" tiktoken = ">=0.3.3" numpy = [ { version = "<2.1.0", markers = "python_version < '3.10'" }, - { version = "*", markers = "python_version >= '3.10'" } + { version = "*", markers = "python_version >= '3.10'" }, ] tqdm = "^4.66.4" PyYAML = ">=6.0.1" @@ -105,10 +105,7 @@ anthropic = ["anthropic"] cohere = ["cohere"] google-generativeai = ["google-generativeai"] ollama = ["ollama"] -azure = [ - "azure-core", - "azure-identity" -] +azure = ["azure-core", "azure-identity"] bedrock = ["boto3"] together = ["together"] mistralai = ["mistralai"] @@ -139,5 +136,5 @@ exclude = ["images"] lint.extend-ignore = [ "E402", # Ignore module-level import issues "E731", - "UP007" # Disable warning for Union types formatting in Python 3.8 + "UP007", # Disable warning for Union types formatting in Python 3.8 ] diff --git a/adalflow/tests/test_trainer.py b/adalflow/tests/test_trainer.py index 09ff1bef..353321e3 100644 --- a/adalflow/tests/test_trainer.py +++ b/adalflow/tests/test_trainer.py @@ -8,43 +8,20 @@ class TestTrainer(unittest.TestCase): def setUp(self): self.train_loader = DataLoader(dataset=[1, 2, 3, 4, 5], batch_size=2) - self.trainer = Trainer( + + def test_no_train_dataset(self): + trainer = Trainer( adaltask=MagicMock(spec=AdalComponent), - train_loader=self.train_loader, - optimizer_type="text-grad", - strategy="random", - max_steps=1000, - num_workers=2, - ckpt_path=None, ) + with self.assertRaises(ValueError): + trainer.fit(train_dataset=None) - # @patch("os.listdir") - # @patch("os.makedirs") - # @patch("os.getcwd", return_value="/fake/dir") - # def test_directory_creation(self, mock_getcwd, mock_makedirs, mock_listdir): - # # mock_getcwd.return_value = "/fake/dir" - # mock_listdir.return_value = [] # Simulate no existing checkpoint files - # hyperparms = self.trainer.gather_trainer_states() - # print(f"hyperparms: {hyperparms}") - - # self.trainer.prep_ckpt_file_path(hyperparms) - # print(f"ckpt_path: {self.trainer.ckpt_path}") - # print(f"adaltask class: {self.trainer.adaltask.__class__.__name__}") - - # expected_ckpt_path = "/fake/dir/ckpt/AdalComponent" - # self.assertEqual(self.trainer.ckpt_path, expected_ckpt_path) - # mock_makedirs.assert_called_once_with(expected_ckpt_path, exist_ok=True) - - # # check file naming - # print(f"ckpt_file: {self.trainer.ckpt_file}") - # self.assertTrue( - # self.trainer.ckpt_file.startswith(expected_ckpt_path), - # "Checkpoint file path does not start with expected path", - # ) - # self.assertTrue( - # "run_1.json" in self.trainer.ckpt_file, - # "Checkpoint file path does not end with expected filename", - # ) + def test_no_val_dataset(self): + trainer = Trainer( + adaltask=MagicMock(spec=AdalComponent), + ) + with self.assertRaises(ValueError): + trainer.fit(train_dataset=[1, 2, 3, 4, 5], val_dataset=None) if __name__ == "__main__": diff --git a/docs/poetry.lock b/docs/poetry.lock index df7c63dc..35ba47e8 100644 --- a/docs/poetry.lock +++ b/docs/poetry.lock @@ -1135,6 +1135,23 @@ test-downstream = ["aiobotocore (>=2.5.4,<3.0.0)", "dask-expr", "dask[dataframe, test-full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "cloudpickle", "dask", "distributed", "dropbox", "dropboxdrivefs", "fastparquet", "fusepy", "gcsfs", "jinja2", "kerchunk", "libarchive-c", "lz4", "notebook", "numpy", "ocifs", "pandas", "panel", "paramiko", "pyarrow", "pyarrow (>=1)", "pyftpdlib", "pygit2", "pytest", "pytest-asyncio (!=0.22.0)", "pytest-benchmark", "pytest-cov", "pytest-mock", "pytest-recording", "pytest-rerunfailures", "python-snappy", "requests", "smbprotocol", "tqdm", "urllib3", "zarr", "zstandard"] tqdm = ["tqdm"] +[[package]] +name = "furo" +version = "2024.8.6" +description = "A clean customisable Sphinx documentation theme." +optional = false +python-versions = ">=3.8" +files = [ + {file = "furo-2024.8.6-py3-none-any.whl", hash = "sha256:6cd97c58b47813d3619e63e9081169880fbe331f0ca883c871ff1f3f11814f5c"}, + {file = "furo-2024.8.6.tar.gz", hash = "sha256:b63e4cee8abfc3136d3bc03a3d45a76a850bada4d6374d24c1716b0e01394a01"}, +] + +[package.dependencies] +beautifulsoup4 = "*" +pygments = ">=2.7" +sphinx = ">=6.0,<9.0" +sphinx-basic-ng = ">=1.0.0.beta2" + [[package]] name = "google-ai-generativelanguage" version = "0.6.6" @@ -4276,6 +4293,23 @@ docs = ["sphinxcontrib-websupport"] lint = ["flake8 (>=6.0)", "importlib-metadata (>=6.0)", "mypy (==1.10.1)", "pytest (>=6.0)", "ruff (==0.5.2)", "sphinx-lint (>=0.9)", "tomli (>=2)", "types-docutils (==0.21.0.20240711)", "types-requests (>=2.30.0)"] test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=8.0)", "setuptools (>=70.0)", "typing_extensions (>=4.9)"] +[[package]] +name = "sphinx-basic-ng" +version = "1.0.0b2" +description = "A modern skeleton for Sphinx themes." +optional = false +python-versions = ">=3.7" +files = [ + {file = "sphinx_basic_ng-1.0.0b2-py3-none-any.whl", hash = "sha256:eb09aedbabfb650607e9b4b68c9d240b90b1e1be221d6ad71d61c52e29f7932b"}, + {file = "sphinx_basic_ng-1.0.0b2.tar.gz", hash = "sha256:9ec55a47c90c8c002b5960c57492ec3021f5193cb26cebc2dc4ea226848651c9"}, +] + +[package.dependencies] +sphinx = ">=4.0" + +[package.extras] +docs = ["furo", "ipython", "myst-parser", "sphinx-copybutton", "sphinx-inline-tabs"] + [[package]] name = "sphinx-copybutton" version = "0.5.2" @@ -4319,6 +4353,26 @@ theme-pydata = ["pydata-sphinx-theme (>=0.15.2,<0.16.0)"] theme-rtd = ["sphinx-rtd-theme (>=2.0,<3.0)"] theme-sbt = ["sphinx-book-theme (>=1.1,<2.0)"] +[[package]] +name = "sphinx-tabs" +version = "3.4.7" +description = "Tabbed views for Sphinx" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sphinx-tabs-3.4.7.tar.gz", hash = "sha256:991ad4a424ff54119799ba1491701aa8130dd43509474aef45a81c42d889784d"}, + {file = "sphinx_tabs-3.4.7-py3-none-any.whl", hash = "sha256:c12d7a36fd413b369e9e9967a0a4015781b71a9c393575419834f19204bd1915"}, +] + +[package.dependencies] +docutils = "*" +pygments = "*" +sphinx = ">=1.8" + +[package.extras] +code-style = ["pre-commit (==2.13.0)"] +testing = ["bs4", "coverage", "pygments", "pytest (>=7.1,<8)", "pytest-cov", "pytest-regressions", "rinohtype"] + [[package]] name = "sphinxcontrib-applehelp" version = "2.0.0" @@ -5263,4 +5317,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = ">=3.9, <4.0" -content-hash = "1c14e7dd7f2c0e157567d430f827366a23677e51216c9838278d49c9249ef34b" +content-hash = "bd1fb04dee7faad72dd62b76c7118a38eb1077d902f08f11b886cfdb8277a202" diff --git a/docs/pyproject.toml b/docs/pyproject.toml index 123a6114..f85d6d7c 100644 --- a/docs/pyproject.toml +++ b/docs/pyproject.toml @@ -13,6 +13,8 @@ packages = [{ from = "_dummy", include = "dummy" }] # empty packages python = ">=3.9, <4.0" adalflow = { path = "../adalflow", develop = true } # Follow latest adalflow version pyvis = "^0.3.2" +sphinx-tabs = "^3.4.7" +furo = "^2024.8.6" [tool.poetry.group.doc.dependencies] # Sphinx documentation dependencies diff --git a/docs/source/_static/css/custom.css b/docs/source/_static/css/custom.css index 73ee6a04..41c1dadb 100644 --- a/docs/source/_static/css/custom.css +++ b/docs/source/_static/css/custom.css @@ -41,7 +41,40 @@ display: none; } */ +/* GitHub sidebar link */ +.sidebar-github-section { + padding: 1rem 1.5rem; + border-bottom: 1px solid var(--color-sidebar-background-border); +} + +.sidebar-github-link { + display: flex; + align-items: center; + gap: 0.75rem; + color: var(--color-brand-primary) !important; + text-decoration: none; + font-weight: 500; + transition: all 0.2s ease; +} + +.sidebar-github-link:hover { + opacity: 0.8; + transform: translateX(3px); +} + +.sidebar-github-link i { + font-size: 1.2rem; +} +.github-text { + font-size: 0.9rem; +} + +.sidebar-brand .logo { + height: 42px; + width: auto; + margin: 0 auto 1rem; +} /* .copyright { text-align: center; @@ -51,6 +84,16 @@ p { margin-bottom: 1.15rem; } +/* Reduce code font size */ +.highlight pre { + font-size: 0.7em !important; +} + +/* Optional: Reduce line spacing */ +.highlight { + line-height: 1.2; +} + html[data-theme=light] { --pst-color-secondary: #3d3d3d; /*change the secondary color, header link to gray */ diff --git a/docs/source/_static/images/AdalFlow.svg b/docs/source/_static/images/AdalFlow.svg new file mode 100644 index 00000000..97c8b6f8 --- /dev/null +++ b/docs/source/_static/images/AdalFlow.svg @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/source/_static/images/AdalFlow_black_bg.svg b/docs/source/_static/images/AdalFlow_black_bg.svg new file mode 100644 index 00000000..cee9ebc1 --- /dev/null +++ b/docs/source/_static/images/AdalFlow_black_bg.svg @@ -0,0 +1 @@ + diff --git a/docs/source/_static/images/embedder.png b/docs/source/_static/images/embedder.png new file mode 100644 index 00000000..b615505a Binary files /dev/null and b/docs/source/_static/images/embedder.png differ diff --git a/docs/source/_templates/github-link.html b/docs/source/_templates/github-link.html new file mode 100644 index 00000000..2d722415 --- /dev/null +++ b/docs/source/_templates/github-link.html @@ -0,0 +1,6 @@ + diff --git a/docs/source/conf.py b/docs/source/conf.py index 985250d0..07386475 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -5,7 +5,7 @@ sys.path.insert(0, os.path.abspath("../../adalflow/adalflow")) project = "AdalFlow" -copyright = "2024, SylphAI, Inc" +copyright = "2025, SylphAI, Inc" author = "SylphAI, Inc" extensions = [ @@ -23,6 +23,9 @@ "sphinx_copybutton", "nbsphinx", "sphinx_search.extension", + "sphinx_tabs.tabs", + "sphinx_design", + "sphinx_copybutton", # "sphinx_sitemap", ] @@ -32,13 +35,28 @@ exclude_patterns = ["adalflow/tests"] -html_theme = "pydata_sphinx_theme" +html_theme = "furo" + html_show_sourcelink = False -html_logo = "./_static/images/adalflow-logo.png" +html_static_path = ["_static"] + # autodoc_mock_imports = ["datasets"] html_theme_options = { + "light_logo": "images/AdalFlow.svg", # For light mode + "dark_logo": "images/AdalFlow_black_bg.svg", # For dark mode + "top_of_page_button": "edit", # Remove default Furo buttons + "light_css_variables": { + "color-brand-primary": "#FF6F00", + "color-brand-content": "#1E2A38", + }, + "dark_css_variables": { + "color-brand-primary": "#FF8F00", + "color-brand-content": "#CFD8DC", + }, + "dark_mode_code_blocks": False, # Remove mixed code block styling + "theme_switcher": ["light", "dark"], # Show only two theme options "collapse_navigation": False, "navigation_depth": 8, "icon_links": [ @@ -103,10 +121,24 @@ } -html_static_path = ["_static"] +html_sidebars = { + "**": [ + "sidebar/brand.html", + "github-link.html", # New custom link + "sidebar/search.html", + "sidebar/scroll-start.html", + "sidebar/navigation.html", + "sidebar/ethical-ads.html", + "sidebar/scroll-end.html", + ] +} + +html_css_files = [ + "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" +] -# html_short_title = "Lightrag" -html_title = "AdalFlow: The Library to Build and Auto-Optimize LLM Task Pipelines" +html_title = "Build and Optimize LM Workflows" +# html_title = "AdalFlow: The Library to Build and Auto-Optimize LLM Task Pipelines" html_favicon = "./_static/images/LightRAG-logo-circle.png" # html_context = { # "docstitle": "AdalFlow: The Lightning Library for LLM Applications" diff --git a/docs/source/get_started/index.rst b/docs/source/get_started/index.rst index da2e2ea1..8c57557c 100644 --- a/docs/source/get_started/index.rst +++ b/docs/source/get_started/index.rst @@ -1,8 +1,6 @@ Get Started ============================= -Here is the content of our documentation project. - .. toctree:: @@ -10,5 +8,5 @@ Here is the content of our documentation project. :caption: Get Started installation - adalflow_in_15mins + adalflow_in_15mins community diff --git a/docs/source/get_started/installation.rst b/docs/source/get_started/installation.rst index ac59d6f1..57bab9a8 100644 --- a/docs/source/get_started/installation.rst +++ b/docs/source/get_started/installation.rst @@ -1,91 +1,319 @@ -Installation -============ +Install AdalFlow and Run your LM +========================= .. _Installation: -AdalFlow is available in Python. -1. Install AdalFlow -~~~~~~~~~~~~~~~~~~~~ - -To install the package, run: - -.. code-block:: bash - - pip install adalflow - -If you know you will need `openai` and `faiss-cpu`, you can do so with: - -.. code-block:: bash - - pip install adalflow[openai,faiss-cpu] - -.. note:: - Check the `Optional Packages` section for more information on the available packages. - -2. Set up API keys -~~~~~~~~~~~~~~~~~~~ - -A ``.env`` file is recommended. -You can have it at your project root directory. -Here is an example: - - - -.. code-block:: bash - - OPENAI_API_KEY=YOUR_API_KEY_IF_YOU_USE_OPENAI - GROQ_API_KEY=YOUR_API_KEY_IF_YOU_USE_GROQ - ANTHROPIC_API_KEY=YOUR_API_KEY_IF_YOU_USE_ANTHROPIC - GOOGLE_API_KEY=YOUR_API_KEY_IF_YOU_USE_GOOGLE - COHERE_API_KEY=YOUR_API_KEY_IF_YOU_USE_COHERE - HF_TOKEN=YOUR_API_KEY_IF_YOU_USE_HF +.. raw:: html + +
+ +
+ ℹ️ Getting Started: Install AdalFlow and set up your LM +
+ + +
+ pip install -U adalflow + +
+ + +
+ + + + + +
+ + +
+

Setup `OPENAI_API_KEY` in your `.env` file or pass the `api_key` to the client.

+
+ +

+   import adalflow as adal
+
+   # setup env or pass the api_key to client
+   from adalflow.utils import setup_env
 
+   setup_env()
 
-3. Load environment variables
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+   openai_llm = adal.Generator(
+      model_client=adal.OpenAIClient(), model_kwargs={"model": "gpt-3.5-turbo"}
+   )
+   resopnse = openai_llm(prompt_kwargs={"input_str": "What is LLM?"})
+                
+
+
-You can use the following import: +
+

Setup `GROQ_API_KEY` in your `.env` file or pass the `api_key` to the client.

-.. code-block:: python +
+ +

+   import adalflow as adal
 
+   # setup env or pass the api_key to client
    from adalflow.utils import setup_env
 
    setup_env()
 
-Or, you can load it yourself with ``python-dotenv``:
+   llama_llm = adal.Generator(
+      model_client=adal.GroqAPIClient(), model_kwargs={"model": "llama3-8b-8192"}
+   )
+   resopnse = llama_llm(prompt_kwargs={"input_str": "What is LLM?"})
 
-.. code-block:: python
 
-   from dotenv import load_dotenv
-   load_dotenv()  # This loads the environment variables from `.env`.
+                
+
+
-This setup ensures that AdalFlow can access all necessary configurations during runtime. +
+

Setup `ANTHROPIC_API_KEY` in your `.env` file or pass the `api_key` to the client.

+
+ +

+   import adalflow as adal
 
-4. Install Optional Packages
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-
-AdalFlow currently has built-in support for (1) OpenAI, Groq, Anthropic, Google, and Cohere, and (2) FAISS and Transformers.
-You can find all optional packages at :class:`OptionalPackages`.
-Make sure to install the necessary SDKs for the components you plan to use.
-Here is the list of our tested versions:
+   # setup env or pass the api_key to client
+   from adalflow.utils import setup_env
 
+   setup_env()
 
-.. code-block::
+   anthropic_llm = adal.Generator(
+      model_client=adal.AnthropicAPIClient(), model_kwargs={"model": "claude-3-opus-20240229"}
+   )
+   resopnse = anthropic_llm(prompt_kwargs={"input_str": "What is LLM?"})
+
+                
+
+
+ +
+

Ollama is one option. You can also use `vllm` or HuggingFace `transformers`.

+
+ +

+   # Download Ollama command line tool
+   curl -fsSL https://ollama.com/install.sh | sh
+
+   # Pull the model to use
+   ollama pull llama3
+                
+
+

Use it in the same way as other providers.

+
+ +

+   import adalflow as adal
+
+   llama_llm = adal.Generator(
+      model_client=adal.OllamaClient(), model_kwargs={"model": "llama3"}
+   )
+   resopnse = llama_llm(prompt_kwargs={"input_str": "What is LLM?"})
+                
+
+
+ + + +
+

For other providers, check the official documentation.

+
+
+ + + + + + + + + + + + + +.. Or, you can load it yourself with ``python-dotenv``: + +.. .. code-block:: python + +.. from dotenv import load_dotenv +.. load_dotenv() # This loads the environment variables from `.env`. + +.. This setup ensures that AdalFlow can access all necessary configurations during runtime. + +.. 4. Install Optional Packages +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +.. AdalFlow currently has built-in support for (1) OpenAI, Groq, Anthropic, Google, and Cohere, and (2) FAISS and Transformers. +.. You can find all optional packages at :class:`OptionalPackages`. +.. Make sure to install the necessary SDKs for the components you plan to use. +.. Here is the list of our tested versions: - openai = "^1.12.0" - groq = "^0.5.0" - faiss-cpu = "^1.8.0" - sqlalchemy = "^2.0.30" - pgvector = "^0.3.1" - torch = "^2.3.1" - anthropic = "^0.31.1" - google-generativeai = "^0.7.2" - cohere = "^5.5.8" -You can install the optional packages with either ``pip install package_name`` or ``pip install adalflow[package_name]``. + +.. openai = "^1.12.0" +.. groq = "^0.5.0" +.. faiss-cpu = "^1.8.0" +.. sqlalchemy = "^2.0.30" +.. pgvector = "^0.3.1" +.. torch = "^2.3.1" +.. anthropic = "^0.31.1" +.. google-generativeai = "^0.7.2" +.. cohere = "^5.5.8" + +.. You can install the optional packages with either ``pip install package_name`` or ``pip install adalflow[package_name]``. diff --git a/docs/source/index.rst b/docs/source/index.rst index 8b98cb15..ff27a5dc 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,285 +1,565 @@ -.. image:: https://raw.githubusercontent.com/SylphAI-Inc/AdalFlow/main/docs/source/_static/images/adalflow-logo.png - :width: 100% - :alt: Adalflow Logo +*"Say Goodbye to Manual Prompting"* +Getting Started: Install AdalFlow and Run Your First Query +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. raw:: html +.. code-block:: bash -

+ pip install -U adalflow +LM apps often relys on other cloud or local model services and each of them often has their own Python SDKs. +AdalFlow handles all of them as optional packages, so that developers only need to install the ones they need. - - Try Quickstart in Colab - -

+.. tabs:: -
- PyPI Version - - GitHub Repo - - GitHub Stars - - discord-invite - - License -
+ .. tab:: OpenAI + Set up `OPENAI_API_KEY` in your `.env` file or pass the `api_key` directly to the client. + .. code-block:: python -.. PyPI Downloads + import adalflow as adal + from adalflow.utils import setup_env + setup_env() -.. raw:: html + openai_llm = adal.Generator( + model_client=adal.OpenAIClient(), model_kwargs={"model": "gpt-3.5-turbo"} + ) + resopnse = openai_llm(prompt_kwargs={"input_str": "What is LLM?"}) -
-

- ⚡ The Library to Build and Auto-optimize Any LLM Task Pipeline ⚡ -

-

- Embracing a design philosophy similar to PyTorch, AdalFlow is powerful, light, modular, and robust. -

-
-
-

- ⚡ The Library to Build and Auto-optimize Any LLM Task Pipeline ⚡ -

-

- Embracing a design philosophy similar to PyTorch, AdalFlow is powerful, light, modular, and robust. -

-
- - - - -..
-..

-.. AdalFlow helps developers build and optimize Retriever-Agent-Generator pipelines.
-.. Embracing a design philosophy similar to PyTorch, it is light, modular, and robust, with a 100% readable codebase. -..

-..
- - - -.. Embracing the PyTorch-like design philosophy, AdalFlow is a powerful, light, modular, and robust library to build and auto-optimize any LLM task pipeline. -.. AdalFlow is a powerful library to build and auto-optimize any LLM task pipeline with PyTorch-like design philosophy. - - -.. # TODO: make this using the new tool, show both the building and the training. - - -.. .. grid:: 1 -.. :gutter: 1 - -.. .. grid-item-card:: PyTorch - -.. .. code-block:: python - -.. import torch -.. import torch.nn as nn - -.. class Net(nn.Module): -.. def __init__(self): -.. super(Net, self).__init__() -.. self.conv1 = nn.Conv2d(1, 32, 3, 1) -.. self.conv2 = nn.Conv2d(32, 64, 3, 1) -.. self.dropout1 = nn.Dropout2d(0.25) -.. self.dropout2 = nn.Dropout2d(0.5) -.. self.fc1 = nn.Linear(9216, 128) -.. self.fc2 = nn.Linear(128, 10) - -.. def forward(self, x): -.. x = self.conv1(x) -.. x = self.conv2(x) -.. x = self.dropout1(x) -.. x = self.dropout2(x) -.. x = self.fc1(x) -.. return self.fc2(x) - -.. .. grid-item-card:: AdalFlow - -.. .. code-block:: python - -.. import adalflow as adal -.. from adalflow.components.model_client import GroqAPIClient - - -.. class SimpleQA(adal.Component): -.. def __init__(self): -.. super().__init__() -.. template = r""" -.. You are a helpful assistant. -.. -.. User: {{input_str}} -.. You: -.. """ -.. self.generator = adal.Generator( -.. model_client=GroqAPIClient(), -.. model_kwargs={"model": "llama3-8b-8192"}, -.. template=template, -.. ) - -.. def call(self, query): -.. return self.generator({"input_str": query}) - -.. async def acall(self, query): -.. return await self.generator.acall({"input_str": query}) -.. raw:: html -

- Light, Modular, and Model-agnositc Task Pipeline -

+ .. tab:: Groq -.. Light, Modular, and Model-agnositc Task Pipeline -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Set up `GROQ_API_KEY` in your `.env` file or pass the `api_key` directly to the client. -LLMs are like water; AdalFlow help developers quickly shape them into any applications, from GenAI applications such as chatbots, translation, summarization, code generation, RAG, and autonomous agents to classical NLP tasks like text classification and named entity recognition. + .. code-block:: python -Only two fundamental but powerful base classes: `Component` for the pipeline and `DataClass` for data interaction with LLMs. -The result is a library with bare minimum abstraction, providing developers with *maximum customizability*. + import adalflow as adal + from adalflow.utils import setup_env -You have full control over the prompt template, the model you use, and the output parsing for your task pipeline. + setup_env() + groq_llm = adal.Generator( + model_client=adal.GroqAPIClient(), model_kwargs={"model": "llama3-8b-8192"} + ) + resopnse = groq_llm(prompt_kwargs={"input_str": "What is LLM?"}) -.. figure:: /_static/images/AdalFlow_task_pipeline.png - :alt: AdalFlow Task Pipeline - :align: center + .. tab:: Anthropic + Set up `ANTHROPIC_API_KEY` in your `.env` file or pass the `api_key` directly to the client. + .. code-block:: python -.. raw:: html + import adalflow as adal + from adalflow.utils import setup_env -

- Unified Framework for Auto-Optimization -

+ setup_env() -.. AdalFlow provides token-efficient and high-performing prompt optimization within a unified framework. -.. To optimize your pipeline, simply define a ``Parameter`` and pass it to our ``Generator``. -.. Wheter it is to optimize the task instruction or the few-shot demonstrations, our unified framework -.. provides you easy way to ``diagnose``, ``visualize``, ``debug``, and to ``train`` your pipeline. + anthropic_llm = adal.Generator( + model_client=adal.AnthropicAPIClient(), model_kwargs={"model" "claude-3-opus-20240229"} + ) + resopnse = anthropic_llm(prompt_kwargs={"input_str": "What is LLM?"}) + + .. tab:: Local + + Ollama is one option. You can also use `vllm` or HuggingFace `transformers`. + + First, install `ollama` and prepare the model. + + .. code-block:: python + + # Download Ollama command line tool + curl -fsSL https://ollama.com/install.sh | sh + + # Pull the model to use + ollama pull llama3 + + Then, use the model in the same way as the cloud-based LLMs. + + .. code-block:: python -.. This trace graph shows how our auto-diffentiation works :doc:`trace_graph <../tutorials/trace_graph>`. + import adalflow as adal -AdalFlow provides token-efficient and high-performing prompt optimization within a unified framework. -To optimize your pipeline, simply define a ``Parameter`` and pass it to our ``Generator``. -Whether you need to optimize task instructions or few-shot demonstrations, -our unified framework offers an easy way to **diagnose**, **visualize**, **debug**, and **train** your pipeline. + llama_llm = adal.Generator( + model_client=adal.OllamaClient(), model_kwargs={"model": "llama3"} + ) + resopnse = llama_llm(prompt_kwargs={"input_str": "What is LLM?"}) -This trace graph demonstrates how our auto-differentiation works: :doc:`trace_graph <../tutorials/trace_graph>` + .. tab:: Other -**Trainable Task Pipeline** -Just define it as a ``Parameter`` and pass it to our ``Generator``. + For other providers, check the :ref:`All Integrations ` page. -.. figure:: /_static/images/Trainable_task_pipeline.png - :alt: AdalFlow Trainable Task Pipeline - :align: center +Try AdalFlow experience end to end in 15 minutes with the `Colab Notebook `__. +Build your LLM workflow with full control over Prompt, Model, and Output Data Parsing +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -**AdalComponent & Trainer** +* `Prompt` is the new programming language. All LLM app patterns, from RAG to agents, are implemented via subprompts. AdalFlow leverages `jinja2` template engine to help developers define the overall prompt structure for various applications. +* `DataClass` helps developers define the input and output data structure for the LLM pipeline. +* `Component` is where we define the LLM workflow, which supports both train and eval modes via `forward` and `call` methods. -``AdalComponent`` acts as the `interpreter` between task pipeline and the trainer, defining training and validation steps, optimizers, evaluators, loss functions, backward engine for textual gradients or tracing the demonstrations, the teacher generator. -.. figure:: /_static/images/trainer.png - :alt: AdalFlow AdalComponent & Trainer - :align: center +.. tabs:: + .. tab:: Question Answering + With `template`, you know exactly what is passed to the LLM. You also have full control over the output parser by defining it yourself. + `system_prompt` is claimed as a `adal.Parameter` to help training. You can also directly pass `string`. -.. and Customizability + .. code-block:: python + template = r""" + {{system_prompt}} + + + {{input_str}} + """ -.. Light -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. AdalFlow shares similar design pattern as `PyTorch` for deep learning modeling. -.. We provide developers with fundamental building blocks of *100% clarity and simplicity*. + @adal.func_to_data_component + def parse_integer_answer(answer: str): + numbers = re.findall(r"\d+", answer) + return int(numbers[-1]) -.. - Only two fundamental but powerful base classes: `Component` for the pipeline and `DataClass` for data interaction with LLMs. -.. - A highly readable codebase and less than two levels of class inheritance. :doc:`tutorials/class_hierarchy`. -.. - We maximize the library's tooling and prompting capabilities to minimize the reliance on LLM API features such as tools and JSON format. -.. - The result is a library with bare minimum abstraction, providing developers with *maximum customizability*. + class ObjectCountTaskPipeline(adal.Component): + def __init__(self, model_client: adal.ModelClient, model_kwargs: Dict): + super().__init__() + system_prompt = adal.Parameter( + data="You will answer a reasoning question. Think step by step. The last line of your response should be of the following format: 'Answer: $VALUE' where VALUE is a numerical value.", + role_desc="To give task instruction to the language model in the system prompt", + requires_opt=True, + param_type=ParameterType.PROMPT, + ) + self.llm_counter = adal.Generator( + model_client=model_client, + model_kwargs=model_kwargs, + template=template, + prompt_kwargs={ + "system_prompt": system_prompt, + }, + output_processors=parse_integer_answer, + ) + def bicall( + self, question: str, id: str = None + ) -> Union[adal.GeneratorOutput, adal.Parameter]: + output = self.llm_counter(prompt_kwargs={"input_str": question}, id=id) + return output + Running the workflow: -.. Modular -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + .. code-block:: python -.. AdalFlow resembles PyTorch in the way that we provide a modular and composable structure for developers to build and to optimize their LLM applications. + object_count_pipeline = ObjectCountTaskPipeline(**model_config) -.. - `Component` and `DataClass` are to AdalFlow for LLM Applications what `module` and `Tensor` are to PyTorch for deep learning modeling. -.. - `ModelClient` to bridge the gap between the LLM API and the AdalFlow pipeline. -.. - `Orchestrator` components like `Retriever`, `Embedder`, `Generator`, and `Agent` are all model-agnostic (you can use the component on different models from different providers). + question = "I have a flute, a piano, a trombone, four stoves, a violin, an accordion, a clarinet, a drum, two lamps, and a trumpet. How many musical instruments do I have?" + response = object_count_pipeline(question) + Check out the :ref:`Full Tutorial ` for more details. -.. Similar to the PyTorch `module`, our `Component` provides excellent visualization of the pipeline structure. -.. .. code-block:: + .. tab:: Classification -.. SimpleQA( -.. (generator): Generator( -.. model_kwargs={'model': 'llama3-8b-8192'}, -.. (prompt): Prompt( -.. template: -.. You are a helpful assistant. -.. -.. User: {{input_str}} -.. You: -.. , prompt_variables: ['input_str'] -.. ) -.. (model_client): GroqAPIClient() -.. ) -.. ) -.. To switch to `gpt-3.5-turbo` by OpenAI, simply update the `model_client`` and `model_kwargs` in the Generator component. + We use `jinja2` to programmatically formulate our classification description. We use `DataClassParser` for structured data output. -.. .. code-block:: python -.. from adalflow.components.model_client import OpenAIClient + .. code-block:: python -.. self.generator = adal.Generator( -.. model_client=OpenAIClient(), -.. model_kwargs={"model": "gpt-3.5-turbo"}, -.. template=template, -.. ) + template = r"""; + {{system_prompt}} + {% if output_format_str is not none %} + {{output_format_str}} + {% endif %} + + + {{input_str}} + """ + + task_desc_template = r"""You are a classifier. Given a question, you need to classify it into one of the following classes: + Format: class_index. class_name, class_description + {% if classes %} + {% for class in classes %} + {{loop.index-1}}. {{class.label}}, {{class.desc}} + {% endfor %} + {% endif %} + - Do not try to answer the question.""" + + @dataclass + class TRECExtendedData(adal.DataClass): + question: str = field( + metadata={"desc": "The question to be classified"}, default=None) + rationale: str = field( + metadata={ + "desc": "Your step-by-step reasoning to classify the question to class_name" + }, default=None) + class_name: Literal["ABBR", "ENTY", "DESC", "HUM", "LOC", "NUM"] = field( + metadata={"desc": "The class name"}, default=None) + + __input_fields__ = ["question"] + __output_fields__ = ["rationale", "class_name"] + + + class TRECClassifierStructuredOutput(adal.Component): + def __init__(self, model_client: adal.ModelClient, model_kwargs: Dict): + super().__init__() + label_desc = [ + {"label": label, "desc": desc} + for label, desc in zip(_COARSE_LABELS, _COARSE_LABELS_DESC) + ] + task_desc_str = adal.Prompt( + template=task_desc_template, prompt_kwargs={"classes": label_desc} + )() + parser = adal.DataClassParser( + data_class=TRECExtendedData, return_data_class=True, format_type="yaml" + ) + prompt_kwargs = { + "system_prompt": adal.Parameter( + data=task_desc_str, + role_desc="Task description", + requires_opt=True, + param_type=adal.ParameterType.PROMPT, + ), + "output_format_str": parser.get_output_format_str(), + } + self.llm = adal.Generator( + model_client=model_client, + model_kwargs=model_kwargs, + prompt_kwargs=prompt_kwargs, + template=template, + output_processors=self.parser, + ) + + def bicall( + self, question: str, id: Optional[str] = None + ) -> Union[adal.GeneratorOutput, adal.Parameter]: + output = self.llm(prompt_kwargs={"input_str": question}, id=id) + return output + + Check out the :ref:`Full Tutorial ` for more details. + + .. tab:: RAG + + + RAG consists of two parts: (1) A data pipeline run off-line to prepare the database with indexces for search and (2) The RAG component that receives a query, retrieves and responds. + + .. code-block:: python + + # Part 1: Data Pipeline using AdalFlow DataComponents + def prepare_data_pipeline(): + splitter = TextSplitter(**configs["text_splitter"]) + embedder = adal.Embedder( + model_client=configs["embedder"]["model_client"](), + model_kwargs=configs["embedder"]["model_kwargs"], + ) + embedder_transformer = ToEmbeddings( + embedder=embedder, batch_size=configs["embedder"]["batch_size"] + ) + data_transformer = adal.Sequential( + splitter, embedder_transformer + ) # sequential will chain together splitter and embedder + return data_transformer + + def transform_documents_and_save_to_db( + documents: List[Document], db_path: str + ) -> adal.LocalDB: + data_transformer = prepare_data_pipeline() + + db = LocalDB() + db.register_transformer(transformer=data_transformer, key="split_and_embed") + db.load(documents) + db.transform(key="split_and_embed") + os.makedirs(os.path.dirname(db_path), exist_ok=True) + db.save_state(filepath=db_path) + return db + + # Part 2: RAG Component using AdalFlow Components + + class RAG(adal.Component): + def __init__(self): + super().__init__() + self.embedder = adal.Embedder( + model_client=configs["embedder"]["model_client"](), + model_kwargs=configs["embedder"]["model_kwargs"], + ) + self.initialize_db_manager() + data_parser = adal.DataClassParser(data_class=RAGAnswer, return_data_class=True) + self.generator = adal.Generator( + template=RAG_TEMPLATE, + prompt_kwargs={ + "output_format_str": data_parser.get_output_format_str(), + "system_prompt": system_prompt, + }, + model_client=configs["generator"]["model_client"](), + model_kwargs=configs["generator"]["model_kwargs"], + output_processors=data_parser, + ) + + def initialize_db_manager(self): + self.db_manager = DatabaseManager() + self.transformed_docs = [] + + def prepare_retriever(self, repo_url_or_path: str): + self.initialize_db_manager() + self.transformed_docs = self.db_manager.prepare_database(repo_url_or_path) + self.retriever = FAISSRetriever( + **configs["retriever"], + embedder=self.embedder, + documents=self.transformed_docs, + ) + + def call(self, query: str) -> Any: + retrieved_documents = self.retriever(query) + prompt_kwargs = { + "input_str": query, + "contexts": retrieved_documents[0].documents, + } + response = self.generator( + prompt_kwargs=prompt_kwargs, + ) + return response.data, retrieved_documents + + Check out a real-world RAG project `GithubChat `__ or this `colab notebook `__. + + .. tab:: Agent + + We can use both another `component`(trainable) or another function as a tool. Here is an example of an AgenticRAG. + + .. code-block:: python + + class AgenticRAG(adal.Component): + def __init__(self, model_client, model_kwargs): + super().__init__() + self.dspy_retriever = DspyRetriever(top_k=2) + + def dspy_retriever_as_tool( + input: str, + id: Optional[str] = None, + ) -> List[str]: + r"""Retrieves the top 2 passages from using input as the query. + Ensure you get all the context to answer the original question. + """ + output = self.dspy_retriever(input=input, id=id) + parsed_output = output + if isinstance(output, adal.Parameter): + parsed_output = output.data.documents + return parsed_output + documents = parsed_output.documents + return documents + + tools = [ + FunctionTool(dspy_retriever_as_tool, component=self.dspy_retriever), + ] + + self.agent = ReActAgent( + max_steps=3, + add_llm_as_fallback=False, + tools=tools, + model_client=model_client, + model_kwargs=model_kwargs, + ) + + def bicall(self, input: str, id: str = None) -> str: + out = self.agent(input=input, id=id) + return out + + Check out the `source code `__. + + + +Auto-optimize your LLM workflow with both Prompt Tuning and Few-shot Learning +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* When a `Parameter` is defined as `PROMPT`, AdalFlow will automatically optimize the prompt on your training dataset via `LLM-AutoDiff `__. +* When a `Parameter` is defined as `DEMOS`, `Few-Shot Bootstrap Learning `__ is applied. +* When both are defined, AdalFlow will use both to find the best performing prompts. + + +.. tabs:: + + .. tab:: AdalComponent + + The `Trainer` requires the following key elements from your workflow: + + 1. **Task Workflow** – Defined as a `Component` from the previous step. + 2. **Model Configuration** – Includes settings for the optimizer and bootstrap teacher. It is recommended to use high-performing LLM models such as `4o`, `o1`, `o3-mini`, or `r1`. + 3. **Evaluation Metrics** – The criteria used to assess model performance. + 4. **LLM Workflow Execution** – Instructions on how to invoke your LLM workflow in both evaluation and training modes. + + + Developers can organize the above information in `AdalComponent`, similar to how `PyTorch LightningModule` is structured. + + + .. code-block:: python + + class TrecClassifierAdal(adal.AdalComponent): + def __init__( + self, + model_client: adal.ModelClient, + model_kwargs: Dict, + teacher_model_config: Dict, + backward_engine_model_config: Dict, + text_optimizer_model_config: Dict, + ): + task = TRECClassifierStructuredOutput(model_client, model_kwargs) + eval_fn = AnswerMatchAcc(type="exact_match").compute_single_item + loss_fn = adal.EvalFnToTextLoss( + eval_fn=eval_fn, + eval_fn_desc="exact_match: 1 if str(y) == str(y_gt) else 0. When the LLM prediction failed with format parsing which results with errors, we set y_pred = -1", + ) + super().__init__( + task=task, + eval_fn=eval_fn, + loss_fn=loss_fn, + backward_engine_model_config=backward_engine_model_config, + text_optimizer_model_config=text_optimizer_model_config, + teacher_model_config=teacher_model_config, + ) + + def prepare_task(self, sample: TRECExtendedData): + return self.task.bicall, {"question": sample.question, "id": sample.id} + def prepare_eval( + self, sample: TRECExtendedData, y_pred: adal.GeneratorOutput + ) -> float: + y_label = -1 + if y_pred and y_pred.data is not None and y_pred.data.class_name is not None: + y_label = y_pred.data.class_name + return self.eval_fn, {"y": y_label, "y_gt": sample.class_name} + + def prepare_loss( + self, sample: TRECExtendedData, y_pred: adal.Parameter, *args, **kwargs + ) -> Tuple[Callable[..., Any], Dict]: + full_response = y_pred.data + y_label = -1 + if (full_response and full_response.data is not None + and full_response.data.class_name is not None): + y_label = full_response.data.class_name + + y_pred.eval_input = y_label + y_gt = adal.Parameter( + name="y_gt", + data=sample.class_name, + eval_input=sample.class_name, + requires_opt=False, + ) + return self.loss_fn, { + "kwargs": {"y": y_pred, "y_gt": y_gt}, + "id": sample.id, + } + Check out the :ref:`Full Tutorial ` for more details. + .. tab:: Load Data and Train -.. Robust -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. Our simplicity did not come from doing less. -.. On the contrary, we have to do more and go deeper and wider on any topic to offer developers *maximum control and robustness*. + Trainer takes `AdalComponent` and datasets as input. It trains the prompts and returns the best checkpoint. + + .. code-block:: python + + def load_datasets(): + train_data = TrecDataset(split="train") + val_data = TrecDataset(split="val")ßß + test_data = TrecDataset(split="test") + return train_data, val_data, test_data + + adal_component = TrecClassifierAdal( + model_client=model_client, + model_kwargs=model_kwargs, + text_optimizer_model_config=deepseek_r1_model, + backward_engine_model_config=gpt_4o_model, + teacher_model_config=gpt_4o_model, + ) + trainer = adal.Trainer( + adaltask=adal_component, + max_steps=12, + raw_shots=1, + bootstrap_shots=1, + ) + + train_dataset, val_dataset, test_dataset = load_datasets() + ckpt, _ = trainer( + train_dataset=train_dataset, + val_dataset=val_dataset, + test_dataset=test_dataset, + ) + + :ref:`Full Tutorial here `. + + + + + +.. .. raw:: html + +..

+.. Light, Modular, and Model-agnositc Task Pipeline + +..

+ +.. Light, Modular, and Model-agnositc Task Pipeline +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. LLMs are like water; AdalFlow help developers quickly shape them into any applications, from GenAI applications such as chatbots, translation, summarization, code generation, RAG, and autonomous agents to classical NLP tasks like text classification and named entity recognition. + + +.. Only two fundamental but powerful base classes: `Component` for the pipeline and `DataClass` for data interaction with LLMs. +.. The result is a library with bare minimum abstraction, providing developers with *maximum customizability*. + +.. You have full control over the prompt template, the model you use, and the output parsing for your task pipeline. + + +.. .. figure:: /_static/images/AdalFlow_task_pipeline.png +.. :alt: AdalFlow Task Pipeline +.. :align: center + + + +.. .. raw:: html + +..

+.. Unified Framework for Auto-Optimization +..

+ + +.. AdalFlow provides token-efficient and high-performing prompt optimization within a unified framework. +.. To optimize your pipeline, simply define a ``Parameter`` and pass it to our ``Generator``. +.. Whether you need to optimize task instructions or few-shot demonstrations, +.. our unified framework offers an easy way to **diagnose**, **visualize**, **debug**, and **train** your pipeline. -.. - LLMs are sensitive to the prompt. We allow developers full control over their prompts without relying on LLM API features such as tools and JSON format with components like `Prompt`, `OutputParser`, `FunctionTool`, and `ToolManager`. -.. - Our goal is not to optimize for integration, but to provide a robust abstraction with representative examples. See this in :ref:`ModelClient` and :ref:`Retriever` components. -.. - All integrations, such as different API SDKs, are formed as optional packages but all within the same library. You can easily switch to any models from different providers that we officially support. +.. This trace graph demonstrates how our auto-differentiation works: :doc:`trace_graph <../tutorials/trace_graph>` +.. **Trainable Task Pipeline** + +.. Just define it as a ``Parameter`` and pass it to our ``Generator``. + + +.. .. figure:: /_static/images/Trainable_task_pipeline.png +.. :alt: AdalFlow Trainable Task Pipeline +.. :align: center + + +.. **AdalComponent & Trainer** + +.. ``AdalComponent`` acts as the `interpreter` between task pipeline and the trainer, defining training and validation steps, optimizers, evaluators, loss functions, backward engine for textual gradients or tracing the demonstrations, the teacher generator. + + +.. .. figure:: /_static/images/trainer.png +.. :alt: AdalFlow AdalComponent & Trainer +.. :align: center Unites Research and Production -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Our team has experience in both AI research and production. We are building a library that unites the two worlds, forming a healthy LLM application ecosystem. @@ -288,12 +568,41 @@ We are building a library that unites the two worlds, forming a healthy LLM appl - Our 100% control and clarity of the source code further make it easy for product teams to build on and for researchers to extend their new methods. +Community +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +*Learn, share and collaborate with the AdalFlow AI community* + +.. You can join our community on various platforms: + +.. * `Discord `_ +.. * `GitHub Discussion `_ + +.. raw:: html + + + + + + + .. toctree:: :glob: :maxdepth: 1 :hidden: - get_started/index + new_tutorials/index .. toctree:: @@ -304,44 +613,26 @@ We are building a library that unites the two worlds, forming a healthy LLM appl integrations/index .. toctree:: - :glob: :maxdepth: 1 :hidden: - tutorials/index - .. :caption: Tutorials - How each part works - .. :hidden: + use_cases/index -.. .. Hide the use cases for now .. toctree:: + :glob: :maxdepth: 1 - :caption: Use Cases - How different parts are used to build various LLM applications :hidden: - use_cases/index - - - .. :caption: Benchmarks - - .. Manually add documents for the code in benchmarks - - -.. :glob: -.. :maxdepth: 1 -.. :caption: Resources - -.. resources/index + contributor/index -.. hide the for contributors now .. toctree:: :glob: :maxdepth: 1 - :caption: For Contributors :hidden: - contributor/index + tutorials/index diff --git a/docs/source/integrations/integrations.rst b/docs/source/integrations/integrations.rst index b25ec3c0..53de64b6 100644 --- a/docs/source/integrations/integrations.rst +++ b/docs/source/integrations/integrations.rst @@ -1,9 +1,9 @@ .. _get_started-integrations: -Integrations -=========== +All Providers +================== -AdalFlow integrates with many popular AI and database platforms to provide a comprehensive solution for your LLM applications. +AdalFlow integrates with many popular AI and database platforms to provide a comprehensive solution for your LM applications. Model Providers ------------- @@ -173,24 +173,6 @@ Embedding and Reranking Models - Chunking large text into smaller segments for more efficient and accurate embedding, retrieval, and LLM context processing. - :ref:`TextSplitter ` -.. .. list-table:: Embeddings and Reranking -.. :widths: 25 55 20 -.. :header-rows: 1 - -.. * - **Major Class** -.. - **Description** -.. - **Reference** -.. * - **Embedding Models** -.. - Models that convert text (or other data) into high-dimensional vectors. A core step for vector similarity or semantic understanding. -.. Examples include OpenAI Embeddings, Hugging Face transformers, etc. -.. - “core.embedder.Embedder” docs -.. * - **Re-ranking Models** -.. - Models that reorder or refine retrieved candidates based on more advanced semantic understanding or specialized metrics. -.. Improves final retrieval accuracy. -.. - “Rerank” doc references (BERT, Cohere, etc.) -.. * - **LLM-based Retrieval** (optional) -.. - Using an LLM directly for retrieval or re-ranking. Often more expensive but can be more accurate for certain tasks. -.. - Could be integrated in your LLM pipeline .. raw:: html diff --git a/docs/source/new_tutorials/embedder.rst b/docs/source/new_tutorials/embedder.rst new file mode 100644 index 00000000..6d95b91f --- /dev/null +++ b/docs/source/new_tutorials/embedder.rst @@ -0,0 +1,258 @@ +.. raw:: html + + + + +.. _tutorials-embedder: + +Embedder +============ + +.. figure:: /_static/images/embedder.png + :align: center + :alt: AdalFlow generator design + :width: 700px + + Embedder - Converts a list of strings into a list of vectors with embedding models. + +Introduction +------------------ + +:class:`core.embedder.Embedder` allows developers to use different embedding models easily. +Like `Generator`, `Embedder` is a user-facing component that orchestrates embedding models via `ModelClient` and `output_processors`, it outputs :class:`EmbedderOutput`. +Unlike `Generator` which is trainable, `Embedder` is just a `DataComponent` that only transforms input strings into embeddings/vectors. + + +By switching the ``ModelClient``, you can use different embedding models in your task pipeline easily, or even embedd different data such as text, image, etc. +For end developers, most likely you want to use :class:`ToEmbeddings` together with `Embedder` as it (1) directly supports a sequence of `Document` objects, and (2) it handles batch processing out of box. +:class:`Document` is a container that AdalFlow uses to also process data in :class:`TextSplitter` which are often required in a RAG pipeline. +.. EmbedderOutput +.. -------------- + +.. :class:`core.types.EmbedderOutput` is a standard output format of ``Embedder``. It is a subclass of `DataClass` and it contains the following core fields: + +.. - ``data``: a list of embeddings, each embedding if of type :class:`core.types.Embedding`. +.. - ``error``: Error message if any error occurs during the model inference stage. Failure in the output processing stage will raise an exception instead of setting this field. +.. - ``raw_response``: Used for failed model inference. + +.. Additionally, we add three properties to the ``EmbedderOutput``: + +.. - ``length``: The number of embeddings in the ``data``. +.. - ``embedding_dim``: The dimension of the embeddings in the ``data``. +.. - ``is_normalized``: Whether the embeddings are normalized to unit vector or not using ``numpy``. + + + +We currently support `all embedding models from OpenAI `_ and `'thenlper/gte-base' `_ from HuggingFace `transformers `_. +We will use these two to demonstrate how to use ``Embedder``. For the local model, you need to ensure you have ``transformers`` installed. + +Use Embedder +---------------------------- +OpenAI Embedding Model +^^^^^^^^^^^^^^^^^^^^^ + + +Before you start ensure you config the API key either in the environment variable or `.env` file, or directly pass it to the ``OpenAIClient``. + +.. code-block:: python + + from adalflow.core.embedder import Embedder + from adalflow.components.model_client import OpenAIClient + from adalflow.utils import setup_env # ensure you setup OPENAI_API_KEY in your project .env file + + setup_env() + + model_kwargs = { + "model": "text-embedding-3-small", + "dimensions": 256, + "encoding_format": "float", + } + + query = "What is LLM?" + + queries = [query] * 100 + + + embedder = Embedder(model_client=OpenAIClient(), model_kwargs=model_kwargs) + + +You can use ``print(embedder)`` to visualize the structure. The output will be: + +.. code-block:: + + Embedder( + model_kwargs={'model': 'text-embedding-3-small', 'dimensions': 256, 'encoding_format': 'float'}, + (model_client): OpenAIClient() + ) + +**Embed single query**: +Run the embedder and print the length and embedding dimension of the output. + +.. code-block:: python + + output = embedder(query) + print(output.length, output.embedding_dim, output.is_normalized) + # 1 256 True + + +**Embed a single batch of queries**: + +.. code-block:: python + + output = embedder(queries) + print(output.length, output.embedding_dim) + # 100 256 + +Local Model +^^^^^^^^^^^^^^^^^^^^^ +Set up the embedder with the local model. + +.. code-block:: python + + from adalflow.core.embedder import Embedder + from adalflow.components.model_client import TransformersClient + + model_kwargs = {"model": "thenlper/gte-base"} + local_embedder = Embedder(model_client=TransformersClient(), model_kwargs=model_kwargs) + +Now, call the embedder with the same query and queries. + +.. code-block:: python + + output = local_embedder(query) + print(output.length, output.embedding_dim, output.is_normalized) + # 1 768 True + + output = local_embedder(queries) + print(output.length, output.embedding_dim, output.is_normalized) + # 100 768 True + +Use Output Processors +^^^^^^^^^^^^^^^^^^^^^ + +If we want to decreate the embedding dimension to only 256 to save memory, we can customize an additional output processing step and pass it to embedder via the ``output_processors`` argument. + +.. code-block:: python + + from adalflow.core.types import Embedding, EmbedderOutput + from adalflow.core.functional import normalize_vector + from typing import List + from adalflow.core.component import DataComponent + from copy import deepcopy + + class DecreaseEmbeddingDim(DataComponent): + def __init__(self, old_dim: int, new_dim: int, normalize: bool = True): + super().__init__() + self.old_dim = old_dim + self.new_dim = new_dim + self.normalize = normalize + assert self.new_dim < self.old_dim, "new_dim should be less than old_dim" + + def call(self, input: List[Embedding]) -> List[Embedding]: + output: EmbedderOutput = deepcopy(input) + for embedding in output.data: + old_embedding = embedding.embedding + new_embedding = old_embedding[: self.new_dim] + if self.normalize: + new_embedding = normalize_vector(new_embedding) + embedding.embedding = new_embedding + return output.data + + def _extra_repr(self) -> str: + repr_str = f"old_dim={self.old_dim}, new_dim={self.new_dim}, normalize={self.normalize}" + return repr_str + +This output procesor will process on the ``data`` field of the ``EmbedderOutput``, which is of type ``List[Embedding]``. Thus we have ``input: List[Embedding] -> output: List[Embedding]`` in the ``call`` method. +Putting it all together, we can create a new embedder with the output processor. + +.. code-block:: python + + local_embedder_256 = Embedder( + model_client=TransformersClient(), + model_kwargs=model_kwargs, + output_processors=DecreaseEmbeddingDim(768, 256), + ) + print(local_embedder_256) + +The structure looks like: + +.. code-block:: + + Embedder( + model_kwargs={'model': 'thenlper/gte-base'}, + (model_client): TransformersClient() + (output_processors): DecreaseEmbeddingDim(old_dim=768, new_dim=256, normalize=True) + ) + +Run a query: + +.. code-block:: python + + output = local_embedder_256(query) + print(output.length, output.embedding_dim, output.is_normalized) + # 1 256 True + + +ToEmbeddings +---------------- +Once we know how to config and set up Embedder, we can use :class:`ToEmbeddings` to directly convert a list of `Document` objects into embeddings. + +.. code-block:: python + + from adalflow.components.data_process.data_components import ToEmbeddings + from adalflow.core.types import Document + + to_embeddings = ToEmbeddings(embedder=embedder, batch_size=100) + + docs = [Document(text="What is LLM?")] * 1000 + output = to_embeddings(docs) + print(f"Response - Length: {len(response)})") + # 1000 + +[Optional]BatchEmbedder +-------------------------- +Especially in data processing pipelines, you can often have more than 1000 queries to embed. We need to chunk our queries into smaller batches to avoid memory overflow. +:class:`core.embedder.BatchEmbedder` is designed to handle this situation. For now, the code is rather simple, but in the future it can be extended to support multi-processing when you use AdalFlow in production data pipeline. + +The BatchEmbedder orchestrates the ``Embedder`` and handles the batching process. To use it, you need to pass the ``Embedder`` and the batch size to the constructor. + +.. code-block:: python + + from adalflow.core.embedder import BatchEmbedder + + batch_embedder = BatchEmbedder(embedder=local_embedder, batch_size=100) + + queries = [query] * 1000 + + response = batch_embedder(queries) + # 100%|██████████| 11/11 [00:04<00:00, 2.59it/s] + + +.. note:: + To integrate your own embedding model or from API providers, you need to implement your own subclass of ``ModelClient``. + +.. admonition:: References + :class: highlight + + - transformers: https://huggingface.co/docs/transformers/en/index + - thenlper/gte-base model: https://huggingface.co/thenlper/gte-base + + +.. admonition:: API reference + :class: highlight + + - :class:`core.embedder.Embedder` + - :class:`core.embedder.BatchEmbedder` + - :class:`core.types.EmbedderOutput` + - :class:`core.types.Embedding` + - :class:`components.model_client.openai_client.OpenAIClient` + - :class:`components.model_client.transformers_client.TransformersClient` + - :class:`core.functional.normalize_vector` diff --git a/docs/source/new_tutorials/generator.rst b/docs/source/new_tutorials/generator.rst new file mode 100644 index 00000000..88be6230 --- /dev/null +++ b/docs/source/new_tutorials/generator.rst @@ -0,0 +1,354 @@ +.. _generator: + +.. raw:: html + + + + +Generator +========= + +Introduction +--------------------------------------- +.. figure:: /_static/images/generator.png + :align: center + :alt: AdalFlow generator design + :width: 700px + + Generator - The Orchestrator for LLM Prediction + +:class:`Generator` is the most important computation unit in AdalFlow: + +1. It orchestrates and unifies `Prompt`, `ModelClient` (model provider apis), and `output_processors` (parser and structured output) to achieve functionalities such as reasoning, structured output, tool usage, code generation. + With these functionalities, developers can program it to be any LLM applications, from chatbots(with or w.o memory), RAG, to agents. +2. It is a `GradComponnent` that has both `forward` and `backward` methods. Developers can optimize the prompt when it is defined as a `Parameter` and being used together with `Trainer`. + + +Generator has two desirable properties: + +1. Model Agnostic: The Generator levarages a standard interface--`ModelClient`--to interact with different LLM models. This makes it easy to switch between different models simply via configuration. +2. Unified Output: With :class:`GeneratorOutput`, we ensure it can capture the final parsed output in ``data``, any error message in ``error``, and the raw response in ``raw_response``. ``id`` and ``usage`` are also included for tracking purposes. + +.. note:: + + Users should decide what to do when the error occurs. + +Basic Usage +--------------------------------------- + +Using the Generator is simple: + +.. code-block:: python + + from adalflow.core import Generator + from adalflow.components.model_client.openai_client import OpenAIClient + + llm = Generator( + model_client=OpenAIClient(), + model_kwargs={ + "model": "o3-mini", + } + ) + + prompt_kwargs = {"input_str": "What is LLM?"} + + response = llm(prompt_kwargs=prompt_kwargs) # or llm.call in eval and llm.forward in training + print(response) + +The generator comes with a default prompt template :class:`DEFAULT_ADALFLOW_SYSTEM_PROMPT`, which can be replaced by user's own template with `Jinja2` syntax. +Here is the printout of the `GeneratorOutput`: + +.. code-block:: + + GeneratorOutput(id=None, data='LLM most commonly stands for "Large Language Model." This is a type of artificial intelligence system that has been trained on vast amounts of text data to understand, generate, and interact using human language. Here are some key points about LLMs:\n\n• They use advanced deep learning techniques (often based on the Transformer architecture) to learn patterns, grammar, context, and semantics in language.\n• Examples of LLMs include models like OpenAI’s GPT series, Google’s BERT, and others.\n• They can perform a wide range of language tasks such as answering questions, summarizing documents, translating languages, writing creative content, and more.\n\nIt’s worth noting that in other contexts, "LLM" might also refer to a Master of Laws degree. However, in discussions related to AI and natural language processing, LLM almost always refers to a Large Language Model.', error=None, usage=CompletionUsage(completion_tokens=570, prompt_tokens=45, total_tokens=615), raw_response='LLM most commonly stands for "Large Language Model." This is a type of artificial intelligence system that has been trained on vast amounts of text data to understand, generate, and interact using human language. Here are some key points about LLMs:\n\n• They use advanced deep learning techniques (often based on the Transformer architecture) to learn patterns, grammar, context, and semantics in language.\n• Examples of LLMs include models like OpenAI’s GPT series, Google’s BERT, and others.\n• They can perform a wide range of language tasks such as answering questions, summarizing documents, translating languages, writing creative content, and more.\n\nIt’s worth noting that in other contexts, "LLM" might also refer to a Master of Laws degree. However, in discussions related to AI and natural language processing, LLM almost always refers to a Large Language Model.', metadata=None) + + +Here is how you can print out the prompt: + +.. code-block:: python + + llm.print_prompt(**prompt_kwargs) + +Now, let's use a simple and customized template to perform a task of counting objects: + + + +.. code-block:: python + + import adalflow as adal + + # the template has three variables: system_prompt, few_shot_demos, and input_str + few_shot_template = r""" + {{system_prompt}} + {# Few shot demos #} + {% if few_shot_demos is not none %} + Here are some examples: + {{few_shot_demos}} + {% endif %} + + + {{input_str}} + + """ + + object_counter = Generator( + model_client=adal.GroqAPIClient(), + model_kwargs={ + "model": "llama3-8b-8192", + }, + template=few_shot_template, + prompt_kwargs={ + "system_prompt": "You will answer a reasoning question. Think step by step. The last line of your response should be of the following format: 'Answer: $VALUE' where VALUE is a numerical value.", + } + ) + + question = "I have a flute, a piano, a trombone, four stoves, a violin, an accordion, a clarinet, a drum, two lamps, and a trumpet. How many musical instruments do I have?" + response = object_counter(prompt_kwargs={"input_str": question}) + print(response) + + prompt = object_counter.print_prompt(input_str=question) + print(prompt) + + +The output will be: + +.. code-block:: + + GeneratorOutput(id=None, data="I'll think step by step!\n\nI'm given a list of items, and I need to identify the musical instruments. Let's go through the list:\n\n* Flute: yes, it's a musical instrument\n* Piano: yes, it's a musical instrument\n* Trombone: yes, it's a musical instrument\n* Violin: yes, it's a musical instrument\n* Accordion: yes, it's a musical instrument\n* Clarinet: yes, it's a musical instrument\n* Drum: yes, it's a musical instrument\n* Trumpet: yes, it's a musical instrument\n\nI've identified 8 musical instruments so far.\n\nNow, let's check if there are any non-musical items on the list:\n\n* Four stoves: no, these are not musical instruments\n* Two lamps: no, these are not musical instruments\n\nSo, I've identified all the musical instruments, and I'm done.\n\nAnswer: 8", error=None, usage=CompletionUsage(completion_tokens=198, prompt_tokens=116, total_tokens=314), raw_response="I'll think step by step!\n\nI'm given a list of items, and I need to identify the musical instruments. Let's go through the list:\n\n* Flute: yes, it's a musical instrument\n* Piano: yes, it's a musical instrument\n* Trombone: yes, it's a musical instrument\n* Violin: yes, it's a musical instrument\n* Accordion: yes, it's a musical instrument\n* Clarinet: yes, it's a musical instrument\n* Drum: yes, it's a musical instrument\n* Trumpet: yes, it's a musical instrument\n\nI've identified 8 musical instruments so far.\n\nNow, let's check if there are any non-musical items on the list:\n\n* Four stoves: no, these are not musical instruments\n* Two lamps: no, these are not musical instruments\n\nSo, I've identified all the musical instruments, and I'm done.\n\nAnswer: 8", metadata=None) + +The prompt will be: + +.. code-block:: + + + You will answer a reasoning question. Think step by step. The last line of your response should be of the following format: 'Answer: $VALUE' where VALUE is a numerical value. + + + I have a flute, a piano, a trombone, four stoves, a violin, an accordion, a clarinet, a drum, two lamps, and a trumpet. How many musical instruments do I have? + + + + +In the next section, we will introduce more advanced features such as structured output, tool usage, and defining trainable prompts. + +Structured Output +--------------------------------------- +First, in the object count example, we want to extract the answer which ideally should be converted to integer. +The best way to do this is to customize a parser that will leverage regular expressions to extract the answer. + + +.. code-block:: python + + import re + + @adal.func_to_data_component + def parse_integer_answer(answer: str): + try: + numbers = re.findall(r"\d+", answer) + if numbers: + answer = int(numbers[-1]) + else: + answer = -1 + except ValueError: + answer = -1 + + return answer + + object_counter = Generator( + model_client=adal.GroqAPIClient(), + model_kwargs={ + "model": "llama3-8b-8192", + }, + template=few_shot_template, + prompt_kwargs={ + "system_prompt": "You will answer a reasoning question. Think step by step. The last line of your response should be of the following format: 'Answer: $VALUE' where VALUE is a numerical value.", + }, + output_processors=parse_integer_answer, + ) + + response = object_counter(prompt_kwargs={"input_str": question}) + print(response) + print(type(response.data)) + +The output will be: + +.. code-block:: + + GeneratorOutput(id=None, data=7, error=None, usage=CompletionUsage(completion_tokens=69, prompt_tokens=116, total_tokens=185), raw_response='The problem asks me to count the number of musical instruments.\n\nI will list down all the instruments I have:\n\n1. Flute\n2. Piano\n3. Trombone\n4. Violin\n5. Accordion\n6. Clarinet\n7. Trumpet\n\nThere are 7 musical instruments. \n\nAnswer: 7', metadata=None) + + +The ``data`` field now is an integer instead of the whole string output. But you can still find all string response from ``raw_response``. + +Now, we can achieve the same result via more advanced data structure. +We will leverage `DataClass` to define a structured output with two fields: ``thought`` and ``answer``. +Then, we leverage :class:`DataClassParser` to parse the output back to the structured data. + +.. code-block:: + + # 1. add an output_format_str variable in the template + template = r""" + {{system_prompt}} + + {{output_format_str}} + + + + {{input_str}} + """ + + + # 2. define the structured output + + from dataclasses import dataclass, field + + @dataclass + class QAOutput(DataClass): + thought: str = field( + metadata={ + "desc": "Your thought process for the question to reach the answer." + } + ) + answer: int = field(metadata={"desc": "The answer to the question."}) + + __output_fields__ = ["thought", "answer"] + + # 3. define the parser + + parser = adal.DataClassParser( + data_class=QAOutput, return_data_class=True, format_type="json" + ) + + object_counter = Generator( + model_client=adal.GroqAPIClient(), + model_kwargs={ + "model": "llama3-8b-8192", + }, + template=template, + prompt_kwargs={ + "system_prompt": "You will answer a reasoning question. Think step by step. ", + "output_format_str": parser.get_output_format_str(), # 4. add the output_format_str in the prompt_kwargs + }, + output_processors=parser, # 5. add the parser as the output_processors + ) + + response = object_counter(prompt_kwargs={"input_str": question}) + print(response) + + object_counter.print_prompt(input_str=question) + + +The output will be: + +.. code-block:: + + GeneratorOutput(id=None, data=customize_template..QAOutput(thought="First, I'll identify the musical instruments in my list. I see flute, piano, trombone, violin, accordion, clarinet, and trumpet, which are all musical instruments. Then, I will count them to find out how many I have. Flute, piano, trombone, violin, accordion, clarinet, and trumpet makes a total of 7 musical instruments.", answer=7), error=None, usage=CompletionUsage(completion_tokens=94, prompt_tokens=229, total_tokens=323), raw_response='```\n{\n "thought": "First, I\'ll identify the musical instruments in my list. I see flute, piano, trombone, violin, accordion, clarinet, and trumpet, which are all musical instruments. Then, I will count them to find out how many I have. Flute, piano, trombone, violin, accordion, clarinet, and trumpet makes a total of 7 musical instruments.",\n "answer": 7\n}', metadata=None) + + Prompt: + ______________________ + + You will answer a reasoning question. Think step by step. + + Your output should be formatted as a standard JSON instance with the following schema: + ``` + { + "thought": "Your thought process for the question to reach the answer. (str) (required)", + "answer": "The answer to the question. (int) (required)" + } + ``` + -Make sure to always enclose the JSON output in triple backticks (```). Please do not add anything other than valid JSON output! + -Use double quotes for the keys and string values. + -DO NOT mistaken the "properties" and "type" in the schema as the actual fields in the JSON output. + -Follow the JSON formatting conventions. + + + + I have a flute, a piano, a trombone, four stoves, a violin, an accordion, a clarinet, a drum, two lamps, and a trumpet. How many musical instruments do I have? + + +From the response we can get ``QAOutput`` in the ``data`` field, which is a structured output with two fields: ``thought`` as string and ``answer`` as integer. +The way we achieve this is via the ``DataClassParser``'s built-in prompt formatting (via ``output_format_str`` variable in the prompt) and parsing as the ``output_processors``. + +**We allow developers to do very complicated data structure and even multi-level of nested data structure.** Check out this `colab example `_ for more details. + + +Trainable Prompt as Parameter +--------------------------------------- +To train the prompt, developers need to define it as ``Parameter``. +For example, if we want to prompt tune the ``system_prompt`` in the object counter example, this is what we do instead: + +.. code-block:: python + + from adalflow.optim.parameter import ParameterType + + system_prompt = adal.Parameter( + data="You will answer a reasoning question. Think step by step. The last line of your response should be of the following format: 'Answer: $VALUE' where VALUE is a numerical value.", + role_desc="To give task instruction to the language model in the system prompt", + requires_opt=True, + param_type=ParameterType.PROMPT, # leverages LLM-AutoDiff to optimize the prompt + instruction_to_optimizer="You can try to show examples to see if it helps.", + ) + +If you want to also leverage Few-shot learning, you can define the ``few_shot_demos`` as another parameter: + + +.. code-block:: python + + few_shot_demos = adal.Parameter( + data=None, + role_desc="To provide few shot demos to the language model", + requires_opt=True, + param_type=ParameterType.DEMOS, + ) + +And then you can pass these parameters to the prompt_kwargs: + +.. code-block:: python + + prompt_kwargs={ + "system_prompt": system_prompt, + "few_shot_demos": few_shot_demos, + } + +By doing so, the trainer will automatically detect these parameters and optimize them accordingly. + + +Tool +--------------------------------------- +LM can use tools in the same way how we did the structured output. +We will need a convenient way to describe each tool or function in the prompt and instruct it using the ``output_format_str`` to manage the function calls. +We have to manage some context variables to achieve the function call. +You can check out :ref:`Tool` for more details. + +Examples Across the Library +--------------------------------------- + +Besides these examples, LLM is like water, even in our library, we have components that have adpated Generator to various other functionalities. + +- :class:`LLMRetriever` is a retriever that uses Generator to call LLM to retrieve the most relevant documents. +- :class:`DefaultLLMJudge` is a judge that uses Generator to call LLM to evaluate the quality of the response. +- :class:`TGDOptimizer` is an optimizer that uses Generator to call LLM to optimize the prompt. +- :class:`ReAct Agent Planner` is an LLM planner that uses Generator to plan and to call functions in ReAct Agent. + + +.. admonition:: API reference + :class: highlight + + - :class:`core.generator.Generator` + - :class:`core.types.GeneratorOutput` + - :class:`core.default_prompt_template.DEFAULT_ADALFLOW_SYSTEM_PROMPT` + - :class:`core.types.ModelClientType` + - :class:`core.types.ModelType` + - :class:`core.string_parser.JsonParser` + - :class:`core.prompt_builder.Prompt` + - :class:`components.retriever.llm_retriever.LLMRetriever` + - :class:`components.agent.react.ReActAgent` + - :class:`eval.llm_as_judge.DefaultLLMJudge` + - :class:`optim.text_grad.tgd_optimizer.TGDOptimizer` diff --git a/docs/source/new_tutorials/index.rst b/docs/source/new_tutorials/index.rst new file mode 100644 index 00000000..1af7b62b --- /dev/null +++ b/docs/source/new_tutorials/index.rst @@ -0,0 +1,17 @@ + +.. _new_tutorials: + + +Tutorials +============================= + +.. toctree:: + :maxdepth: 1 + :caption: Basics + :hidden: + + introduction + prompt + parser + generator + embedder diff --git a/docs/source/new_tutorials/introduction.rst b/docs/source/new_tutorials/introduction.rst new file mode 100644 index 00000000..bb558fef --- /dev/null +++ b/docs/source/new_tutorials/introduction.rst @@ -0,0 +1,37 @@ +Introduction +================= + +LLM-AutoDiff +----------------- +AdalFlow mainly relys on `LLM-AutoDiff `__ to do automatic prompt engineering(APE). + +Similar to Auto-differentiation in PyTorch, LLM-AutoDiff works by forming a runtime computation graph of prompts, hyperparameters, intermediate outputs, and losses in the `forward` pass. +In the `backward` pass, the `backward engine` LLM we put at each node will work together to identify which prompts are the cause of errors, so that a `feedback`-driven LM optimizer can leverage it to propose new prompts. + + +Components +----------------- +:class:`Component` is to LM task pipelines what `nn.Module` is to PyTorch models. +A component can recursively contain and register other components, allow easy control of (1) `training` and `inference` modes, (2) visualization of the workflow structure, and (3) serialization and deserialization of the component. + + +We require a component to have (1) a `call` method, which will be called during the `inference` time, +and (2) a `forward` method, which will be called during the `training` time and output a `Parameter` object which has the output of the component wrapped in the `data` field. + +There are four main types of components in AdalFlow: + +1. `Component`: the base class of all components. With `forward` and `call` method, or `bicall`(handles both in one method). You use it to put together an LM workflow. +2. `GradComponent`: a subclass of `Component` that has a `backward` method. It defines a unit of computation that are capabalbe of backpropagation. One example is `Generator` and `Retriever`. +3. `DataComponent`: a subclass of `Component` that only has `call` method and does not handle any `Parameter` object. Examples include `Prompt`, `DataClassParser` which only handles the data formatting but rather the transformation. +4. `LossComponent`: a subclass of `Component` that likely takes an evaluation metric function and has a `backward` method. When it is attached to your LM workflow's output, the whole training pipeline is capable of backpropagation. + +Parameters +----------------- +:class:`Parameter` are used to save (1) intermediate forward data and (2) gradients/feedback, and (3) graph information such as `predecessors`. +It also has function like `draw_graph` to help you visualize the structure of your computation graph. + +DataClass and Structured Output +---------------------------------- +:class:`DataClass` is used for developers to define a data model. +Similar to `Pydantic`, it has methods like `to_yaml_signature`, `to_json_signature`, `to_yaml`, `to_json` to help you generate the data model schema and to generate the json/yaml data representation as strings. +It can be best used together with :class:`DataClassParser` for structured output. diff --git a/docs/source/new_tutorials/parser.rst b/docs/source/new_tutorials/parser.rst new file mode 100644 index 00000000..89caeac7 --- /dev/null +++ b/docs/source/new_tutorials/parser.rst @@ -0,0 +1,412 @@ +.. _components-output_parser_note: + +.. raw:: html + + + +Parser and Structured Output +============================== + +Parser is the `interpreter` of the LLM output. + +.. We have three types of parsers: + +.. - **String Parsers**: it simply converts the string to the desired data type. They are located at :ref:`core.string_parser`. +.. - **Output Parsers**: it orchestrates the parsing and output formatting(in yaml, json and more) process. They are located at :ref:`components.output_parsers.outputs`. :class:`JsonOutputParser` and :class:`YamlOutputParser` can work with :ref:`DataClass` for structured output. +.. - **DataClass Parser**: On top of :class:`YamlOutputParser` and :class:`JsonOutputParser`, :class:`DataClassParser` is the most compatible to work with :ref:`DataClass` for structured output. + +Basic Parser +~~~~~~~~~~~~~~ +For basic data formats where you do not need to create a data class for, you can use the following Parsers in the library. + +.. list-table:: + :header-rows: 1 + :widths: 25 25 50 + + * - Parser Class + - Target Python Object + - Description + * - :class:`BooleanParser` + - ``bool`` + - Extracts the first boolean value from the text as ``bool``. Supports both 'True/False' and 'true/false'. + * - :class:`IntParser` + - ``int`` + - Extracts the first integer value from the text as ``int``. + * - :class:`FloatParser` + - ``float`` + - Extracts the first float value from the text as ``float``. + * - :class:`ListParser` + - ``list`` + - Extracts '[]' and parses the first list string from the text. Uses both `json.loads` and `yaml.safe_load`. + * - :class:`JsonParser` + - ``dict`` + - Extracts '[]' and '{}' and parses JSON strings from the text. It resorts to `yaml.safe_load` for robust parsing. + * - :class:`YamlParser` + - ``dict`` + - Extracts ```yaml```, ```yml``` or the whole string and parses YAML strings from the text. + + +Here are some quick demonstrations: + +**BooleanParser** + +.. code-block:: python + + from adalflow.core.string_parser import BooleanParser + + bool_str = "True" + bool_str_2 = "False" + bool_str_3 = "true" + bool_str_4 = "false" + bool_str_5 = "1" # will fail + bool_str_6 = "0" # will fail + bool_str_7 = "yes" # will fail + bool_str_8 = "no" # will fail + + # it will all return True/False + parser = BooleanParser() + print(parser(bool_str)) + print(parser(bool_str_2)) + print(parser(bool_str_3)) + print(parser(bool_str_4)) + +The printout will be: + +.. code-block:: + + True + False + True + False + +Boolean parsers will not work for '1', '0', 'yes', 'no' as they are not the standard boolean values. + + +**IntParser** + +.. code-block:: python + + rom adalflow.core.string_parser import IntParser + + int_str = "42" + int_str_2 = "42.0" + int_str_3 = "42.7" + int_str_4 = "the answer is 42.75" + + # it will all return 42 + parser = IntParser() + print(parser(int_str)) + print(parser(int_str_2)) + print(parser(int_str_3)) + print(parser(int_str_4)) + +The printout will be: + +.. code-block:: + + 42 + 42 + 42 + 42 + +``IntParser`` will return the integer value of the first number in the string, even if it is a float. + + +**FloatParser** + +.. code-block:: python + + from adalflow.core.string_parser import FloatParser + + float_str = "42.0" + float_str_2 = "42" + float_str_3 = "42.7" + float_str_4 = "the answer is 42.75" + + # it will all return 42.0 + parser = FloatParser() + print(parser(float_str)) + print(parser(float_str_2)) + print(parser(float_str_3)) + print(parser(float_str_4)) + +The printout will be: + +.. code-block:: + + 42.0 + 42.0 + 42.7 + 42.75 + + +``FloatParser`` will return the float value of the first number in the string, even if it is an integer. + + +**ListParser** + +.. code-block:: python + + from adalflow.core.string_parser import ListParser + + list_str = '["key", "value"]' + list_str_2 = 'prefix["key", 2]...' + list_str_3 = '[{"key": "value"}, {"key": "value"}]' + + parser = ListParser() + print(parser(list_str)) + print(parser(list_str_2)) + print(parser(list_str_3)) + +The output will be: + +.. code-block:: python + + ['key', 'value'] + ['key', 2] + [{'key': 'value'}, {'key': 'value'}] + + +**JsonParser** + +Even though it can work on lists, it is better to only use it for dictionaries. + +.. code-block:: python + + from adalflow.core.string_parser import JsonParser + + dict_str = '{"key": "value"}' + nested_dict_str = ( + '{"name": "John", "age": 30, "attributes": {"height": 180, "weight": 70}}' + ) + list_str = '["key", 2]' + list_dict_str = '[{"key": "value"}, {"key": "value"}]' + + parser = JsonParser() + print(parser) + print(parser(dict_str)) + print(parser(nested_dict_str)) + print(parser(list_str)) + print(parser(list_dict_str)) + +The output will be: + +.. code-block:: python + + {'key': 'value'} + {'name': 'John', 'age': 30, 'attributes': {'height': 180, 'weight': 70}} + ['key', 2] + [{'key': 'value'}, {'key': 'value'}] + + +**YamlParser** + +Though it works almost on all of the previous examples, it is better to use it for yaml formatted dictionaries. + +.. code-block:: python + + from adalflow.core.string_parser import YamlParser + + yaml_dict_str = "key: value" + yaml_nested_dict_str = ( + "name: John\nage: 30\nattributes:\n height: 180\n weight: 70" + ) + yaml_list_str = "- key\n- value" + + parser = YamlParser() + print(parser) + print(parser(yaml_dict_str)) + print(parser(yaml_nested_dict_str)) + print(parser(yaml_list_str)) + +The output will be: + +.. code-block:: python + + {'key': 'value'} + {'name': 'John', 'age': 30, 'attributes': {'height': 180, 'weight': 70}} + ['key', 'value'] + +.. note:: + All parsers will raise ``ValueError`` if it fails at any step. Developers should process it accordingly. + + +DataClassParser +~~~~~~~~~~~~~~~~~~~~~~~ +For more complicated data structures, we can use :class:`DataClass` to define it. +The usage of it is pretty much the same as native `dataclass` from `dataclasses`. + +Let's try to define a User class: + +.. code-block:: python + + from dataclasses import dataclass, field + from adalflow.core import DataClass + + # no need to use Optional, when default is on, it is optional. + .. code-block:: python + + @dataclass + class SampleDataClass(DataClass): + description: str = field(metadata={"desc": "A sample description"}) + category: str = field(metadata={"desc": "Category of the sample"}) + value: int = field(metadata={"desc": "A sample integer value"}) + status: str = field(metadata={"desc": "Status of the sample"}) + + # input and output fields can work with DataClassParser + __input_fields__ = [ + "description", + "category", + ] + __output_fields__ = ["value", "status"] + + +We have three classes to work with structured data. +They are :class:`DataClassParser`, +:class:`JsonOutputParser`, and `YamlOutputParser`. +`DataClassParser` is the easiest to use. + +Now, lets' create a parser that will use the `SampleDataClass` to parse the output json string back to the data class instance. + +.. code-block:: python + + from adalflow.components.output_parsers import DataClassParser + + parser = DataClassParser(data_class=SampleDataClass, return_data_class=True, format_type="json") + +Let's view the structure of the parser use `print(parser)`. + +The output will be: + +.. code-block:: + + DataClassParser( + data_class=SampleDataClass, format_type=json, return_data_class=True, input_fields=['description', 'category'], output_fields=['value', 'status'] + (_output_processor): JsonParser() + (output_format_prompt): Prompt( + template: Your output should be formatted as a standard JSON instance with the following schema: + ``` + {{schema}} + ``` + -Make sure to always enclose the JSON output in triple backticks (```). Please do not add anything other than valid JSON output! + -Use double quotes for the keys and string values. + -DO NOT mistaken the "properties" and "type" in the schema as the actual fields in the JSON output. + -Follow the JSON formatting conventions., prompt_variables: ['schema'] + ) + ) + +You can get the output and input format strings using the following methods: + +.. code-block:: python + + print(parser.get_input_format_str()) + print(parser.get_output_format_str()) + +The output for the output format string will be: + +.. code-block:: + + Your output should be formatted as a standard JSON instance with the following schema: + ``` + { + "value": " (int) (required)", + "status": " (str) (required)" + } + ``` + -Make sure to always enclose the JSON output in triple backticks (```). Please do not add anything other than valid JSON output! + -Use double quotes for the keys and string values. + -DO NOT mistaken the "properties" and "type" in the schema as the actual fields in the JSON output. + -Follow the JSON formatting conventions. + +The input format string will be: + +.. code-block:: + + { + "description": " (str) (required)", + "category": " (str) (required)" + } + +Convert a json string to a data class instance: + +.. code-block:: python + + user_input = '{"description": "Parsed description", "category": "Sample Category", "value": 100, "status": "active"}' + parsed_instance = parser.call(user_input) + + print(parsed_instance) + +The output will be: + +.. code-block:: python + + SampleDataClass(description='Parsed description', category='Sample Category', value=100, status='active') + +Try the examples string: + +.. code-block:: python + + samples = [ + SampleDataClass( + description="Sample description", + category="Sample category", + value=100, + status="active", + ), + SampleDataClass( + description="Another description", + category="Another category", + value=200, + status="inactive", + ), + ] + + examples_str = parser.get_examples_str(examples=samples) + print(examples_str) + +The output will be: + +.. code-block:: python + + examples_str: + { + "description": "Sample description", + "category": "Sample category", + "value": 100, + "status": "active" + } + __________ + { + "description": "Another description", + "category": "Another category", + "value": 200, + "status": "inactive" + } + __________ + +You can check out :ref:`Deep Dive Parser ` for more. + + + + +.. admonition:: API References + :class: highlight + + - :ref:`string_parser` + - :ref:`OutputParser` + - :class:`components.output_parsers.outputs.JsonOutputParser` + - :class:`components.output_parsers.outputs.YamlOutputParser` + - :class:`components.output_parsers.outputs.OutputParser` + - :class:`components.output_parsers.outputs.BooleanOutputParser` + - :class:`components.output_parsers.outputs.ListOutputParser` + - :class:`components.output_parsers.dataclass_parser.DataClassParser` + - :class:`core.base_data_class.DataClass` diff --git a/docs/source/new_tutorials/prompt.rst b/docs/source/new_tutorials/prompt.rst new file mode 100644 index 00000000..7d5931a6 --- /dev/null +++ b/docs/source/new_tutorials/prompt.rst @@ -0,0 +1,139 @@ + + +.. raw:: html + + + +Prompt +============ + +AdalFlow leverages `Jinja2` [1]_ to programmatically format the prompt for the language model. +By aggregating different parts of the prompt, it is 10X easier to understand the LLM applications. +We created a :class:`Prompt ` class to allow developers to render the prompt with the string ``template`` and ``prompt_kwargs`` conveniently and securely. + +Introduction +---------------- +A `prompt` is the input text given to a language model(LM) to generate responses. +We believe in `prompting` is the new programming language and developers need to seek maximum control over the prompt. + + +.. figure:: /_static/images/LightRAG_dataflow.png + :align: center + :alt: Data Flow in LLM applications + :width: 620px + + Data flow in LLM applications + +Various LLM app patterns, from RAG to agents, can be implemented via formatting a subpart of the prompt. + +Researchers often use `special tokens` [2]_ to separate different sections of the prompt, such as the system message, user message, and assistant message. +If it is `Llama3` model, the final text sent to the model for tokenization will be: + +.. code-block:: python + + final_prompt = r"""<|begin_of_text|><|start_header_id|>system<|end_header_id|> + {{simple_prompt}} <|eot_id|>""" + +And the LLM will return the following text: + +.. code-block:: python + + prediction = r"""<|start_header_id|>assistant<|end_header_id|> You can ask me anything you want. <|eot_id|><|end_of_text|>""" + +However, many proprietary APIs did not disclose their special tokens, and requires users to send them in the forms of messages of different roles. + + + +Use Prompt Class +---------------- +Besides the placeholders using ``{{}}`` for keyword arguments, Jinja2 also allow users to write code similar to Python syntax. +This includes conditionals, loops, filters, and even comments, which are lacking in Python's native string formatting. + +In default, the ``Prompt`` class uses the :const:`DEFAULT_ADALFLOW_SYSTEM_PROMPT` as its string template if no template is provided. +But it is super easy to create your own template with Jinja2 syntax. +Here is one example of using `Jinja2` to format the prompt with comments `{# #}` and code blocks `{% %}`: + + +.. code-block:: python + + import adalflow as adal + + template = r"""{{ task_desc_str }} + {# tools #} + {% if tools %} + + {% for tool in tools %} + {{loop.index}}. {{ tool }} + {% endfor %} + {% endif %} + {{ input_str }} """ + + task_desc_str = "You are a helpful assitant" + + tools = ["google", "wikipedia", "wikidata"] + + prompt = adal.Prompt( + template=template, + prompt_kwargs={ + "task_desc_str": task_desc_str, + "tools": tools, + }, + ) + + print(prompt(input_str="What is the capital of France?")) + +The printout would be: + +.. code-block:: + + You are a helpful assitant + + 1. google + 2. wikipedia + 3. wikidata + + What is the capital of France? + +As with all components, you can use ``to_dict`` and ``from_dict`` to serialize and deserialize the component. + + +.. note:: + + In reality, we barely need to use the raw ``Prompt`` class directly as it is orchestrated by the ``Generator``. + +Here is the :class:`prompt template` for REACT agent, it consists of (1) + system task description, (2) tools, (3) context variables, (4) output format, (5) user input, and (6) past history. + By reading the prompt structure, developers can easily understand the agent's behavior and functionalities. + +You do not need to worry about handling all functionalities of a prompt, we have (1) `Parser` such as `JsonParser`, `DataClassParser` to help you handle the outpt formatting, +(2) `FuncTool` to help you describe a functional tool in the prompt. + + +.. Prompt Engineering experience +.. ------------------------------- +.. There is no robust prompt, and it is one of the most sensitive creatures in the AI world. +.. Here are some tips: + +.. - Even the output format matters, the order of your output fields, the formating. Output yaml or json format can lead to different performance. We have better luck with yaml format. +.. - Few-shot works so well in some case, but it can lead to regression in some cases. +.. - It is not fun to be a prompt engineer! But what can we do for now. + +.. admonition:: References + :class: highlight + + .. [1] Jinja2: https://jinja.palletsprojects.com/en/3.1.x/ + .. [2] Llama3 special tokens: https://llama.meta.com/docs/model-cards-and-prompt-formats/meta-llama-3/ + +.. admonition:: API References + :class: highlight + + - :class:`core.prompt_builder.Prompt` + - :const:`core.default_prompt_template.DEFAULT_ADALFLOW_SYSTEM_PROMPT` diff --git a/docs/source/tutorials/generator.rst b/docs/source/tutorials/generator.rst index 2b406fda..0f72400e 100644 --- a/docs/source/tutorials/generator.rst +++ b/docs/source/tutorials/generator.rst @@ -141,126 +141,8 @@ Here's the simplest way to use a Generator: # Use the generator response = gen({"input_str": "What is the capital of France?"}) - print(response.raw_response) + print(response) -Understanding the Output ----------------------- - -Every Generator call returns a ``GeneratorOutput`` object: - -.. code-block:: python - - response = gen({"input_str": "Hello"}) - - # Access different parts of the response - print(response.raw_response) # Raw model output - print(response.data) # Processed data (if using output processors) - print(response.error) # Error message if something went wrong - print(response.usage) # Token usage information - -When to Create a Subclass ------------------------ - -You should create a Generator subclass in two main cases: - -1. **Different Model Types**: When using non-LLM endpoints - - .. code-block:: python - - class ImageGenerator(Generator): - """For DALL-E and other image generation models""" - model_type = ModelType.IMAGE_GENERATION - -2. **Custom Processing**: When you need special input/output handling - - .. code-block:: python - - class CustomGenerator(Generator): - def _pre_call(self, prompt_kwargs, model_kwargs): - # Custom preprocessing - return super()._pre_call(prompt_kwargs, model_kwargs) - -When NOT to Subclass ------------------- - -Don't create a subclass for: - -1. **Model Parameters**: Use ``model_kwargs`` instead - - .. code-block:: python - - # Just pass parameters directly - gen = Generator( - model_client=client, - model_kwargs={ - "model": "gpt-4o-mini", - "temperature": 0.9 - } - ) - -2. **Output Processing**: Use output processors - - .. code-block:: python - - from adalflow.components.output_processors import JsonParser - - gen = Generator( - model_client=client, - output_processors=JsonParser() # Process output as JSON - ) - -Common Patterns -------------- - -1. **Error Handling**: - - .. code-block:: python - - response = gen({"input_str": "Query"}) - if response.error: - print(f"Error: {response.error}") - else: - print(response.raw_response) - -2. **Async Usage**: - - .. code-block:: python - - async def generate(): - response = await gen.acall({"input_str": "Hello"}) - print(response.raw_response) - -3. **Streaming**: - - .. code-block:: python - - gen = Generator( - model_client=client, - model_kwargs={"stream": True} - ) - for chunk in gen({"input_str": "Tell me a story"}): - print(chunk) - -Model Types ----------- - -Generator supports different model types through ``ModelType``: - -- ``ModelType.LLM``: Text generation (default) -- ``ModelType.IMAGE_GENERATION``: Image generation (DALL-E) -- ``ModelType.EMBEDDER``: Text embeddings -- ``ModelType.RERANKER``: Document reranking - -Best Practices ------------- - -1. Always check for errors in the response -2. Use output processors for structured outputs -3. Set model parameters in ``model_kwargs`` -4. Use async methods for better performance in async contexts -5. Use streaming for long responses - -Remember: The Generator is designed to provide a consistent interface regardless of the underlying model or task. Generator In Action --------------------------------------- diff --git a/docs/source/tutorials/index.rst b/docs/source/tutorials/index.rst index 754d0217..84963bfc 100644 --- a/docs/source/tutorials/index.rst +++ b/docs/source/tutorials/index.rst @@ -3,7 +3,7 @@ .. _developer_notes: -Tutorials +Developer Notes ============================= .. *Why and How Each Part works* diff --git a/docs/source/tutorials/output_parsers.rst b/docs/source/tutorials/output_parsers.rst index 0df19e09..41372573 100644 --- a/docs/source/tutorials/output_parsers.rst +++ b/docs/source/tutorials/output_parsers.rst @@ -2,7 +2,7 @@ .. raw:: html -
+
Try Quickstart in Colab diff --git a/docs/source/use_cases/classification.rst b/docs/source/use_cases/classification.rst index 0ba09159..f004f120 100644 --- a/docs/source/use_cases/classification.rst +++ b/docs/source/use_cases/classification.rst @@ -11,6 +11,8 @@
+.. _classification_end_to_end: + Classification Optimization ============================= diff --git a/docs/source/use_cases/question_answering.rst b/docs/source/use_cases/question_answering.rst index c5c2fc1f..bfb69917 100644 --- a/docs/source/use_cases/question_answering.rst +++ b/docs/source/use_cases/question_answering.rst @@ -1,3 +1,5 @@ +.. _question_answering: + .. raw:: html
@@ -10,7 +12,6 @@
- Question Answering =============================== diff --git a/notebooks/integration/adalflow_together_deepseek_r1.ipynb b/notebooks/integration/adalflow_together_deepseek_r1.ipynb index bd5f49a6..ff451c08 100644 --- a/notebooks/integration/adalflow_together_deepseek_r1.ipynb +++ b/notebooks/integration/adalflow_together_deepseek_r1.ipynb @@ -96,7 +96,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# 😇 Have the fun\n", + "# 😇 Have fun\n", "\n", "Let's get started! 🚀\n", "\n" diff --git a/tutorials/adalflow_embedder.py b/tutorials/adalflow_embedder.py index c73ac13e..89d491b6 100644 --- a/tutorials/adalflow_embedder.py +++ b/tutorials/adalflow_embedder.py @@ -1,13 +1,14 @@ from adalflow.core.embedder import Embedder, BatchEmbedder from adalflow.components.model_client import OpenAIClient, TransformersClient -from adalflow.core.types import Embedding, EmbedderOutput +from adalflow.core.types import Embedding, EmbedderOutput, Document from adalflow.core.functional import normalize_vector from typing import List -from adalflow.core.component import Component +from adalflow.core.component import DataComponent from copy import deepcopy +from adalflow.components.data_process.data_components import ToEmbeddings -class DecreaseEmbeddingDim(Component): +class DecreaseEmbeddingDim(DataComponent): def __init__(self, old_dim: int, new_dim: int, normalize: bool = True): super().__init__() self.old_dim = old_dim @@ -38,7 +39,7 @@ def test_openai_embedder(): "encoding_format": "float", } - query = "What is the capital of China?" + query = "What is LLM?" queries = [query] * 100 embedder = Embedder(model_client=OpenAIClient(), model_kwargs=model_kwargs) @@ -54,6 +55,26 @@ def test_openai_embedder(): print(f"Batch queries - Length: {output.length}, Dimension: {output.embedding_dim}") +def test_to_embeddings(): + print("\nTesting ToEmbeddings:") + model_kwargs = { + "model": "text-embedding-3-small", + "dimensions": 256, + } + embedder = Embedder(model_client=OpenAIClient(), model_kwargs=model_kwargs) + + to_embeddings = ToEmbeddings(embedder=embedder, batch_size=50) + + query = "What is LLM?" + queries = [Document(text=query)] * 1000 + + print("Starting embedding processing...") + response = to_embeddings(queries) + print(f"Embedding processing complete - Total queries processed: {len(queries)}") + + print(f"Response - Length: {len(response)}, vector: {response[0].vector}") + + def test_local_embedder(): print("\nTesting Local Embedder (HuggingFace):") model_kwargs = {"model": "thenlper/gte-base"} @@ -61,7 +82,7 @@ def test_local_embedder(): model_client=TransformersClient(), model_kwargs=model_kwargs ) - query = "What is the capital of China?" + query = "What is LLM?" queries = [query] * 100 # Test single query @@ -86,7 +107,7 @@ def test_custom_embedder(): output_processors=DecreaseEmbeddingDim(768, 256), ) - query = "What is the capital of China?" + query = "What is LLM?" output = local_embedder_256(query) print( f"Reduced dimension output - Length: {output.length}, Dimension: {output.embedding_dim}, Normalized: {output.is_normalized}" @@ -101,22 +122,24 @@ def test_batch_embedder(): ) batch_embedder = BatchEmbedder(embedder=local_embedder, batch_size=100) - query = "What is the capital of China?" + query = "What is LLM?" queries = [query] * 1000 print("Starting batch processing...") response = batch_embedder(queries) print(f"Batch processing complete - Total queries processed: {len(queries)}") - print(f"Response - Length: {response.length}, Dimension: {response.embedding_dim}") + + print(f"Response - Length: {len(response)}, Dimension: {response[0].embedding_dim}") def main(): # Run all tests - test_openai_embedder() - test_local_embedder() - test_custom_embedder() - test_batch_embedder() + # test_openai_embedder() + # test_local_embedder() + # test_custom_embedder() + # test_batch_embedder() + test_to_embeddings() if __name__ == "__main__": diff --git a/tutorials/generator_note.py b/tutorials/generator_note.py index 1a1693b7..e8024b7c 100644 --- a/tutorials/generator_note.py +++ b/tutorials/generator_note.py @@ -213,6 +213,154 @@ def create_purely_from_config_2(): print(output) +def simple_query(): + + from adalflow.core import Generator + from adalflow.components.model_client.openai_client import OpenAIClient + + gen = Generator( + model_client=OpenAIClient(), + model_kwargs={ + "model": "o3-mini", + }, + ) + + response = gen({"input_str": "What is LLM?"}) + print(response) + + +def customize_template(): + + import adalflow as adal + + # the template has three variables: system_prompt, few_shot_demos, and input_str + few_shot_template = r""" +{{system_prompt}} +{# Few shot demos #} +{% if few_shot_demos is not none %} +Here are some examples: +{{few_shot_demos}} +{% endif %} + + +{{input_str}} +""" + + object_counter = Generator( + model_client=adal.GroqAPIClient(), + model_kwargs={ + "model": "llama3-8b-8192", + }, + template=few_shot_template, + prompt_kwargs={ + "system_prompt": "You will answer a reasoning question. Think step by step. The last line of your response should be of the following format: 'Answer: $VALUE' where VALUE is a numerical value.", + }, + ) + + question = "I have a flute, a piano, a trombone, four stoves, a violin, an accordion, a clarinet, a drum, two lamps, and a trumpet. How many musical instruments do I have?" + response = object_counter(prompt_kwargs={"input_str": question}) + print(response) + + object_counter.print_prompt(input_str=question) + + # use an int parser + + from adalflow.core.string_parser import IntParser + + object_counter = Generator( + model_client=adal.GroqAPIClient(), + model_kwargs={ + "model": "llama3-8b-8192", + }, + template=few_shot_template, + prompt_kwargs={ + "system_prompt": "You will answer a reasoning question. Think step by step. The last line of your response should be of the following format: 'Answer: $VALUE' where VALUE is a numerical value.", + }, + output_processors=IntParser(), + ) + + response = object_counter(prompt_kwargs={"input_str": question}) + print(response) + print(type(response.data)) + + # use customize parser + import re + + @adal.func_to_data_component + def parse_integer_answer(answer: str): + try: + numbers = re.findall(r"\d+", answer) + if numbers: + answer = int(numbers[-1]) + else: + answer = -1 + except ValueError: + answer = -1 + + return answer + + object_counter = Generator( + model_client=adal.GroqAPIClient(), + model_kwargs={ + "model": "llama3-8b-8192", + }, + template=few_shot_template, + prompt_kwargs={ + "system_prompt": "You will answer a reasoning question. Think step by step. The last line of your response should be of the following format: 'Answer: $VALUE' where VALUE is a numerical value.", + }, + output_processors=parse_integer_answer, + ) + + response = object_counter(prompt_kwargs={"input_str": question}) + print(response) + print(type(response.data)) + + template = r""" +{{system_prompt}} + +{{output_format_str}} + + + +{{input_str}} +""" + + from dataclasses import dataclass, field + + @dataclass + class QAOutput(DataClass): + thought: str = field( + metadata={ + "desc": "Your thought process for the question to reach the answer." + } + ) + answer: int = field(metadata={"desc": "The answer to the question."}) + + __output_fields__ = ["thought", "answer"] + + parser = adal.DataClassParser( + data_class=QAOutput, return_data_class=True, format_type="json" + ) + + object_counter = Generator( + model_client=adal.GroqAPIClient(), + model_kwargs={ + "model": "llama3-8b-8192", + }, + template=template, + prompt_kwargs={ + "system_prompt": "You will answer a reasoning question. Think step by step. ", + "output_format_str": parser.get_output_format_str(), + }, + output_processors=parser, + ) + + response = object_counter(prompt_kwargs={"input_str": question}) + print(response) + + object_counter.print_prompt(input_str=question) + + if __name__ == "__main__": qa1 = SimpleQA() answer = qa1("What is adalflow?") @@ -228,6 +376,8 @@ def create_purely_from_config_2(): ) minimum_generator() + simple_query() + customize_template() # use_a_json_parser() # use_its_own_template() # use_model_client_enum_to_switch_client() diff --git a/use_cases/classification/data.py b/use_cases/classification/data.py index 78fb8107..4031909a 100644 --- a/use_cases/classification/data.py +++ b/use_cases/classification/data.py @@ -14,12 +14,15 @@ @dataclass class TRECExtendedData(TrecData): + """Dataclass for TREC dataset""" + rationale: str = field( metadata={ "desc": "Your step-by-step reasoning to classify the question to class_name" }, default=None, ) + __input_fields__ = ["question"] __output_fields__ = ["rationale", "class_name"] diff --git a/use_cases/classification/train.py b/use_cases/classification/train.py index 22914935..34afd5be 100644 --- a/use_cases/classification/train.py +++ b/use_cases/classification/train.py @@ -39,7 +39,7 @@ def __init__( ) def prepare_task(self, sample: TRECExtendedData): - return self.task.call, {"question": sample.question, "id": sample.id} + return self.task.bicall, {"question": sample.question, "id": sample.id} def prepare_eval( self, sample: TRECExtendedData, y_pred: adal.GeneratorOutput diff --git a/use_cases/classification/trec_task_structured_output.py b/use_cases/classification/trec_task_structured_output.py index 56014cc6..797930ad 100644 --- a/use_cases/classification/trec_task_structured_output.py +++ b/use_cases/classification/trec_task_structured_output.py @@ -43,11 +43,8 @@ def __init__(self, model_client: adal.ModelClient, model_kwargs: Dict): template=task_desc_template, prompt_kwargs={"classes": label_desc} )() - self.data_class = TRECExtendedData - self.data_class.set_task_desc(task_desc_str) - self.parser = adal.DataClassParser( - data_class=self.data_class, return_data_class=True, format_type="yaml" + data_class=TRECExtendedData, return_data_class=True, format_type="yaml" ) prompt_kwargs = { @@ -55,7 +52,7 @@ def __init__(self, model_client: adal.ModelClient, model_kwargs: Dict): # it is better to split it into two prompts it is more effective at training # 0.8056 val, 0.903 test "system_prompt": adal.Parameter( - data=self.parser.get_task_desc_str(), + data=task_desc_str, # data="You are a classifier. Given a question, classify it into one of the following classes based on what the question is seeking:\n\nFormat: class_index. class_name, class_description\n\n0. ABBR, Abbreviation\n1. ENTY, Entity\n2. DESC, Description and abstract concept\n3. HUM, Human being\n4. LOC, Location\n5. NUM, Numeric value\n\nPay close attention to whether a question asks for specific terms, traditions, entities, or people, versus a general description or numerical detail. Do not try to answer the question:", # data="You are a classifier. Given a question, classify it into one of the following classes based on what the question is seeking:\n\nFormat: class_index. class_name, class_description\n\n0. ABBR, Abbreviation\n1. ENTY, Entity\n2. DESC, Description and abstract concept\n3. HUM, Human being\n4. LOC, Location\n5. NUM, Numeric value\n\nPay special attention to questions about entities versus descriptions, as well as those asking for specific terms or people. Do not try to answer the question:", # best # data="You are a classifier. For each question given, classify it into one of the following classes:\n\nFormat: class_index. class_name, class_description\n\n0. ABBR, Abbreviation (includes initials)\n1. ENTY, Entity (includes products, languages, objects, etc.)\n2. DESC, Description and abstract concept (includes explanations)\n3. HUM, Human being (includes individuals, groups, etc.)\n4. LOC, Location (includes addresses, places, etc.)\n5. NUM, Numeric value (includes distances, dates, ages, etc.)\n\n- Focus on identifying the primary subject of the question and classifying based on what is being explicitly asked for.", @@ -87,27 +84,10 @@ def __init__(self, model_client: adal.ModelClient, model_kwargs: Dict): use_cache=True, ) - # TODO: can automatically convert everything to parameter if it is not already - # inside of the forward function instead of doing it here. - # and this conversion will give input type automatically - def _prepare_input(self, question: str): - input_data = self.data_class(question=question) - input_str = self.parser.get_input_str(input_data) - prompt_kwargs = { - "input_str": adal.Parameter( - data=input_str, - requires_opt=False, - role_desc="input to the LLM", - param_type=adal.ParameterType.INPUT, - ) - } - return prompt_kwargs - - def call( + def bicall( self, question: str, id: Optional[str] = None ) -> Union[adal.GeneratorOutput, adal.Parameter]: - prompt_kwargs = self._prepare_input(question) - output = self.llm(prompt_kwargs=prompt_kwargs, id=id) + output = self.llm(prompt_kwargs={"input_str": question}, id=id) if isinstance(output, adal.Parameter): output.data_in_prompt = lambda x: x.data.raw_response return output diff --git a/use_cases/question_answering/bbh/data.py b/use_cases/question_answering/bbh/data.py index d363248d..86d8221d 100644 --- a/use_cases/question_answering/bbh/data.py +++ b/use_cases/question_answering/bbh/data.py @@ -66,12 +66,9 @@ class QuestionAnswer(DataClass): @func_to_data_component def parse_integer_answer(answer: str): - """A function that parses the last integer from a string using regular expressions.""" try: - # Use regular expression to find all sequences of digits numbers = re.findall(r"\d+", answer) if numbers: - # Get the last number found answer = int(numbers[-1]) else: answer = -1 diff --git a/use_cases/question_answering/bbh/object_count/task.py b/use_cases/question_answering/bbh/object_count/task.py index 4892fe0f..adcc9120 100644 --- a/use_cases/question_answering/bbh/object_count/task.py +++ b/use_cases/question_answering/bbh/object_count/task.py @@ -30,7 +30,6 @@ def __init__(self, model_client: adal.ModelClient, model_kwargs: Dict): super().__init__() system_prompt = adal.Parameter( - # data="You will answer a reasoning question. Think step by step. The last line of your response should be of the following format: 'Answer: $VALUE' where VALUE is a numerical value.", data="You will answer a reasoning question. Think step by step. The last line of your response should be of the following format: 'Answer: $VALUE' where VALUE is a numerical value.", role_desc="To give task instruction to the language model in the system prompt", requires_opt=True,