summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorIgor Radovanovic <74266147+IgorWounds@users.noreply.github.com>2024-04-19 17:49:53 +0200
committerGitHub <noreply@github.com>2024-04-19 15:49:53 +0000
commit04d3af5c5062bbbfdc49cebea9eff2436b4e82d3 (patch)
tree5b0e264fbc916c61b8362c09f16e55a3b0f171d2
parentfe0debe12c2bd6854f3789016707b0b00bbca3df (diff)
[Feature] - LLM mode (#6308)
* LLM mode * Docstring truncation * Preferences * Output type * use settings instead? (#6314) * Refactor with @montezdesousa * fix test * fix: max length type * field descriptions * try with FieldInfo * ugly... * partial * override FieldInfo __repr__ * typing * separate model_config * rename OpenBBCustomParameter to OpenBBField * lint * pydocstyle * don't include choices if no choices --------- Co-authored-by: montezdesousa <79287829+montezdesousa@users.noreply.github.com> Co-authored-by: Henrique Joaquim <henriquecjoaquim@gmail.com> Co-authored-by: Diogo Sousa <montezdesousa@gmail.com>
-rw-r--r--openbb_platform/core/openbb_core/app/model/api_settings.py (renamed from openbb_platform/core/openbb_core/app/model/fast_api_settings.py)2
-rw-r--r--openbb_platform/core/openbb_core/app/model/custom_parameter.py37
-rw-r--r--openbb_platform/core/openbb_core/app/model/field.py28
-rw-r--r--openbb_platform/core/openbb_core/app/model/obbject.py18
-rw-r--r--openbb_platform/core/openbb_core/app/model/preferences.py24
-rw-r--r--openbb_platform/core/openbb_core/app/model/python_settings.py23
-rw-r--r--openbb_platform/core/openbb_core/app/model/system_settings.py8
-rw-r--r--openbb_platform/core/openbb_core/app/service/system_service.py1
-rw-r--r--openbb_platform/core/openbb_core/app/static/package_builder.py163
-rw-r--r--openbb_platform/core/tests/app/static/test_package_builder.py2
-rw-r--r--openbb_platform/openbb/assets/reference.json26
-rw-r--r--openbb_platform/openbb/package/crypto.py6
-rw-r--r--openbb_platform/openbb/package/crypto_price.py14
-rw-r--r--openbb_platform/openbb/package/currency.py12
-rw-r--r--openbb_platform/openbb/package/currency_price.py14
-rw-r--r--openbb_platform/openbb/package/derivatives_options.py14
-rw-r--r--openbb_platform/openbb/package/economy.py134
-rw-r--r--openbb_platform/openbb/package/economy_gdp.py40
-rw-r--r--openbb_platform/openbb/package/equity.py20
-rw-r--r--openbb_platform/openbb/package/equity_calendar.py46
-rw-r--r--openbb_platform/openbb/package/equity_compare.py8
-rw-r--r--openbb_platform/openbb/package/equity_discovery.py45
-rw-r--r--openbb_platform/openbb/package/equity_estimates.py31
-rw-r--r--openbb_platform/openbb/package/equity_fundamental.py252
-rw-r--r--openbb_platform/openbb/package/equity_ownership.py40
-rw-r--r--openbb_platform/openbb/package/equity_price.py30
-rw-r--r--openbb_platform/openbb/package/equity_shorts.py8
-rw-r--r--openbb_platform/openbb/package/etf.py62
-rw-r--r--openbb_platform/openbb/package/fixedincome.py12
-rw-r--r--openbb_platform/openbb/package/fixedincome_corporate.py71
-rw-r--r--openbb_platform/openbb/package/fixedincome_government.py19
-rw-r--r--openbb_platform/openbb/package/fixedincome_rate.py76
-rw-r--r--openbb_platform/openbb/package/fixedincome_spreads.py40
-rw-r--r--openbb_platform/openbb/package/index.py24
-rw-r--r--openbb_platform/openbb/package/news.py28
-rw-r--r--openbb_platform/openbb/package/regulators_sec.py36
-rw-r--r--website/content/platform/development/how-to/add_data_provider_extension.md3
37 files changed, 607 insertions, 810 deletions
diff --git a/openbb_platform/core/openbb_core/app/model/fast_api_settings.py b/openbb_platform/core/openbb_core/app/model/api_settings.py
index 24ad76a3916..1eccb73eaef 100644
--- a/openbb_platform/core/openbb_core/app/model/fast_api_settings.py
+++ b/openbb_platform/core/openbb_core/app/model/api_settings.py
@@ -24,7 +24,7 @@ class Servers(BaseModel):
description: str = "Local OpenBB development server"
-class FastAPISettings(BaseModel):
+class APISettings(BaseModel):
"""Settings model for FastAPI configuration."""
model_config = ConfigDict(frozen=True)
diff --git a/openbb_platform/core/openbb_core/app/model/custom_parameter.py b/openbb_platform/core/openbb_core/app/model/custom_parameter.py
deleted file mode 100644
index 58cfb3faa53..00000000000
--- a/openbb_platform/core/openbb_core/app/model/custom_parameter.py
+++ /dev/null
@@ -1,37 +0,0 @@
-"""Custom parameter and choices for OpenBB."""
-
-import sys
-from dataclasses import dataclass
-from typing import Dict, Optional
-
-from typing_extensions import LiteralString
-
-# `slots` is available on Python >= 3.10
-if sys.version_info >= (3, 10):
- slots_true = {"slots": True}
-else:
- slots_true: Dict[str, bool] = {}
-
-
-class BaseMetadata:
- """Base class for all metadata.
-
- This exists mainly so that implementers
- can do `isinstance(..., BaseMetadata)` while traversing field annotations.
- """
-
- __slots__ = ()
-
-
-@dataclass(frozen=True, **slots_true)
-class OpenBBCustomParameter(BaseMetadata):
- """Custom parameter for OpenBB."""
-
- description: Optional[str] = None
-
-
-@dataclass(frozen=True, **slots_true)
-class OpenBBCustomChoices(BaseMetadata):
- """Custom choices for OpenBB."""
-
- choices: Optional[LiteralString] = None
diff --git a/openbb_platform/core/openbb_core/app/model/field.py b/openbb_platform/core/openbb_core/app/model/field.py
new file mode 100644
index 00000000000..d791cb08f08
--- /dev/null
+++ b/openbb_platform/core/openbb_core/app/model/field.py
@@ -0,0 +1,28 @@
+"""Custom field for OpenBB."""
+
+from typing import Any, List, Optional
+
+from pydantic.fields import FieldInfo
+
+
+class OpenBBField(FieldInfo):
+ """Custom field for OpenBB."""
+
+ def __repr__(self):
+ """Override FieldInfo __repr__."""
+ # We use repr() to avoid decoding special characters like \n
+ if self.choices:
+ return f"OpenBBField(description={repr(self.description)}, choices={repr(self.choices)})"
+ return f"OpenBBField(description={repr(self.description)})"
+
+ def __init__(self, description: str, choices: Optional[List[Any]] = None):
+ """Initialize OpenBBField."""
+ json_schema_extra = {"choices": choices} if choices else None
+ super().__init__(description=description, json_schema_extra=json_schema_extra) # type: ignore[arg-type]
+
+ @property
+ def choices(self) -> Optional[List[Any]]:
+ """Custom choices."""
+ if self.json_schema_extra:
+ return self.json_schema_extra.get("choices") # type: ignore[union-attr,return-value]
+ return None
diff --git a/openbb_platform/core/openbb_core/app/model/obbject.py b/openbb_platform/core/openbb_core/app/model/obbject.py
index 92038ea4970..2db09e0cd00 100644
--- a/openbb_platform/core/openbb_core/app/model/obbject.py
+++ b/openbb_platform/core/openbb_core/app/model/obbject.py
@@ -243,6 +243,24 @@ class OBBject(Tagged, Generic[T]):
del results["index"]
return results
+ def to_llm(self) -> Union[Dict[Hashable, Any], List[Dict[Hashable, Any]]]:
+ """Convert results field to an LLM compatible output.
+
+ Returns
+ -------
+ Union[Dict[Hashable, Any], List[Dict[Hashable, Any]]]
+ Dictionary of lists or list of dictionaries if orient is "records".
+ """
+ df = self.to_dataframe(index=None)
+
+ results = df.to_json(
+ orient="records",
+ date_format="iso",
+ date_unit="s",
+ )
+
+ return results
+
def show(self, **kwargs: Any) -> None:
"""Display chart."""
# pylint: disable=no-member
diff --git a/openbb_platform/core/openbb_core/app/model/preferences.py b/openbb_platform/core/openbb_core/app/model/preferences.py
index 57987020b17..19c2d7f745d 100644
--- a/openbb_platform/core/openbb_core/app/model/preferences.py
+++ b/openbb_platform/core/openbb_core/app/model/preferences.py
@@ -9,24 +9,28 @@ from pydantic import BaseModel, ConfigDict, Field, PositiveInt
class Preferences(BaseModel):
"""Preferences for the OpenBB platform."""
- data_directory: str = str(Path.home() / "OpenBBUserData")
- export_directory: str = str(Path.home() / "OpenBBUserData" / "exports")
- user_styles_directory: str = str(Path.home() / "OpenBBUserData" / "styles" / "user")
cache_directory: str = str(Path.home() / "OpenBBUserData" / "cache")
chart_style: Literal["dark", "light"] = "dark"
+ data_directory: str = str(Path.home() / "OpenBBUserData")
+ export_directory: str = str(Path.home() / "OpenBBUserData" / "exports")
+ metadata: bool = True
+ output_type: Literal[
+ "OBBject", "dataframe", "polars", "numpy", "dict", "chart", "llm"
+ ] = Field(
+ default="OBBject",
+ description="Python default output type.",
+ validate_default=True,
+ )
plot_enable_pywry: bool = True
- plot_pywry_width: PositiveInt = 1400
- plot_pywry_height: PositiveInt = 762
plot_open_export: bool = (
False # Whether to open plot image exports after they are created
)
- table_style: Literal["dark", "light"] = "dark"
+ plot_pywry_height: PositiveInt = 762
+ plot_pywry_width: PositiveInt = 1400
request_timeout: PositiveInt = 15
- metadata: bool = True
- output_type: Literal["OBBject", "dataframe", "polars", "numpy", "dict", "chart"] = (
- Field(default="OBBject", description="Python default output type.")
- )
show_warnings: bool = True
+ table_style: Literal["dark", "light"] = "dark"
+ user_styles_directory: str = str(Path.home() / "OpenBBUserData" / "styles" / "user")
model_config = ConfigDict(validate_assignment=True)
diff --git a/openbb_platform/core/openbb_core/app/model/python_settings.py b/openbb_platform/core/openbb_core/app/model/python_settings.py
new file mode 100644
index 00000000000..f03648c92b4
--- /dev/null
+++ b/openbb_platform/core/openbb_core/app/model/python_settings.py
@@ -0,0 +1,23 @@
+"""Python configuration settings model."""
+
+from typing import List, Optional
+
+from pydantic import BaseModel, Field, PositiveInt
+
+
+class PythonSettings(BaseModel):
+ """Settings model for Python interface configuration."""
+
+ docstring_sections: List[str] = Field(
+ default_factory=lambda: ["description", "parameters", "returns", "examples"],
+ description="Sections to include in autogenerated docstrings.",
+ )
+ docstring_max_length: Optional[PositiveInt] = Field(
+ default=None, description="Maximum length of autogenerated docstrings."
+ )
+
+ def __repr__(self) -> str:
+ """Return a string representation of the model."""
+ return f"{self.__class__.__name__}\n\n" + "\n".join(
+ f"{k}: {v}" for k, v in self.model_dump().items()
+ )
diff --git a/openbb_platform/core/openbb_core/app/model/system_settings.py b/openbb_platform/core/openbb_core/app/model/system_settings.py
index db30f6c20df..c5439fb760d 100644
--- a/openbb_platform/core/openbb_core/app/model/system_settings.py
+++ b/openbb_platform/core/openbb_core/app/model/system_settings.py
@@ -14,7 +14,8 @@ from openbb_core.app.constants import (
USER_SETTINGS_PATH,
)
from openbb_core.app.model.abstract.tagged import Tagged
-from openbb_core.app.model.fast_api_settings import FastAPISettings
+from openbb_core.app.model.api_settings import APISettings
+from openbb_core.app.model.python_settings import PythonSettings
from openbb_core.app.version import CORE_VERSION, VERSION
@@ -46,7 +47,10 @@ class SystemSettings(Tagged):
log_collect: bool = True
# API section
- api_settings: FastAPISettings = Field(default_factory=FastAPISettings)
+ api_settings: APISettings = Field(default_factory=APISettings)
+
+ # Python section
+ python_settings: PythonSettings = Field(default_factory=PythonSettings)
# Others
debug_mode: bool = False
diff --git a/openbb_platform/core/openbb_core/app/service/system_service.py b/openbb_platform/core/openbb_core/app/service/system_service.py
index fd7b565b588..906ff6808ec 100644
--- a/openbb_platform/core/openbb_core/app/service/system_service.py
+++ b/openbb_platform/core/openbb_core/app/service/system_service.py
@@ -20,6 +20,7 @@ class SystemService(metaclass=SingletonMeta):
"headless",
"logging_sub_app",
"api_settings",
+ "python_settings",
"debug_mode",
}
diff --git a/openbb_platform/core/openbb_core/app/static/package_builder.py b/openbb_platform/core/openbb_core/app/static/package_builder.py
index 52073a05cff..8a2a86a6619 100644
--- a/openbb_platform/core/openbb_core/app/static/package_builder.py
+++ b/openbb_platform/core/openbb_core/app/static/package_builder.py
@@ -6,7 +6,7 @@ import inspect
import re
import shutil
import sys
-from dataclasses import Field
+from dataclasses import Field as DCField
from functools import partial
from inspect import Parameter, _empty, isclass, signature
from json import dumps, load
@@ -38,14 +38,12 @@ from starlette.routing import BaseRoute
from typing_extensions import Annotated, _AnnotatedAlias
from openbb_core.app.extension_loader import ExtensionLoader, OpenBBGroups
-from openbb_core.app.model.custom_parameter import (
- OpenBBCustomChoices,
- OpenBBCustomParameter,
-)
from openbb_core.app.model.example import Example
+from openbb_core.app.model.field import OpenBBField
from openbb_core.app.model.obbject import OBBject
from openbb_core.app.provider_interface import ProviderInterface
from openbb_core.app.router import RouterLoader
+from openbb_core.app.service.system_service import SystemService
from openbb_core.app.static.utils.console import Console
from openbb_core.app.static.utils.linters import Linters
from openbb_core.app.version import CORE_VERSION, VERSION
@@ -354,7 +352,6 @@ class ImportDefinition:
hint_type_list = cls.get_path_hint_type_list(path=path)
code = "from openbb_core.app.static.container import Container"
code += "\nfrom openbb_core.app.model.obbject import OBBject"
- code += "\nfrom openbb_core.app.model.custom_parameter import OpenBBCustomParameter, OpenBBCustomChoices"
# These imports were not detected before build, so we add them manually and
# ruff --fix the resulting code to remove unused imports.
@@ -363,6 +360,7 @@ class ImportDefinition:
code += "\nimport pandas"
code += "\nimport numpy"
code += "\nimport datetime"
+ code += "\nfrom datetime import date"
code += "\nimport pydantic"
code += "\nfrom pydantic import BaseModel"
code += "\nfrom inspect import Parameter"
@@ -379,6 +377,7 @@ class ImportDefinition:
code += "\nfrom openbb_core.app.static.utils.filters import filter_inputs\n"
code += "\nfrom openbb_core.provider.abstract.data import Data"
code += "\nfrom openbb_core.app.deprecation import OpenBBDeprecationWarning\n"
+ code += "\nfrom openbb_core.app.model.field import OpenBBField"
if path.startswith("/quantitative"):
code += "\nfrom openbb_quantitative.models import "
code += "(CAPMModel,NormalityModel,OmegaModel,SummaryModel,UnitRootModel)"
@@ -581,8 +580,8 @@ class MethodDefinition:
kind=Parameter.POSITIONAL_OR_KEYWORD,
annotation=Annotated[
bool,
- OpenBBCustomParameter(
- description="Whether to create a chart or not, by default False."
+ OpenBBField(
+ description="Whether to create a chart or not, by default False.",
),
],
default=False,
@@ -604,14 +603,14 @@ class MethodDefinition:
name="provider",
kind=Parameter.POSITIONAL_OR_KEYWORD,
annotation=Annotated[
- Union[MethodDefinition.get_type(field), None],
- OpenBBCustomParameter(
+ Optional[MethodDefinition.get_type(field)],
+ OpenBBField(
description=(
"The provider to use for the query, by default None.\n"
f" If None, the provider specified in defaults is selected or '{first}' if there is\n"
" no default."
""
- )
+ ),
),
],
default=None,
@@ -662,7 +661,7 @@ class MethodDefinition:
):
"""Add the field custom description and choices to the param signature as annotations."""
if model_name:
- available_fields: Dict[str, Field] = (
+ available_fields: Dict[str, DCField] = (
ProviderInterface().params[model_name]["standard"].__dataclass_fields__
)
@@ -671,27 +670,26 @@ class MethodDefinition:
continue
field_default = available_fields[param].default
-
choices = getattr(field_default, "json_schema_extra", {}).get(
"choices", []
)
description = getattr(field_default, "description", "")
- if choices:
- new_value = value.replace(
- annotation=Annotated[
- value.annotation,
- OpenBBCustomParameter(description=description),
- OpenBBCustomChoices(choices=choices),
- ],
- )
- else:
- new_value = value.replace(
- annotation=Annotated[
- value.annotation,
- OpenBBCustomParameter(description=description),
- ],
- )
+ PartialParameter = partial(
+ OpenBBField,
+ description=description,
+ )
+
+ new_value = value.replace(
+ annotation=Annotated[
+ value.annotation,
+ (
+ PartialParameter(choices=choices)
+ if choices
+ else PartialParameter()
+ ),
+ ],
+ )
od[param] = new_value
@@ -1044,6 +1042,7 @@ class DocstringGenerator:
kwarg_params: dict,
returns: Dict[str, FieldInfo],
results_type: str,
+ sections: List[str],
) -> str:
"""Create the docstring for model."""
@@ -1077,50 +1076,56 @@ class DocstringGenerator:
description = getattr(metadata[0], "description", "") if metadata else ""
return type_, description
- docstring = summary.strip("\n").replace("\n ", f"\n{create_indent(2)}")
- docstring += "\n\n"
- docstring += f"{create_indent(2)}Parameters\n"
- docstring += f"{create_indent(2)}----------\n"
-
- # Explicit parameters
- for param_name, param in explicit_params.items():
- type_, description = get_param_info(param)
- type_str = format_type(str(type_), char_limit=79)
- docstring += f"{create_indent(2)}{param_name} : {type_str}\n"
- docstring += f"{create_indent(3)}{format_description(description)}\n"
-
- # Kwargs
- for param_name, param in kwarg_params.items():
- p_type = getattr(param, "type", "")
- type_ = (
- getattr(p_type, "__name__", "") if inspect.isclass(p_type) else p_type
- )
+ # Description summary
+ if "description" in sections:
+ docstring = summary.strip("\n").replace("\n ", f"\n{create_indent(2)}")
+ docstring += "\n\n"
+ if "parameters" in sections:
+ docstring += f"{create_indent(2)}Parameters\n"
+ docstring += f"{create_indent(2)}----------\n"
+
+ # Explicit parameters
+ for param_name, param in explicit_params.items():
+ type_, description = get_param_info(param)
+ type_str = format_type(str(type_), char_limit=79)
+ docstring += f"{create_indent(2)}{param_name} : {type_str}\n"
+ docstring += f"{create_indent(3)}{format_description(description)}\n"
+
+ # Kwargs
+ for param_name, param in kwarg_params.items():
+ p_type = getattr(param, "type", "")
+ type_ = (
+ getattr(p_type, "__name__", "")
+ if inspect.isclass(p_type)
+ else p_type
+ )
- if "NoneType" in str(type_):
- type_ = f"Optional[{type_}]".replace(", NoneType", "")
-
- default = getattr(param, "default", "")
- description = getattr(default, "description", "")
- docstring += f"{create_indent(2)}{param_name} : {type_}\n"
- docstring += f"{create_indent(3)}{format_description(description)}\n"
-
- # Returns
- docstring += "\n"
- docstring += f"{create_indent(2)}Returns\n"
- docstring += f"{create_indent(2)}-------\n"
- providers, _ = get_param_info(explicit_params.get("provider", None))
- docstring += cls.get_OBBject_description(results_type, providers)
-
- # Schema
- underline = "-" * len(model_name)
- docstring += f"\n{create_indent(2)}{model_name}\n"
- docstring += f"{create_indent(2)}{underline}\n"
-
- for name, field in returns.items():
- field_type = cls.get_field_type(field.annotation, field.is_required())
- description = getattr(field, "description", "")
- docstring += f"{create_indent(2)}{field.alias or name} : {field_type}\n"
- docstring += f"{create_indent(3)}{format_description(description)}\n"
+ if "NoneType" in str(type_):
+ type_ = f"Optional[{type_}]".replace(", NoneType", "")
+
+ default = getattr(param, "default", "")
+ description = getattr(default, "description", "")
+ docstring += f"{create_indent(2)}{param_name} : {type_}\n"
+ docstring += f"{create_indent(3)}{format_description(description)}\n"
+
+ if "returns" in sections:
+ # Returns
+ docstring += "\n"
+ docstring += f"{create_indent(2)}Returns\n"
+ docstring += f"{create_indent(2)}-------\n"
+ providers, _ = get_param_info(explicit_params.get("provider", None))
+ docstring += cls.get_OBBject_description(results_type, providers)
+
+ # Schema
+ underline = "-" * len(model_name)
+ docstring += f"\n{create_indent(2)}{model_name}\n"
+ docstring += f"{create_indent(2)}{underline}\n"
+
+ for name, field in returns.items():
+ field_type = cls.get_field_type(field.annotation, field.is_required())
+ description = getattr(field, "description", "")
+ docstring += f"{create_indent(2)}{field.alias or name} : {field_type}\n"
+ docstring += f"{create_indent(3)}{format_description(description)}\n"
return docstring
@classmethod</