From 4a8b154d26f951fd559505320d015e26c19ab526 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?The=20Vinh=20LUONG=20=28LU=CC=9BO=CC=9BNG=20The=CC=82?= =?UTF-8?q?=CC=81=20Vinh=29?= Date: Wed, 6 Mar 2024 15:07:12 -0800 Subject: [PATCH 1/4] migrate to open-source --- .../workflows/install-lint-test-on-mac.yml | 1 - .../workflows/install-lint-test-on-ubuntu.yml | 1 - .../workflows/install-lint-test-on-win.yml | 1 - .ruff.toml | 1 + .vscode/settings.json | 2 + openssa/integrations/README.md | 2 +- openssa/utils/llms.py | 6 +++ openssa/utils/usage_logger.py | 35 ++++++++++++++++ pyproject.toml | 8 ++-- tests/utils/test_llms.py | 40 +++++++++++++++++++ tests/utils/test_usage_logger.py | 15 +++++++ 11 files changed, 104 insertions(+), 8 deletions(-) create mode 100644 openssa/utils/usage_logger.py create mode 100644 tests/utils/test_llms.py create mode 100644 tests/utils/test_usage_logger.py diff --git a/.github/workflows/install-lint-test-on-mac.yml b/.github/workflows/install-lint-test-on-mac.yml index bc1240aca..273be5408 100644 --- a/.github/workflows/install-lint-test-on-mac.yml +++ b/.github/workflows/install-lint-test-on-mac.yml @@ -16,7 +16,6 @@ jobs: strategy: matrix: python-version: - - '3.10' - 3.11 steps: diff --git a/.github/workflows/install-lint-test-on-ubuntu.yml b/.github/workflows/install-lint-test-on-ubuntu.yml index 8a30328b1..0d0bedaea 100644 --- a/.github/workflows/install-lint-test-on-ubuntu.yml +++ b/.github/workflows/install-lint-test-on-ubuntu.yml @@ -16,7 +16,6 @@ jobs: strategy: matrix: python-version: - - '3.10' - 3.11 steps: diff --git a/.github/workflows/install-lint-test-on-win.yml b/.github/workflows/install-lint-test-on-win.yml index ab9c5bfad..bac65dde3 100644 --- a/.github/workflows/install-lint-test-on-win.yml +++ b/.github/workflows/install-lint-test-on-win.yml @@ -16,7 +16,6 @@ jobs: strategy: matrix: python-version: - - '3.10' - 3.11 steps: diff --git a/.ruff.toml b/.ruff.toml index 0b91a8b0b..620d0e214 100644 --- a/.ruff.toml +++ b/.ruff.toml @@ -123,6 +123,7 @@ ignore = [ "SIM401", # use `item.get("role", "assistant")` instead of an `if` block "SLF001", # private member accessed "T201", # `print` found + "T203", # `pprint` found "TD002", # missing author in TODO; try: `# TODO(): ...` or `# TODO @: ...` "TD003", # missing issue link on the line following this TODO "TRY003", # avoid specifying long messages outside the exception class diff --git a/.vscode/settings.json b/.vscode/settings.json index 7f8210683..4a310a014 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -23,6 +23,8 @@ }, "python.analysis.diagnosticSeverityOverrides": { + "reportInvalidTypeForm": "none", + "reportMissingImports": "none" }, diff --git a/openssa/integrations/README.md b/openssa/integrations/README.md index 06c15a91f..1ced9e2d8 100644 --- a/openssa/integrations/README.md +++ b/openssa/integrations/README.md @@ -1 +1 @@ -# Integrating SSMs with existing industrial and other systems +# Integrating SSMs with existing industrial and other systems diff --git a/openssa/utils/llms.py b/openssa/utils/llms.py index 67391c144..053e47b75 100644 --- a/openssa/utils/llms.py +++ b/openssa/utils/llms.py @@ -2,6 +2,7 @@ import json from openai import OpenAI, AzureOpenAI from openssa.utils.config import Config +from openssa.utils.usage_logger import BasicUsageLogger, AbstractUsageLogger class AnLLM: @@ -28,11 +29,15 @@ def __init__( model: str = None, api_base: str = None, api_key: str = None, + user_id: str = "openssa", + usage_logger: AbstractUsageLogger = BasicUsageLogger(), **additional_kwargs, ): self.model = model self.api_base = api_base self.api_key = api_key + self.user_id = user_id + self.usage_logger = usage_logger self._client = None self._additional_kwargs = additional_kwargs @@ -49,6 +54,7 @@ def call(self, is_chat: bool = True, **kwargs): result = self.client.completions.create( model=self.model, **kwargs, **self._additional_kwargs ) + self.usage_logger.log_usage(user=self.user_id, result=result) return result def create_embeddings(self): diff --git a/openssa/utils/usage_logger.py b/openssa/utils/usage_logger.py new file mode 100644 index 000000000..ee2bf74e0 --- /dev/null +++ b/openssa/utils/usage_logger.py @@ -0,0 +1,35 @@ +from abc import ABC, abstractmethod +from datetime import datetime, timezone +from openai.resources.chat.completions import ChatCompletion + + +# Abstract Logger +class AbstractUsageLogger(ABC): + @abstractmethod + def log_usage(self, **kwargs): + pass + + +# Basic Logger +class BasicUsageLogger(AbstractUsageLogger): + def log_usage(self, **kwargs): + user_id = kwargs.get("user", "openssa") + result = kwargs.get("result", {}) + if isinstance(result, ChatCompletion): + model = result.model + utc_date_time = datetime.fromtimestamp(result.created, tz=timezone.utc) + completion_tokens = result.usage.completion_tokens + prompt_tokens = result.usage.prompt_tokens + total_tokens = result.usage.total_tokens + token_info = f"input tokens: {completion_tokens}, ouput tokens: {prompt_tokens}, total: {total_tokens}" + print( + f"model: {model}, utc-timestamp: {utc_date_time}, user: {user_id}, {token_info}" + ) + else: + print(f"user_id: {user_id}, result: {result}") + + +# DB Logger (Placeholder for actual implementation) +# class DBUsageLogger(AbstractUsageLogger): +# def log_usage(self, **kwargs): +# pass diff --git a/pyproject.toml b/pyproject.toml index 5b9c5b7ad..72c3a7780 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,7 +53,7 @@ myst-parser = ">=2.0" [tool.poetry.group.lint.dependencies] flake8 = ">=7.0" pylint = ">=3.1" -ruff = ">=0.2" +ruff = ">=0.3" pydocstyle = ">=6.3" [tool.poetry.group.test.dependencies] @@ -62,9 +62,9 @@ pytest = ">=8.0" [tool.poetry.dependencies] python = ">=3.10,<3.12" # OpenAI interface -openai = ">=1.12.0" +openai = ">=1.13" # LlamaIndex & related -llama-index = ">=0.10.13" # should keep up-to-date with Llama-Index's minor releases (often backward-incompatible) +llama-index = ">=0.10.14" # should keep up-to-date with Llama-Index's minor releases (often backward-incompatible) llama-hub = ">=0.0.79" llama-index-llms-azure-openai = ">=0.1" llama-index-embeddings-azure-openai = ">=0.1" @@ -80,7 +80,7 @@ gcsfs = ">=2024.2" # Google Cloud Storage s3fs = ">=2024.2" # S3 # misc / other click = ">=8.1" -google-api-python-client = ">=2.119" +google-api-python-client = ">=2.120" httpx = ">=0.27" loguru = ">=0.7" pydantic = "1.10.9" diff --git a/tests/utils/test_llms.py b/tests/utils/test_llms.py new file mode 100644 index 000000000..3a8d2f0c6 --- /dev/null +++ b/tests/utils/test_llms.py @@ -0,0 +1,40 @@ +import unittest +from unittest.mock import Mock, patch +from openssa.utils.llms import AnLLM, BasicUsageLogger + + +class TestAnLLM(unittest.TestCase): + def setUp(self): + self.model = "test-model" + self.api_base = "https://api.example.com" + self.api_key = "testapikey" + self.user_id = "testuser" + self.usage_logger = Mock(spec=BasicUsageLogger) + + def test_initialization(self): + anllm = AnLLM( + self.model, self.api_base, self.api_key, self.user_id, self.usage_logger + ) + self.assertEqual(anllm.model, self.model) + self.assertEqual(anllm.api_base, self.api_base) + self.assertEqual(anllm.api_key, self.api_key) + self.assertEqual(anllm.user_id, self.user_id) + self.assertIs(anllm.usage_logger, self.usage_logger) + + @patch("openssa.utils.llms.AnLLM.client") + def test_call_is_chat_true(self, mock_client): + # Setup + anllm = AnLLM(self.model, usage_logger=self.usage_logger) + mock_result = {"dummy": "result"} + mock_client.chat.completions.create.return_value = mock_result + + # Action + result = anllm.call(is_chat=True, test_arg="value") + + # Assert + self.assertEqual(result, mock_result) + self.usage_logger.log_usage.assert_called_once() + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/utils/test_usage_logger.py b/tests/utils/test_usage_logger.py new file mode 100644 index 000000000..8153f514d --- /dev/null +++ b/tests/utils/test_usage_logger.py @@ -0,0 +1,15 @@ +import unittest +from openssa.utils.llms import OpenAILLM + + +class TestUsageLogger(unittest.TestCase): + def setUp(self): + pass + + def test_with_openai_llm(self): + llm = OpenAILLM() + llm.get_response("say hello") + + +if __name__ == "__main__": + unittest.main() From a3885366fa6a352d1b2b87b5a63c452cfb9ddb9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?The=20Vinh=20LUONG=20=28LU=CC=9BO=CC=9BNG=20The=CC=82?= =?UTF-8?q?=CC=81=20Vinh=29?= Date: Wed, 6 Mar 2024 17:34:33 -0800 Subject: [PATCH 2/4] add Level-2 Planning & Reasoning intelligence capabilities --- .../financebench/Planning-and-Reasoning.ipynb | 224 ++++++++++++++++++ openssa/__init__.py | 16 +- openssa/l2/agent/abstract.py | 48 ++++ openssa/l2/agent/agent.py | 25 ++ openssa/l2/knowledge/fact/abstract.py | 8 + openssa/l2/knowledge/heuristic/abstract.py | 8 + .../l2/knowledge/inference_rule/abstract.py | 8 + openssa/l2/planning/abstract.py | 59 +++++ openssa/l2/planning/hierarchical/__init__.py | 151 ++++++++++++ openssa/l2/planning/hierarchical/_prompts.py | 115 +++++++++ openssa/l2/planning/map_reduce/__init__.py | 23 ++ openssa/l2/planning/map_reduce/_prompts.py | 0 openssa/l2/reasoning/abstract.py | 28 +++ openssa/l2/reasoning/base.py | 28 +++ openssa/l2/reasoning/ooda/__init__.py | 26 ++ openssa/l2/reasoning/ooda/_prompts.py | 0 openssa/l2/resource/_global.py | 27 +++ openssa/l2/resource/_prompts.py | 7 + openssa/l2/resource/abstract.py | 30 +++ openssa/l2/resource/db.py | 10 + openssa/l2/resource/file.py | 57 +++++ openssa/l2/resource/rss.py | 10 + openssa/l2/resource/sensor.py | 10 + openssa/l2/resource/web.py | 10 + openssa/l2/task/abstract.py | 61 +++++ openssa/l2/task/status.py | 12 + openssa/l2/task/task.py | 41 ++++ openssa/l2/tool/abstract.py | 5 + 28 files changed, 1046 insertions(+), 1 deletion(-) create mode 100644 examples/financebench/Planning-and-Reasoning.ipynb create mode 100644 openssa/l2/agent/abstract.py create mode 100644 openssa/l2/agent/agent.py create mode 100644 openssa/l2/knowledge/fact/abstract.py create mode 100644 openssa/l2/knowledge/heuristic/abstract.py create mode 100644 openssa/l2/knowledge/inference_rule/abstract.py create mode 100644 openssa/l2/planning/abstract.py create mode 100644 openssa/l2/planning/hierarchical/__init__.py create mode 100644 openssa/l2/planning/hierarchical/_prompts.py create mode 100644 openssa/l2/planning/map_reduce/__init__.py create mode 100644 openssa/l2/planning/map_reduce/_prompts.py create mode 100644 openssa/l2/reasoning/abstract.py create mode 100644 openssa/l2/reasoning/base.py create mode 100644 openssa/l2/reasoning/ooda/__init__.py create mode 100644 openssa/l2/reasoning/ooda/_prompts.py create mode 100644 openssa/l2/resource/_global.py create mode 100644 openssa/l2/resource/_prompts.py create mode 100644 openssa/l2/resource/abstract.py create mode 100644 openssa/l2/resource/db.py create mode 100644 openssa/l2/resource/file.py create mode 100644 openssa/l2/resource/rss.py create mode 100644 openssa/l2/resource/sensor.py create mode 100644 openssa/l2/resource/web.py create mode 100644 openssa/l2/task/abstract.py create mode 100644 openssa/l2/task/status.py create mode 100644 openssa/l2/task/task.py create mode 100644 openssa/l2/tool/abstract.py diff --git a/examples/financebench/Planning-and-Reasoning.ipynb b/examples/financebench/Planning-and-Reasoning.ipynb new file mode 100644 index 000000000..afa8623d5 --- /dev/null +++ b/examples/financebench/Planning-and-Reasoning.ipynb @@ -0,0 +1,224 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Setups" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext autoreload\n", + "%autoreload 2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pprint import pprint\n", + "from IPython.display import display, Markdown, Pretty" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from dotenv import load_dotenv\n", + "load_dotenv()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import nest_asyncio\n", + "nest_asyncio.apply()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Imports" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "\n", + "from openssa import (Agent,\n", + " HTP, AutoHTPlanner,\n", + " OodaReasoner,\n", + " FileResource)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Problems & Resources" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "PROBLEM = 'Does AMD have a healthy liquidity profile based on FY22 Quick Ratio?'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "RESOURCE_PATH = Path() / '.FinanceBench' / 'docs' / 'AMD_2022_10K'\n", + "assert RESOURCE_PATH.is_dir()\n", + "\n", + "resource = FileResource(RESOURCE_PATH)\n", + "display(Markdown(resource.overview))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Agent with Planning & Reasoning" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "agent = Agent(planner=AutoHTPlanner(max_depth=3, max_subtasks_per_decomp=9),\n", + " reasoner=OodaReasoner(),\n", + " resources={resource})\n", + "pprint(agent)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Problem-Solving with Automated Planner" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "auto_plan = agent.planner.plan(PROBLEM)\n", + "pprint(auto_plan)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "solution_1 = agent.solve(PROBLEM)\n", + "display(Markdown(solution_1))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Problem-Solving with Expert-Specified Plan" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "expert_plan = HTP.from_dict(\n", + " {\n", + " 'task': PROBLEM,\n", + " 'sub-plans': [\n", + " {\n", + " 'task': 'retrieve data points needed for Quick Ratio',\n", + " 'sub-plans': [\n", + " {\n", + " 'task': 'retrieve Cash & Cash Equivalents'\n", + " },\n", + " {\n", + " 'task': 'retrieve Accounts Receivable'\n", + " },\n", + " {\n", + " 'task': 'retrieve Short-Term Liabilities'\n", + " },\n", + " {\n", + " 'task': 'retrieve Accounts Payable'\n", + " },\n", + " ]\n", + " },\n", + " {\n", + " 'task': 'calculate Quick Ratio'\n", + " },\n", + " {\n", + " 'task': 'see whether Quick Ratio is healthy, i.e. greater than 1'\n", + " },\n", + " ]\n", + " }\n", + ")\n", + "pprint(expert_plan)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "solution_2 = agent.solve(PROBLEM, plan=expert_plan)\n", + "display(Markdown(solution_2))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.8" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/openssa/__init__.py b/openssa/__init__.py index a2c1d2204..3e97d1116 100644 --- a/openssa/__init__.py +++ b/openssa/__init__.py @@ -2,7 +2,6 @@ from pathlib import Path import tomllib -# pylint: disable=wrong-import-position from openssa.core.ooda_rag.heuristic import TaskDecompositionHeuristic from openssa.core.ooda_rag.solver import OodaSSA from openssa.core.prompts import Prompts @@ -24,6 +23,21 @@ from openssa.utils.logs import Logs, logger, mlogger from openssa.utils.utils import Utils +from .l2.agent.agent import Agent + +from .l2.planning.abstract import AbstractPlan, AbstractPlanner +from .l2.planning.hierarchical import HTP, AutoHTPlanner + +from .l2.reasoning.abstract import AbstractReasoner +from .l2.reasoning.base import BaseReasoner +from .l2.reasoning.ooda import OodaReasoner + +from .l2.resource.abstract import AbstractResource +from .l2.resource.file import FileResource + +from .l2.task.abstract import AbstractTask +from .l2.task.task import Task + try: __version__: str = version(distribution_name='OpenSSA') diff --git a/openssa/l2/agent/abstract.py b/openssa/l2/agent/abstract.py new file mode 100644 index 000000000..543a92f72 --- /dev/null +++ b/openssa/l2/agent/abstract.py @@ -0,0 +1,48 @@ +"""Abstract agent with planning, reasoning & informational resources.""" + + +from abc import ABC +from dataclasses import dataclass, field +from pprint import pprint + +from openssa.l2.planning.abstract import AbstractPlan, AbstractPlanner +from openssa.l2.reasoning.abstract import AbstractReasoner +from openssa.l2.reasoning.base import BaseReasoner +from openssa.l2.resource.abstract import AbstractResource + + +@dataclass(init=True, + repr=True, + eq=True, + order=False, + unsafe_hash=False, + frozen=False, # mutable + match_args=True, + kw_only=False, + slots=False, + weakref_slot=False) +class AbstractAgent(ABC): + """Abstract agent with planning, reasoning & informational resources.""" + + planner: AbstractPlanner + reasoner: AbstractReasoner = field(default_factory=BaseReasoner) + resources: set[AbstractResource] = field(default_factory=set, + init=True, + repr=True, + hash=False, # mutable + compare=True, + metadata=None, + kw_only=True) + + @property + def resource_overviews(self) -> dict[str, str]: + return {r.unique_name: r.overview for r in self.resources} + + def solve(self, problem: str, plan: AbstractPlan | None = None) -> str: + """Solve problem, with an automatically generated plan (default) or explicitly specified plan.""" + plan: AbstractPlan = (self.planner.update_plan_resources(plan, resources=self.resources) + if plan + else self.planner.plan(problem, resources=self.resources)) + pprint(plan) + + return plan.execute(reasoner=self.reasoner) diff --git a/openssa/l2/agent/agent.py b/openssa/l2/agent/agent.py new file mode 100644 index 000000000..817439f80 --- /dev/null +++ b/openssa/l2/agent/agent.py @@ -0,0 +1,25 @@ +"""Agent with planning, reasoning & informational resources.""" + + +from dataclasses import dataclass, field + +from openssa.l2.planning.abstract import AbstractPlanner +from openssa.l2.planning.hierarchical import AutoHTPlanner + +from .abstract import AbstractAgent + + +@dataclass(init=True, + repr=True, + eq=True, + order=False, + unsafe_hash=False, + frozen=False, # mutable + match_args=True, + kw_only=False, + slots=False, + weakref_slot=False) +class Agent(AbstractAgent): + """Agent with planning, reasoning & informational resources.""" + + planner: AbstractPlanner = field(default_factory=AutoHTPlanner) diff --git a/openssa/l2/knowledge/fact/abstract.py b/openssa/l2/knowledge/fact/abstract.py new file mode 100644 index 000000000..e7a6a5cd4 --- /dev/null +++ b/openssa/l2/knowledge/fact/abstract.py @@ -0,0 +1,8 @@ +"""Abstract fact.""" + + +from abc import ABC + + +class AbstractFact(ABC): # noqa: B024 + """Abstract fact.""" diff --git a/openssa/l2/knowledge/heuristic/abstract.py b/openssa/l2/knowledge/heuristic/abstract.py new file mode 100644 index 000000000..500ae8c75 --- /dev/null +++ b/openssa/l2/knowledge/heuristic/abstract.py @@ -0,0 +1,8 @@ +"""Abstract heuristic.""" + + +from abc import ABC + + +class AbstractHeuristic(ABC): # noqa: B024 + """Abstract heuristic.""" diff --git a/openssa/l2/knowledge/inference_rule/abstract.py b/openssa/l2/knowledge/inference_rule/abstract.py new file mode 100644 index 000000000..08312a263 --- /dev/null +++ b/openssa/l2/knowledge/inference_rule/abstract.py @@ -0,0 +1,8 @@ +"""Abstract inference rule.""" + + +from abc import ABC + + +class AbstractInferenceRule(ABC): # noqa: B024 + """Abstract inference rule.""" diff --git a/openssa/l2/planning/abstract.py b/openssa/l2/planning/abstract.py new file mode 100644 index 000000000..2155b1e6a --- /dev/null +++ b/openssa/l2/planning/abstract.py @@ -0,0 +1,59 @@ +"""Abstract planning classes.""" + + +from __future__ import annotations + +from abc import ABC, abstractmethod +from dataclasses import dataclass, field +from typing import TYPE_CHECKING + +from openssa.l2.reasoning.base import BaseReasoner +from openssa.utils.llms import AnLLM, OpenAILLM + +if TYPE_CHECKING: + from openssa.l2.reasoning.abstract import AbstractReasoner + from openssa.l2.resource.abstract import AbstractResource + from openssa.l2.task.task import Task + + +@dataclass(init=True, + repr=True, + eq=True, + order=False, + unsafe_hash=False, + frozen=False, # mutable + match_args=True, + kw_only=False, + slots=False, + weakref_slot=False) +class AbstractPlan(ABC): + """Abstract plan.""" + task: Task + + @abstractmethod + def execute(self, reasoner: AbstractReasoner = BaseReasoner()) -> str: + """Execute and return result, using specified reasoner to reason through involved tasks.""" + + +@dataclass(init=True, + repr=True, + eq=True, + order=False, + unsafe_hash=False, + frozen=False, # mutable + match_args=True, + kw_only=False, + slots=False, + weakref_slot=False) +class AbstractPlanner(ABC): + """Abstract planner.""" + + lm: AnLLM = field(default_factory=OpenAILLM.get_gpt_4_1106_preview) + + @abstractmethod + def plan(self, problem: str, resources: set[AbstractResource] | None = None) -> AbstractPlan: + """Make plan for solving problem based on informational resources.""" + + @abstractmethod + def update_plan_resources(self, plan: AbstractPlan, /, resources: set[AbstractResource]) -> AbstractPlan: + """Make updated plan copy with relevant informational resources.""" diff --git a/openssa/l2/planning/hierarchical/__init__.py b/openssa/l2/planning/hierarchical/__init__.py new file mode 100644 index 000000000..974249055 --- /dev/null +++ b/openssa/l2/planning/hierarchical/__init__.py @@ -0,0 +1,151 @@ +"""Hierarchical task-planning classes.""" + + +from __future__ import annotations + +from dataclasses import dataclass, asdict, field +import json +from typing import TYPE_CHECKING + +from loguru import logger +from tqdm import tqdm + +from openssa.l2.planning.abstract import AbstractPlan, AbstractPlanner +from openssa.l2.reasoning.base import BaseReasoner +from openssa.l2.task.abstract import TaskDict +from openssa.l2.task.status import TaskStatus +from openssa.l2.task.task import Task +from openssa.utils.llms import AnLLM, OpenAILLM + +from ._prompts import (HTP_PROMPT_TEMPLATE, HTP_WITH_RESOURCES_PROMPT_TEMPLATE, HTP_UPDATE_RESOURCES_PROMPT_TEMPLATE, + HTP_RESULTS_SYNTH_PROMPT_TEMPLATE) + +if TYPE_CHECKING: + from openssa.l2.reasoning.abstract import AbstractReasoner + from openssa.l2.resource.abstract import AbstractResource + + +HTPDict: type = dict[str, TaskDict | str | list[dict]] + + +@dataclass(init=True, + repr=True, + eq=True, + order=False, + unsafe_hash=False, + frozen=False, # mutable + match_args=True, + kw_only=False, + slots=False, + weakref_slot=False) +class HTP(AbstractPlan): + """Hierarchical task plan (HTP).""" + + sub_plans: list[HTP] = field(default_factory=list, + init=True, + repr=True, + hash=False, # mutable + compare=True, + metadata=None, + kw_only=True) + + @classmethod + def from_dict(cls, htp_dict: HTPDict, /) -> HTP: + """Create hierarchical task plan from dictionary representation.""" + return HTP(task=Task.from_dict_or_str(htp_dict['task']), + sub_plans=[HTP.from_dict(d) for d in htp_dict.get('sub-plans', [])]) + + def to_dict(self) -> HTPDict: + """Return dictionary representation.""" + return {'task': asdict(self.task), + 'sub-plans': [p.to_dict() for p in self.sub_plans]} + + def fix_missing_resources(self): + """Fix missing resources in HTP.""" + for p in self.sub_plans: + if not p.task.resource: + p.task.resource: AbstractResource | None = self.task.resource + p.fix_missing_resources() + + def execute(self, reasoner: AbstractReasoner = BaseReasoner()) -> str: + """Execute and return result, using specified reasoner to reason through involved tasks.""" + if self.sub_plans: + sub_results: tuple[str, str] = ((p.task.ask, p.execute(reasoner)) for p in tqdm(self.sub_plans)) + + prompt: str = HTP_RESULTS_SYNTH_PROMPT_TEMPLATE.format( + ask=self.task.ask, + supporting_info='\n\n'.join((f'SUPPORTING QUESTION/TASK #{i + 1}:\n{ask}\n' + '\n' + f'SUPPORTING RESULT #{i + 1}:\n{result}\n') + for i, (ask, result) in enumerate(sub_results))) + logger.debug(prompt) + + self.task.result: str = reasoner.lm.get_response(prompt) + + else: + self.task.result: str = reasoner.reason(self.task) + + self.task.status: TaskStatus = TaskStatus.DONE + return self.task.result + + +@dataclass(init=True, + repr=True, + eq=True, + order=False, + unsafe_hash=False, + frozen=False, # mutable + match_args=True, + kw_only=False, + slots=False, + weakref_slot=False) +class AutoHTPlanner(AbstractPlanner): + """Automated (generative) hierarchical task planner.""" + + max_depth: int = 3 + max_subtasks_per_decomp: int = 9 + + def plan(self, problem: str, resources: set[AbstractResource] | None = None) -> HTP: + """Make hierarchical task plan (HTP) for solving problem.""" + prompt: str = ( + HTP_WITH_RESOURCES_PROMPT_TEMPLATE.format(problem=problem, + resource_overviews={r.unique_name: r.overview for r in resources}, + max_depth=self.max_depth, + max_subtasks_per_decomp=self.max_subtasks_per_decomp) + if resources + else HTP_PROMPT_TEMPLATE.format(problem=problem, + max_depth=self.max_depth, + max_subtasks_per_decomp=self.max_subtasks_per_decomp) + ) + + # TODO: more rigorous JSON schema validation + htp_dict: HTPDict = {} + while not htp_dict: + htp_dict: HTPDict = self.lm.parse_output(self.lm.get_response(prompt)) + + htp: HTP = HTP.from_dict(htp_dict) + + if resources: + htp.fix_missing_resources() + + return htp + + def update_plan_resources(self, plan: HTP, /, resources: set[AbstractResource]) -> HTP: + """Make updated hierarchical task plan (HTP) copy with relevant informational resources.""" + assert isinstance(plan, HTP), TypeError(f'*** {plan} NOT OF TYPE {HTP.__name__} ***') + assert resources, ValueError(f'*** {resources} NOT A NON-EMPTY SET OF INFORMATIONAL RESOURCES ***') + + prompt: str = HTP_UPDATE_RESOURCES_PROMPT_TEMPLATE.format(resource_overviews={r.unique_name: r.overview + for r in resources}, + htp_json=json.dumps(obj=plan.to_dict())) + + # TODO: more rigorous JSON schema validation + updated_htp_dict: HTPDict = {} + while not updated_htp_dict: + updated_htp_dict: HTPDict = self.lm.parse_output(self.lm.get_response(prompt)) + + updated_htp: HTP = HTP.from_dict(updated_htp_dict) + + updated_htp.fix_missing_resources() + + return updated_htp diff --git a/openssa/l2/planning/hierarchical/_prompts.py b/openssa/l2/planning/hierarchical/_prompts.py new file mode 100644 index 000000000..863fb4f41 --- /dev/null +++ b/openssa/l2/planning/hierarchical/_prompts.py @@ -0,0 +1,115 @@ +HTP_JSON_TEMPLATE: str = """ +{{ + "task": "(textual description of problem/task to solve)", + "sub-plans": [ + {{ + "task": "(textual description of 1st sub-problem/sub-task to solve)", + "sub-plans": [ + (... nested sub-plans ...) + ] + }}, + {{ + "task": "(textual description of 2nd sub-problem/sub-task to solve)", + "sub-plans": [ + (... nested sub-plans ...) + ] + }}, + ... + ] +}} +""" + +HTP_WITH_RESOURCES_JSON_TEMPLATE: str = """ +{{ + "task": {{ + "ask": "(textual description of problem/task to solve)" + }}, + "sub-plans": [ + {{ + "task": {{ + "ask": "(textual description of 1st sub-problem/sub-task to solve)", + "resource": "(unique name of most relevant informational resource, IF ANY)" OR null + }}, + "sub-plans": [ + (... nested sub-plans ...) + ] + }}, + {{ + "task": {{ + "ask": "(textual description of 2nd sub-problem/sub-task to solve)", + "resource": "(unique name of most relevant informational resource, IF ANY)" OR null + }}, + "sub-plans": [ + (... nested sub-plans ...) + ] + }}, + ... + ] +}} +""" + + +def htp_prompt_template(with_resources: bool) -> str: + return ( +'Using the following JSON hierarchical task plan data structure:' # noqa: E122 +f'\n{HTP_WITH_RESOURCES_JSON_TEMPLATE if with_resources else HTP_JSON_TEMPLATE}' # noqa: E122 +""" +please return a suggested hierarchical task plan with +Max Depth of {max_depth} and Max Subtasks per Decomposition of {max_subtasks_per_decomp} +for the following problem: + +``` +{problem} +``` + +Please return ONLY the JSON DICTIONARY and no other text, not even the "```json" wrapping! +""" # noqa: E122 +) # noqa: E122 + + +HTP_PROMPT_TEMPLATE: str = htp_prompt_template(with_resources=False) + + +RESOURCE_OVERVIEW_PROMPT_SECTION: str = \ +"""Consider that you can access informational resources summarized in the below dictionary, +in which each key is a resource's unique name and the corresponding value is that resource's overview: + +``` +{resource_overviews} +``` + +""" # noqa: E122 + + +HTP_WITH_RESOURCES_PROMPT_TEMPLATE: str = RESOURCE_OVERVIEW_PROMPT_SECTION + htp_prompt_template(with_resources=True) + + +HTP_UPDATE_RESOURCES_PROMPT_TEMPLATE: str = ( +RESOURCE_OVERVIEW_PROMPT_SECTION + # noqa: E122 +"""please return an updated version of the following JSON hierarchical task plan +by appropriately replacing `"resource": null` with `"resource": "(unique name of most relevant informational resource)"` +for any case in which such a relevant informational resource can be identified for the corresponding problem/task: + +```json +{htp_json} +``` + +Please return ONLY the UPDATED JSON DICTIONARY and no other text, not even the "```json" wrapping! +""" # noqa: E122 +) + + +HTP_RESULTS_SYNTH_PROMPT_TEMPLATE: str = ( +"""Synthesize an answer/solution for the following question/problem/task: + +``` +{ask} +``` + +given the following supporting information: + +``` +{supporting_info} +``` +""" # noqa: E122 +) diff --git a/openssa/l2/planning/map_reduce/__init__.py b/openssa/l2/planning/map_reduce/__init__.py new file mode 100644 index 000000000..c389875e6 --- /dev/null +++ b/openssa/l2/planning/map_reduce/__init__.py @@ -0,0 +1,23 @@ +"""Map-Reduce planning classes.""" + + +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import TYPE_CHECKING + +from openssa.l2.planning.abstract import AbstractPlan, AbstractPlanner +from openssa.l2.task.status import TaskStatus +from openssa.l2.task.task import Task +from openssa.utils.llms import AnLLM, OpenAILLM + +if TYPE_CHECKING: + from openssa.l2.resource.abstract import AbstractResource + + +class MRTP(AbstractPlan): + """Map-Reduce task plan (MRTP).""" + + +class AutoMRTPlanner(AbstractPlanner): + """Automated (generative) Map-Reduce task planner.""" diff --git a/openssa/l2/planning/map_reduce/_prompts.py b/openssa/l2/planning/map_reduce/_prompts.py new file mode 100644 index 000000000..e69de29bb diff --git a/openssa/l2/reasoning/abstract.py b/openssa/l2/reasoning/abstract.py new file mode 100644 index 000000000..6648d4bc2 --- /dev/null +++ b/openssa/l2/reasoning/abstract.py @@ -0,0 +1,28 @@ +"""Abstract reasoner.""" + + +from abc import ABC, abstractmethod +from dataclasses import dataclass, field + +from openssa.l2.task.task import Task +from openssa.utils.llms import AnLLM, OpenAILLM + + +@dataclass(init=True, + repr=True, + eq=True, + order=False, + unsafe_hash=False, + frozen=False, # mutable + match_args=True, + kw_only=False, + slots=False, + weakref_slot=False) +class AbstractReasoner(ABC): + """Abstract reasoner.""" + + lm: AnLLM = field(default_factory=OpenAILLM.get_gpt_4_1106_preview) + + @abstractmethod + def reason(self, task: Task) -> str: + """Reason through task and return conclusion.""" diff --git a/openssa/l2/reasoning/base.py b/openssa/l2/reasoning/base.py new file mode 100644 index 000000000..d93f14998 --- /dev/null +++ b/openssa/l2/reasoning/base.py @@ -0,0 +1,28 @@ +"""Base reasoner.""" + + +from dataclasses import dataclass + +from openssa.l2.task.abstract import AbstractTask + +from .abstract import AbstractReasoner + + +@dataclass(init=True, + repr=True, + eq=True, + order=False, + unsafe_hash=False, + frozen=False, # mutable + match_args=True, + kw_only=False, + slots=False, + weakref_slot=False) +class BaseReasoner(AbstractReasoner): + """Base reasoner.""" + + def reason(self, task: AbstractTask) -> str: + """Reason through task and return conclusion.""" + return (task.resource.answer(question=task.ask) + if task.resource + else self.lm.get_response(prompt=task.ask)) diff --git a/openssa/l2/reasoning/ooda/__init__.py b/openssa/l2/reasoning/ooda/__init__.py new file mode 100644 index 000000000..123c906f5 --- /dev/null +++ b/openssa/l2/reasoning/ooda/__init__.py @@ -0,0 +1,26 @@ +"""OODA reasoner.""" + + +from dataclasses import dataclass + +from openssa.l2.reasoning.abstract import AbstractReasoner +from openssa.l2.reasoning.base import BaseReasoner +from openssa.l2.task.abstract import AbstractTask + + +@dataclass(init=True, + repr=True, + eq=True, + order=False, + unsafe_hash=False, + frozen=False, # mutable + match_args=True, + kw_only=False, + slots=False, + weakref_slot=False) +class OodaReasoner(BaseReasoner): + """OODA reasoner.""" + + max_depth: int = 3 + + # TODO: full OODA reasoning diff --git a/openssa/l2/reasoning/ooda/_prompts.py b/openssa/l2/reasoning/ooda/_prompts.py new file mode 100644 index 000000000..e69de29bb diff --git a/openssa/l2/resource/_global.py b/openssa/l2/resource/_global.py new file mode 100644 index 000000000..ca9f7ad1c --- /dev/null +++ b/openssa/l2/resource/_global.py @@ -0,0 +1,27 @@ +"""Global informational resources register.""" + + +from typing import TYPE_CHECKING + +from .abstract import AbstractResource + +if TYPE_CHECKING: + from collections.abc import Callable + + +GLOBAL_RESOURCES: dict[str, AbstractResource] = {} + + +def global_register(resource_class): + orig_init: Callable[..., None] = resource_class.__init__ + + def wrapped_init(self, *args, **kwargs) -> None: + orig_init(self, *args, **kwargs) # pylint: disable=unnecessary-dunder-call + + assert self.unique_name not in GLOBAL_RESOURCES, \ + KeyError(f'*** RESOURCE UNIQUE NAME CONFLICT: "{self.unique_name}"') + GLOBAL_RESOURCES[self.unique_name]: AbstractResource = self + + resource_class.__init__: Callable[..., None] = wrapped_init + + return resource_class diff --git a/openssa/l2/resource/_prompts.py b/openssa/l2/resource/_prompts.py new file mode 100644 index 000000000..3aeefe640 --- /dev/null +++ b/openssa/l2/resource/_prompts.py @@ -0,0 +1,7 @@ +RESOURCE_OVERVIEW_PROMPT_TEMPLATE: str = ( + 'Considering that your informational resource is named "{name}" (if that is helpful or relevant), ' + 'within 300 words, ' + 'identify the chief ENTITY/ENTITIES of interest, state the main TIME PERIOD(S) of interest, ' + 'and give an overview of the key KINDS of INFO contained in your resource, ' + 'without mentioning specific facts.' +) diff --git a/openssa/l2/resource/abstract.py b/openssa/l2/resource/abstract.py new file mode 100644 index 000000000..ef7cea536 --- /dev/null +++ b/openssa/l2/resource/abstract.py @@ -0,0 +1,30 @@ +"""Abstract informational resource.""" + + +from abc import ABC, abstractmethod +from functools import cached_property + +from ._prompts import RESOURCE_OVERVIEW_PROMPT_TEMPLATE + + +class AbstractResource(ABC): + """Abstract informational resource.""" + + @cached_property + @abstractmethod + def unique_name(self) -> str: + """Return globally-unique name.""" + + @cached_property + @abstractmethod + def name(self) -> str: + """Return potentially non-unique, but informationally helpful name.""" + + @abstractmethod + def answer(self, question: str) -> str: + """Answer question from informational resource.""" + + @cached_property + def overview(self): + """Return overview of informational resource.""" + return self.answer(question=RESOURCE_OVERVIEW_PROMPT_TEMPLATE.format(name=self.name)) diff --git a/openssa/l2/resource/db.py b/openssa/l2/resource/db.py new file mode 100644 index 000000000..105c6087a --- /dev/null +++ b/openssa/l2/resource/db.py @@ -0,0 +1,10 @@ +"""Database informational resource.""" + + +from .abstract import AbstractResource +from ._global import global_register + + +@global_register +class DbResource(AbstractResource): + """Database informational resource.""" diff --git a/openssa/l2/resource/file.py b/openssa/l2/resource/file.py new file mode 100644 index 000000000..c3d7194ca --- /dev/null +++ b/openssa/l2/resource/file.py @@ -0,0 +1,57 @@ +"""File-stored informational resource.""" + + +from functools import cached_property +import os +from pathlib import Path +from typing import TYPE_CHECKING + +# pylint: disable=unused-import +from llama_index.core.readers.file.base import SimpleDirectoryReader +from llama_index.core.indices.vector_store.base import VectorStoreIndex +from llama_index.core.settings import Settings + +from openssa.integrations.llama_index.backend import Backend as LlamaIndexRAG +from openssa import LlamaIndexSSM + +from .abstract import AbstractResource +from ._global import global_register + +if TYPE_CHECKING: + from openssa.core.backend.rag_backend import AbstractRAGBackend + from openssa.core.ssm.rag_ssm import RAGSSM + + +@global_register +class FileResource(AbstractResource): + """File-stored informational resource.""" + + def __init__(self, path: Path | str): + """Initialize file-stored informational resource and associated RAG.""" + self.path: str = (str(path.resolve(strict=True)) + if isinstance(path, Path) + else path.lstrip().rstrip('/\\')) + + self.rag = LlamaIndexSSM() + if self.path.startswith('s3://'): + self.rag.read_s3(s3_paths=self.path) + else: + self.rag.read_directory(storage_dir=self.path) + + @cached_property + def unique_name(self) -> str: + """Return globally-unique name.""" + return self.path + + @cached_property + def name(self) -> str: + """Return potentially non-unique, but informationally helpful name.""" + return os.path.basename(self.path) + + def __repr__(self) -> str: + """Return string representation.""" + return f'"{self.name}" {type(self).__name__}[{self.path}]' + + def answer(self, question: str) -> str: + """Answer question from informational resource.""" + return self.rag.discuss(user_input=question)['content'] diff --git a/openssa/l2/resource/rss.py b/openssa/l2/resource/rss.py new file mode 100644 index 000000000..49787c707 --- /dev/null +++ b/openssa/l2/resource/rss.py @@ -0,0 +1,10 @@ +"""RSS informational resource.""" + + +from .abstract import AbstractResource +from ._global import global_register + + +@global_register +class RssResource(AbstractResource): + """RSS informational resource.""" diff --git a/openssa/l2/resource/sensor.py b/openssa/l2/resource/sensor.py new file mode 100644 index 000000000..0a827b5a1 --- /dev/null +++ b/openssa/l2/resource/sensor.py @@ -0,0 +1,10 @@ +"""Sensor informational resource.""" + + +from .abstract import AbstractResource +from ._global import global_register + + +@global_register +class SensorResource(AbstractResource): + """Sensor informational resource.""" diff --git a/openssa/l2/resource/web.py b/openssa/l2/resource/web.py new file mode 100644 index 000000000..2d53ea4a1 --- /dev/null +++ b/openssa/l2/resource/web.py @@ -0,0 +1,10 @@ +"""Web informational resource.""" + + +from .abstract import AbstractResource +from ._global import global_register + + +@global_register +class WebResource(AbstractResource): + """Web informational resource.""" diff --git a/openssa/l2/task/abstract.py b/openssa/l2/task/abstract.py new file mode 100644 index 000000000..af2c0fe34 --- /dev/null +++ b/openssa/l2/task/abstract.py @@ -0,0 +1,61 @@ +"""Abstract task.""" + + +from abc import ABC +from dataclasses import dataclass +from typing import Self + +from openssa.l2.resource.abstract import AbstractResource +from openssa.l2.resource._global import GLOBAL_RESOURCES + +from .status import TaskStatus + + +TaskDict: type = dict[str, str | AbstractResource | TaskStatus | None] + + +@dataclass(init=True, + repr=True, + eq=True, + order=False, + unsafe_hash=False, + frozen=False, # mutable + match_args=True, + kw_only=False, + slots=False, + weakref_slot=False) +class AbstractTask(ABC): + """Abstract task.""" + + ask: str + resource: AbstractResource | None = None + status: TaskStatus = TaskStatus.PENDING + result: str | None = None + + @classmethod + def from_dict(cls, d: TaskDict, /) -> Self: + """Create resource instance from dictionary representation.""" + task: Self = cls(**d) + + if isinstance(task.resource, str): + task.resource: AbstractResource = GLOBAL_RESOURCES[task.resource] + + task.status: TaskStatus = TaskStatus(task.status) + + return task + + @classmethod + def from_str(cls, s: str, /) -> Self: + """Create resource instance from dictionary representation.""" + return cls(ask=s) + + @classmethod + def from_dict_or_str(cls, dict_or_str: TaskDict | str, /) -> Self: + """Create resource instance from dictionary or string representation.""" + if isinstance(dict_or_str, dict): + return cls.from_dict(dict_or_str) + + if isinstance(dict_or_str, str): + return cls.from_str(dict_or_str) + + raise TypeError(f'*** {dict_or_str} IS NEITHER A DICTIONARY NOR A STRING ***') diff --git a/openssa/l2/task/status.py b/openssa/l2/task/status.py new file mode 100644 index 000000000..047618278 --- /dev/null +++ b/openssa/l2/task/status.py @@ -0,0 +1,12 @@ +"""Task Status enum.""" + + +from enum import StrEnum, auto + + +class TaskStatus(StrEnum): + PENDING: str = auto() + IN_PROGRESS: str = auto() + DONE: str = auto() + FAILED: str = auto() + TIMED_OUT: str = auto() diff --git a/openssa/l2/task/task.py b/openssa/l2/task/task.py new file mode 100644 index 000000000..3dc771371 --- /dev/null +++ b/openssa/l2/task/task.py @@ -0,0 +1,41 @@ +"""Task.""" + + +from .abstract import AbstractTask + + +class Task(AbstractTask): + """Task.""" + + def execute(self): + if self.all_dependencies_satisfied(): + self.select_agent() + self.status = 'in progress' + try: + self.result = self.agent.execute_task(self.ask) + self.status = 'completed' + except Exception as e: # pylint: disable=broad-exception-caught + self.status = 'failed' + self.result = str(e) + else: + self.status = 'waiting for dependencies' + + def all_dependencies_satisfied(self): + # Check if all dependencies are completed + return all(dep.status == 'completed' for dep in self.dependencies) + + def select_agent(self): + # Logic to select the most appropriate agent based on the task's requirements + pass + + +class UnitTask(Task): + def execute(self): + # Direct execution logic for UnitTasks, potentially overriding Task's execute method + pass + + +# Agent interface (to be implemented by each agent) +class Agent: + def execute_task(self, task_description): + pass diff --git a/openssa/l2/tool/abstract.py b/openssa/l2/tool/abstract.py new file mode 100644 index 000000000..4095a698d --- /dev/null +++ b/openssa/l2/tool/abstract.py @@ -0,0 +1,5 @@ +"""Asbtract Tool.""" + + +class AbstractTool: + """Asbtract Tool.""" From 552509218a7edc92aba1b77e44e2c1388e6ead80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?The=20Vinh=20LUONG=20=28LU=CC=9BO=CC=9BNG=20The=CC=82?= =?UTF-8?q?=CC=81=20Vinh=29?= Date: Wed, 6 Mar 2024 18:26:15 -0800 Subject: [PATCH 3/4] remove unused imports from openssa.l2 submodules --- openssa/l2/planning/map_reduce/__init__.py | 11 ----------- openssa/l2/reasoning/ooda/__init__.py | 2 -- openssa/l2/resource/file.py | 11 ----------- 3 files changed, 24 deletions(-) diff --git a/openssa/l2/planning/map_reduce/__init__.py b/openssa/l2/planning/map_reduce/__init__.py index c389875e6..297b7b11d 100644 --- a/openssa/l2/planning/map_reduce/__init__.py +++ b/openssa/l2/planning/map_reduce/__init__.py @@ -1,18 +1,7 @@ """Map-Reduce planning classes.""" -from __future__ import annotations - -from dataclasses import dataclass, field -from typing import TYPE_CHECKING - from openssa.l2.planning.abstract import AbstractPlan, AbstractPlanner -from openssa.l2.task.status import TaskStatus -from openssa.l2.task.task import Task -from openssa.utils.llms import AnLLM, OpenAILLM - -if TYPE_CHECKING: - from openssa.l2.resource.abstract import AbstractResource class MRTP(AbstractPlan): diff --git a/openssa/l2/reasoning/ooda/__init__.py b/openssa/l2/reasoning/ooda/__init__.py index 123c906f5..d7793b500 100644 --- a/openssa/l2/reasoning/ooda/__init__.py +++ b/openssa/l2/reasoning/ooda/__init__.py @@ -3,9 +3,7 @@ from dataclasses import dataclass -from openssa.l2.reasoning.abstract import AbstractReasoner from openssa.l2.reasoning.base import BaseReasoner -from openssa.l2.task.abstract import AbstractTask @dataclass(init=True, diff --git a/openssa/l2/resource/file.py b/openssa/l2/resource/file.py index c3d7194ca..24333db69 100644 --- a/openssa/l2/resource/file.py +++ b/openssa/l2/resource/file.py @@ -4,23 +4,12 @@ from functools import cached_property import os from pathlib import Path -from typing import TYPE_CHECKING -# pylint: disable=unused-import -from llama_index.core.readers.file.base import SimpleDirectoryReader -from llama_index.core.indices.vector_store.base import VectorStoreIndex -from llama_index.core.settings import Settings - -from openssa.integrations.llama_index.backend import Backend as LlamaIndexRAG from openssa import LlamaIndexSSM from .abstract import AbstractResource from ._global import global_register -if TYPE_CHECKING: - from openssa.core.backend.rag_backend import AbstractRAGBackend - from openssa.core.ssm.rag_ssm import RAGSSM - @global_register class FileResource(AbstractResource): From 724d36f2e53130168b9cdfd2157738b7a28d905a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?The=20Vinh=20LUONG=20=28LU=CC=9BO=CC=9BNG=20The=CC=82?= =?UTF-8?q?=CC=81=20Vinh=29?= Date: Wed, 6 Mar 2024 18:26:47 -0800 Subject: [PATCH 4/4] remove unused import & disable Pylint in openssa.l2.planning.hierarchical submodule --- openssa/l2/planning/hierarchical/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openssa/l2/planning/hierarchical/__init__.py b/openssa/l2/planning/hierarchical/__init__.py index 974249055..a7d3f8317 100644 --- a/openssa/l2/planning/hierarchical/__init__.py +++ b/openssa/l2/planning/hierarchical/__init__.py @@ -15,7 +15,6 @@ from openssa.l2.task.abstract import TaskDict from openssa.l2.task.status import TaskStatus from openssa.l2.task.task import Task -from openssa.utils.llms import AnLLM, OpenAILLM from ._prompts import (HTP_PROMPT_TEMPLATE, HTP_WITH_RESOURCES_PROMPT_TEMPLATE, HTP_UPDATE_RESOURCES_PROMPT_TEMPLATE, HTP_RESULTS_SYNTH_PROMPT_TEMPLATE) @@ -52,7 +51,7 @@ class HTP(AbstractPlan): @classmethod def from_dict(cls, htp_dict: HTPDict, /) -> HTP: """Create hierarchical task plan from dictionary representation.""" - return HTP(task=Task.from_dict_or_str(htp_dict['task']), + return HTP(task=Task.from_dict_or_str(htp_dict['task']), # pylint: disable=unexpected-keyword-arg sub_plans=[HTP.from_dict(d) for d in htp_dict.get('sub-plans', [])]) def to_dict(self) -> HTPDict: