From c1eaac0e7dce1ff141bea15bca60c88101392750 Mon Sep 17 00:00:00 2001 From: Glenn Galvizo Date: Thu, 30 Jan 2025 08:33:19 -0800 Subject: [PATCH 01/10] Putting a hold on this -- going to wait for the "init" command PR to be pushed. TODO: [] - tests for different sentence transformers models [] - tests for embedding model use from OpenAI endpoint --- docs/source/env.rst | 41 +++++-- libs/agentc_cli/agentc_cli/cmds/env.py | 4 +- libs/agentc_cli/agentc_cli/cmds/index.py | 2 + libs/agentc_cli/agentc_cli/main.py | 18 ++- .../agentc_core/catalog/descriptor.py | 7 +- .../agentc_core/learned/embedding.py | 112 +++++++++++------- libs/agentc_core/agentc_core/learned/model.py | 17 +++ .../agentc_core/tool/descriptor/models.py | 24 +++- .../agentc_core/tool/descriptor/secrets.py | 10 +- .../tool/generate/templates/semantic_q.jinja | 2 +- .../tool/templates/semantic_search.jinja | 10 +- 11 files changed, 175 insertions(+), 72 deletions(-) create mode 100644 libs/agentc_core/agentc_core/learned/model.py diff --git a/docs/source/env.rst b/docs/source/env.rst index 8ef627f0..d47c983b 100644 --- a/docs/source/env.rst +++ b/docs/source/env.rst @@ -23,7 +23,7 @@ Mandatory Environment Variables The username of the account/database access key used to access your Couchbase cluster. ``AGENT_CATALOG_PASSWORD`` - The password of the account/database access key used to access your Couchbase cluster + The password of the account/database access key used to access your Couchbase cluster. ``AGENT_CATALOG_BUCKET`` The name of the bucket where your catalog and all audit logs are/will be stored. @@ -34,9 +34,8 @@ Optional Environment Variables ``AGENT_CATALOG_CONN_ROOT_CERTIFICATE`` Path to the `TLS `_ Root Certificate associated with your - Couchbase cluster for secure connection establishment. - - Instructions for Couchbase Server certificates can be found `here `_. + Couchbase cluster. + More information about Couchbase Server certificates can be found `here `_. ``AGENT_CATALOG_ACTIVITY`` The location on your filesystem that denotes where the local audit logs are stored. @@ -68,17 +67,39 @@ Optional Environment Variables The location + filename of the audit logs that the :python:`agentc.Auditor` will write to. By default, the :python:`agentc.Auditor` class will write and rotate logs in the :file:`./agent-activity` directory. -``AGENT_CATALOG_EMBEDDING_MODEL`` +``AGENT_CATALOG_EMBEDDING_MODEL_NAME`` The embedding model that Agent Catalog will use when indexing and querying tools and prompts. This *must* be a valid embedding model that is supported by the :python:`sentence_transformers.SentenceTransformer` - class. + class *or* the name of a model that can be used from the endpoint specified in the environment variable + ``AGENT_CATALOG_EMBEDDING_MODEL_URL``. By default, the ``sentence-transformers/all-MiniLM-L12-v2`` model is used. +``AGENT_CATALOG_EMBEDDING_MODEL_URL`` + An OpenAI-standard client base URL whose ``/embeddings`` endpoint will be used to generate embeddings for Agent + Catalog tools and prompts. + The specified endpoint *must* host the embedding model given in ``AGENT_CATALOG_EMBEDDING_MODEL_NAME``. + If this variable is specified, Agent Catalog will assume the model given in ``AGENT_CATALOG_EMBEDDING_MODEL_NAME`` + should be accessed through an OpenAI-standard interface. + This variable *must* be specified with ``AGENT_CATALOG_EMBEDDING_MODEL_AUTH``. + By default, this variable is not set (thus, a locally hosted SentenceTransformers is used). + +``AGENT_CATALOG_EMBEDDING_MODEL_AUTH`` + The field used in the authorization header of all OpenAI-standard client embedding requests. + For embedding models hosted by OpenAI, this field refers to the API key. + For embedding models hosted by Capella, this field refers to the Base64-encoded value of + ``MY_USERNAME.MY_PASSWORD``. + If this variable is specified, Agent Catalog will assume the model given in ``AGENT_CATALOG_EMBEDDING_MODEL_NAME`` + should be accessed through an OpenAI-standard interface. + This variable *must* be specified with ``AGENT_CATALOG_EMBEDDING_MODEL_URL``. + By default, this variable is not set (thus, a locally hosted SentenceTransformers is used). + ``AGENT_CATALOG_INDEX_PARTITION`` - Required for advanced vector index definition. This is an integer that defines the number of index partitions on your node. - If not set, this value is ``2 * number of nodes with 'search' service`` on your cluster. + The number of index partitions associated with your cluster. + This variable is used during the creation of vector indexes for semantic catalog search. + By default, this value is set to ``2 * number of nodes with 'search' service on your cluster``. More information on index partitioning can be found `here `_. ``AGENT_CATALOG_MAX_SOURCE_PARTITION`` - Required for advanced vector index definition. This is an integer that defines the maximum number of source partitions. - If not set, this value is 1024. \ No newline at end of file + The maximum number of source partitions associated with your cluster. + This variable is used during the creation of vector indexes for semantic catalog search. + By default, this value is set to 1024. diff --git a/libs/agentc_cli/agentc_cli/cmds/env.py b/libs/agentc_cli/agentc_cli/cmds/env.py index 3ef0f4fc..1e170e6d 100644 --- a/libs/agentc_cli/agentc_cli/cmds/env.py +++ b/libs/agentc_cli/agentc_cli/cmds/env.py @@ -26,7 +26,9 @@ def cmd_env(ctx: Context = None): "AGENT_CATALOG_SNAPSHOT": os.getenv("AGENT_CATALOG_SNAPSHOT", LATEST_SNAPSHOT_VERSION), "AGENT_CATALOG_PROVIDER_OUTPUT": os.getenv("AGENT_CATALOG_PROVIDER_OUTPUT", None), "AGENT_CATALOG_AUDITOR_OUTPUT": os.getenv("AGENT_CATALOG_AUDITOR_OUTPUT", None), - "AGENT_CATALOG_EMBEDDING_MODEL": os.getenv("AGENT_CATALOG_EMBEDDING_MODEL", DEFAULT_EMBEDDING_MODEL), + "AGENT_CATALOG_EMBEDDING_MODEL_NAME": os.getenv("AGENT_CATALOG_EMBEDDING_MODEL_NAME", DEFAULT_EMBEDDING_MODEL), + "AGENT_CATALOG_EMBEDDING_MODEL_URL": os.getenv("AGENT_CATALOG_EMBEDDING_MODEL_URL", None), + "AGENT_CATALOG_EMBEDDING_MODEL_AUTH": os.getenv("AGENT_CATALOG_EMBEDDING_MODEL_AUTH", None), } for line in json.dumps(environment_dict, indent=4).split("\n"): if re.match(r'\s*"AGENT_CATALOG_.*": (?!null)', line): diff --git a/libs/agentc_cli/agentc_cli/cmds/index.py b/libs/agentc_cli/agentc_cli/cmds/index.py index efea3d85..0ef5ec21 100644 --- a/libs/agentc_cli/agentc_cli/cmds/index.py +++ b/libs/agentc_cli/agentc_cli/cmds/index.py @@ -29,6 +29,7 @@ def cmd_index( source_dirs: list[str | os.PathLike], kinds: list[typing.Literal["tool", "prompt"]], embedding_model_name: str = DEFAULT_EMBEDDING_MODEL, + embedding_model_url: str = None, dry_run: bool = False, ctx: Context = None, **_, @@ -54,6 +55,7 @@ def cmd_index( repo, get_path_version = load_repository(pathlib.Path(os.getcwd())) embedding_model = EmbeddingModel( embedding_model_name=embedding_model_name, + embedding_model_url=embedding_model_url, catalog_path=pathlib.Path(ctx.catalog), ) diff --git a/libs/agentc_cli/agentc_cli/main.py b/libs/agentc_cli/agentc_cli/main.py index 54b8d084..74c022ce 100644 --- a/libs/agentc_cli/agentc_cli/main.py +++ b/libs/agentc_cli/agentc_cli/main.py @@ -74,7 +74,8 @@ def resolve_command(self, ctx, args): @click.group( cls=AliasedGroup, - epilog="See: https://docs.couchbase.com or https://couchbaselabs.github.io/agent-catalog/index.html# for more information.", + epilog="See: https://docs.couchbase.com or " + "https://couchbaselabs.github.io/agent-catalog/index.html# for more information.", context_settings=dict(max_content_width=800), ) @click.option( @@ -450,9 +451,15 @@ def find( ) @click.option( "-em", - "--embedding-model", + "--embedding-model-name", default=DEFAULT_EMBEDDING_MODEL, - help="Embedding model used when indexing source files into the local catalog.", + help="Name of the embedding model used when indexing source files into the local catalog.", + show_default=True, +) +@click.option( + "--embedding-model-url", + default=None, + help="Base URL of an OpenAI-standard endpoint that exposes an embedding model.", show_default=True, ) @click.option( @@ -463,7 +470,7 @@ def find( show_default=True, ) @click.pass_context -def index(ctx, source_dirs, tools, prompts, embedding_model, dry_run): +def index(ctx, source_dirs, tools, prompts, embedding_model_name, embedding_model_url, dry_run): """Walk the source directory trees (SOURCE_DIRS) to index source files into the local catalog. Source files that will be scanned include *.py, *.sqlpp, *.yaml, etc.""" @@ -488,7 +495,8 @@ def index(ctx, source_dirs, tools, prompts, embedding_model, dry_run): ctx=ctx.obj, source_dirs=source_dirs, kinds=kinds, - embedding_model_name=embedding_model, + embedding_model_name=embedding_model_name, + embedding_model_url=embedding_model_url, dry_run=dry_run, ) diff --git a/libs/agentc_core/agentc_core/catalog/descriptor.py b/libs/agentc_core/agentc_core/catalog/descriptor.py index 3bcea87a..7cdb3daf 100644 --- a/libs/agentc_core/agentc_core/catalog/descriptor.py +++ b/libs/agentc_core/agentc_core/catalog/descriptor.py @@ -12,6 +12,7 @@ from ..tool.descriptor.models import SemanticSearchToolDescriptor from ..tool.descriptor.models import SQLPPQueryToolDescriptor from ..version import VersionDescriptor +from agentc_core.learned.model import EmbeddingModel class CatalogKind(enum.StrEnum): @@ -47,10 +48,8 @@ class CatalogDescriptor(pydantic.BaseModel): kind: CatalogKind = pydantic.Field(description="The type of items within the catalog.") - embedding_model: str = pydantic.Field( - description="The sentence-transformers embedding model used to generate the vector representations " - "of each catalog entry.", - examples=["sentence-transformers/all-MiniLM-L12-v2"], + embedding_model: EmbeddingModel = pydantic.Field( + description="Embedding model used for all descriptions in the catalog.", ) version: VersionDescriptor = pydantic.Field( diff --git a/libs/agentc_core/agentc_core/learned/embedding.py b/libs/agentc_core/agentc_core/learned/embedding.py index 896f4a72..d4d586e0 100644 --- a/libs/agentc_core/agentc_core/learned/embedding.py +++ b/libs/agentc_core/agentc_core/learned/embedding.py @@ -1,6 +1,7 @@ import couchbase.cluster import couchbase.exceptions import logging +import os import pathlib import pydantic import typing @@ -12,6 +13,7 @@ from agentc_core.defaults import DEFAULT_MODEL_CACHE_FOLDER from agentc_core.defaults import DEFAULT_PROMPT_CATALOG_NAME from agentc_core.defaults import DEFAULT_TOOL_CATALOG_NAME +from agentc_core.learned.model import EmbeddingModel as PydanticEmbeddingModel logger = logging.getLogger(__name__) @@ -21,6 +23,7 @@ class EmbeddingModel(pydantic.BaseModel): # Embedding models are defined in three distinct ways: explicitly (by name)... embedding_model_name: typing.Optional[str] = DEFAULT_EMBEDDING_MODEL + embedding_model_url: typing.Optional[str] = None # ...or implicitly (by path)... catalog_path: typing.Optional[pathlib.Path] = None @@ -29,8 +32,8 @@ class EmbeddingModel(pydantic.BaseModel): cb_bucket: typing.Optional[str] = None cb_cluster: typing.Optional[couchbase.cluster.Cluster] = None - # The actual embedding model object (we won't type this to avoid the sentence transformers import). - _embedding_model: None + # The actual embedding model object (we won't type this to avoid the potential sentence transformers import). + _embedding_model: None = None @pydantic.model_validator(mode="after") def validate_bucket_cluster(self) -> "EmbeddingModel": @@ -46,34 +49,32 @@ def validate_embedding_model(self) -> "EmbeddingModel": if self.embedding_model_name is None and self.catalog_path is None and self.cb_cluster is None: raise ValueError("embedding_model_name, catalog_path, or cb_cluster must be specified.") - from_catalog_embedding_model_name = None + from_catalog_embedding_model = None if self.catalog_path is not None: - collected_embedding_model_names = set() + collected_embedding_models = set() # Grab our local tool embedding model... local_tool_catalog_path = self.catalog_path / DEFAULT_TOOL_CATALOG_NAME if local_tool_catalog_path.exists(): with local_tool_catalog_path.open("r") as fp: local_tool_catalog = CatalogDescriptor.model_validate_json(fp.read()) - collected_embedding_model_names.add(local_tool_catalog.embedding_model) + collected_embedding_models.add(local_tool_catalog.embedding_model) # ...and now our local prompt embedding model. local_prompt_catalog_path = self.catalog_path / DEFAULT_PROMPT_CATALOG_NAME if local_prompt_catalog_path.exists(): with local_prompt_catalog_path.open("r") as fp: local_prompt_catalog = CatalogDescriptor.model_validate_json(fp.read()) - collected_embedding_model_names.add(local_prompt_catalog.embedding_model) + collected_embedding_models.add(local_prompt_catalog.embedding_model) - if len(collected_embedding_model_names) > 1: - raise ValueError( - f"Multiple embedding models found in local catalogs: " f"{collected_embedding_model_names}" - ) - elif len(collected_embedding_model_names) == 1: - from_catalog_embedding_model_name = collected_embedding_model_names.pop() - logger.debug("Found embedding model %s in local catalogs.", from_catalog_embedding_model_name) + if len(collected_embedding_models) > 1: + raise ValueError(f"Multiple embedding models found in local catalogs: " f"{collected_embedding_models}") + elif len(collected_embedding_models) == 1: + from_catalog_embedding_model = collected_embedding_models.pop() + logger.debug("Found embedding model %s in local catalogs.", from_catalog_embedding_model) if self.cb_cluster is not None: - collected_embedding_model_names = set() + collected_embedding_models = set() # TODO (GLENN): There is probably a cleaner way to do this (but this is Pythonic, so...). union_subqueries = [] @@ -83,7 +84,8 @@ def validate_embedding_model(self) -> "EmbeddingModel": f"`{self.cb_bucket}`.`{DEFAULT_CATALOG_SCOPE}`.`{kind}{DEFAULT_META_COLLECTION_NAME}`" ) self.cb_cluster.query(f""" - FROM {qualified_collection_name} AS mc + FROM + {qualified_collection_name} AS mc SELECT * LIMIT 1 """).execute() @@ -115,37 +117,32 @@ def validate_embedding_model(self) -> "EmbeddingModel": if metadata_query is not None: for row in metadata_query: - collected_embedding_model_names.add(row) + collected_embedding_models.add(PydanticEmbeddingModel(**row)) - if len(collected_embedding_model_names) > 1: + if len(collected_embedding_models) > 1: raise ValueError( - f"Multiple embedding models found in remote catalogs: " f"{collected_embedding_model_names}" + f"Multiple embedding models found in remote catalogs: " f"{collected_embedding_models}" ) - elif len(collected_embedding_model_names) == 1: - remote_embedding_model_name = collected_embedding_model_names.pop() - logger.debug("Found embedding model %s in remote catalogs.", remote_embedding_model_name) - if ( - from_catalog_embedding_model_name is not None - and from_catalog_embedding_model_name != remote_embedding_model_name - ): + elif len(collected_embedding_models) == 1: + remote_embedding_model = collected_embedding_models.pop() + logger.debug("Found embedding model %s in remote catalogs.", remote_embedding_model) + if from_catalog_embedding_model is not None and from_catalog_embedding_model != remote_embedding_model: raise ValueError( - f"Local embedding model {from_catalog_embedding_model_name} does not match " - f"remote embedding model {remote_embedding_model_name}!" + f"Local embedding model {from_catalog_embedding_model} does not match " + f"remote embedding model {remote_embedding_model}!" ) - elif from_catalog_embedding_model_name is None: - from_catalog_embedding_model_name = remote_embedding_model_name + elif from_catalog_embedding_model is None: + from_catalog_embedding_model = remote_embedding_model if self.embedding_model_name is None: - self.embedding_model_name = from_catalog_embedding_model_name - elif ( - from_catalog_embedding_model_name is not None - and self.embedding_model_name != from_catalog_embedding_model_name - ): + self.embedding_model_name = from_catalog_embedding_model.name + self.embedding_model_url = from_catalog_embedding_model.base_url + elif from_catalog_embedding_model is not None and self.embedding_model_name != from_catalog_embedding_model: raise ValueError( - f"Local embedding model {from_catalog_embedding_model_name} does not match " + f"Local embedding model {from_catalog_embedding_model.name} does not match " f"specified embedding model {self.embedding_model_name}!" ) - elif self.embedding_model_name is None and from_catalog_embedding_model_name is None: + elif self.embedding_model_name is None and from_catalog_embedding_model is None: raise ValueError("No embedding model found (run 'agentc index' to download one).") # Note: we won't validate the embedding model name because sentence_transformers takes a while to import. @@ -157,15 +154,40 @@ def name(self) -> str: return self.embedding_model_name def encode(self, text: str) -> list[float]: + # Lazily-load the embedding model. if self._embedding_model is None: - import sentence_transformers + if self.embedding_model_name.startswith("https://") or self.embedding_model_name.startswith("http://"): + import openai - self._embedding_model = sentence_transformers.SentenceTransformer( - self.embedding_model_name, - tokenizer_kwargs={"clean_up_tokenization_spaces": True}, - cache_folder=DEFAULT_MODEL_CACHE_FOLDER, - local_files_only=False, - ) + open_ai_client = openai.OpenAI( + base_url=self.embedding_model_url, api_key=os.getenv("AGENT_CATALOG_EMBEDDING_MODEL_AUTH") + ) + + def _encode(_text: str) -> list[float]: + return ( + open_ai_client.embeddings.create( + model=self.embedding_model_name, input=text, encoding_format="float" + ) + .data[0] + .embedding + ) + + self._embedding_model = _encode + + else: + import sentence_transformers + + sentence_transformers_model = sentence_transformers.SentenceTransformer( + self.embedding_model_name, + tokenizer_kwargs={"clean_up_tokenization_spaces": True}, + cache_folder=DEFAULT_MODEL_CACHE_FOLDER, + local_files_only=False, + ) + + def _encode(_text: str) -> list[float]: + return sentence_transformers_model.encode(_text, convert_to_tensor=False).tolist() + + self._embedding_model = _encode - # Normalize embeddings to unit length (only dot-product is computed with Couchbase, so...). - return self._embedding_model.encode(text, normalize_embeddings=True).tolist() + # Invoke our model. + return self._embedding_model(text) diff --git a/libs/agentc_core/agentc_core/learned/model.py b/libs/agentc_core/agentc_core/learned/model.py new file mode 100644 index 00000000..a84f9eee --- /dev/null +++ b/libs/agentc_core/agentc_core/learned/model.py @@ -0,0 +1,17 @@ +import pydantic +import typing + + +class EmbeddingModel(pydantic.BaseModel): + kind: typing.Literal["sentence-transformers", "openai"] = pydantic.Field( + description="The type of embedding model being used." + ) + name: str = pydantic.Field( + description="The name of the embedding model being used.", + examples=["all-MiniLM-L12-v2", "https://12fs345d.apps.cloud.couchbase.com"], + ) + base_url: typing.Optional[str] = pydantic.Field( + description="The base URL of the embedding model." + "This field must be specified is using a non-SentenceTransformers-based model.", + examples=["https://12fs345d.apps.cloud.couchbase.com"], + ) diff --git a/libs/agentc_core/agentc_core/tool/descriptor/models.py b/libs/agentc_core/agentc_core/tool/descriptor/models.py index 3201b2cf..ce1c6eca 100644 --- a/libs/agentc_core/agentc_core/tool/descriptor/models.py +++ b/libs/agentc_core/agentc_core/tool/descriptor/models.py @@ -22,6 +22,7 @@ from ..decorator import get_name from ..decorator import is_tool from .secrets import CouchbaseSecrets +from agentc_core.tool.descriptor.secrets import EmbeddingModelSecrets logger = logging.getLogger(__name__) @@ -139,19 +140,27 @@ def __iter__(self) -> typing.Iterable["SQLPPQueryToolDescriptor"]: class SemanticSearchToolDescriptor(RecordDescriptor): class VectorSearchMetadata(pydantic.BaseModel): - # TODO (GLENN): Copy all vector-search-specific validations here. + class EmbeddingModel(pydantic.BaseModel): + name: str + base_url: typing.Optional[str] + + @property + @pydantic.computed_field + def kind(self) -> typing.Literal["sentence-transformers", "openai"]: + return "sentence-transformers" if self.base_url is None else "openai" + bucket: str scope: str collection: str index: str vector_field: str text_field: str - embedding_model: str + embedding_model: EmbeddingModel num_candidates: int = 3 input: str vector_search: VectorSearchMetadata - secrets: list[CouchbaseSecrets] = pydantic.Field(min_length=1, max_length=1) + secrets: list[CouchbaseSecrets | EmbeddingModelSecrets] = pydantic.Field(min_length=1, max_length=2) record_kind: typing.Literal[RecordKind.SemanticSearch] class Factory(_BaseFactory): @@ -163,11 +172,18 @@ class Metadata(pydantic.BaseModel, JSONSchemaValidatingMixin): name: str description: str input: str - secrets: list[CouchbaseSecrets] = pydantic.Field(min_length=1, max_length=1) + secrets: list[CouchbaseSecrets | EmbeddingModelSecrets] = pydantic.Field(min_length=1, max_length=2) annotations: typing.Optional[dict[str, str] | None] = None vector_search: "SemanticSearchToolDescriptor.VectorSearchMetadata" num_candidates: typing.Optional[pydantic.PositiveInt] = 3 + @pydantic.field_validator("secrets") + @classmethod + def couchbase_secrets_must_exist(cls, v: list[CouchbaseSecrets | EmbeddingModelSecrets]): + if not any(isinstance(s, CouchbaseSecrets) for s in v): + raise ValueError("Secrets list 'secrets' must contain the 'couchbase' field.") + return v + @pydantic.field_validator("input") @classmethod def value_should_be_valid_json_schema(cls, v: str): diff --git a/libs/agentc_core/agentc_core/tool/descriptor/secrets.py b/libs/agentc_core/agentc_core/tool/descriptor/secrets.py index 5c0a697a..78dd9054 100644 --- a/libs/agentc_core/agentc_core/tool/descriptor/secrets.py +++ b/libs/agentc_core/agentc_core/tool/descriptor/secrets.py @@ -1,7 +1,7 @@ import pydantic +import typing -# TODO (GLENN): Add more types of secrets below. class CouchbaseSecrets(pydantic.BaseModel): class Couchbase(pydantic.BaseModel): conn_string: str @@ -9,3 +9,11 @@ class Couchbase(pydantic.BaseModel): password: str couchbase: Couchbase + + +class EmbeddingModelSecrets(pydantic.BaseModel): + class OpenAI(pydantic.BaseModel): + username: typing.Optional[str] + password: typing.Optional[str] + + openai: OpenAI diff --git a/libs/agentc_core/agentc_core/tool/generate/templates/semantic_q.jinja b/libs/agentc_core/agentc_core/tool/generate/templates/semantic_q.jinja index edad90be..ba4856c5 100644 --- a/libs/agentc_core/agentc_core/tool/generate/templates/semantic_q.jinja +++ b/libs/agentc_core/agentc_core/tool/generate/templates/semantic_q.jinja @@ -50,8 +50,8 @@ def {{ tool.name }}(question: {{ input.type_name }}) -> list[str]: else: raise ValueError("Bad input given to tool!") + if import sentence_transformers - logger.debug("{{ tool.name }} is generating an embedding for: " + formatted_question + ".") embedding_model = sentence_transformers.SentenceTransformer( '{{ vector_search.embedding_model }}', diff --git a/libs/agentc_core/agentc_core/tool/templates/semantic_search.jinja b/libs/agentc_core/agentc_core/tool/templates/semantic_search.jinja index d9a28e5a..17ac2c02 100644 --- a/libs/agentc_core/agentc_core/tool/templates/semantic_search.jinja +++ b/libs/agentc_core/agentc_core/tool/templates/semantic_search.jinja @@ -29,6 +29,12 @@ secrets: username: CB_USERNAME password: CB_PASSWORD + # For cloud-hosted embedding models, a Bearer token is required. + # If your embedding model is hosted on Capella, provide the username and password below. + - openai: + username: CB_USERNAME + password: CB_PASSWORD + # Couchbase semantic search tools always involve a vector search. vector_search: @@ -53,7 +59,9 @@ vector_search: # The embedding model used to generate the vector_field. # This embedding model field value is directly passed to sentence transformers. # In the future, we will add support for other types of embedding models. - embedding_model: {{ embedding_model }} + embedding_model: + name: {{ embedding_model_name }} + {% if embedding_model_url is not none %}base_url: {{ embedding_model_url }}{% endif %} # The number of candidates (i.e., the K value) to request for when performing a vector top-k search. # This field is optional, and defaults to k=3 if not specified. From 13ea906241ae23bf493f0ae4c323bd9b2d4352e0 Mon Sep 17 00:00:00 2001 From: ThejasNU Date: Thu, 6 Feb 2025 19:02:27 +0530 Subject: [PATCH 02/10] fix test --- libs/agentc_core/agentc_core/tool/descriptor/models.py | 2 +- .../tests/tool/resources/semantic_search/positive_1.yaml | 3 ++- .../tests/tool/resources/semantic_search/positive_2.yaml | 3 ++- .../tests/tool/resources/semantic_search/positive_3.yaml | 3 ++- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/libs/agentc_core/agentc_core/tool/descriptor/models.py b/libs/agentc_core/agentc_core/tool/descriptor/models.py index ce1c6eca..72600764 100644 --- a/libs/agentc_core/agentc_core/tool/descriptor/models.py +++ b/libs/agentc_core/agentc_core/tool/descriptor/models.py @@ -142,7 +142,7 @@ class SemanticSearchToolDescriptor(RecordDescriptor): class VectorSearchMetadata(pydantic.BaseModel): class EmbeddingModel(pydantic.BaseModel): name: str - base_url: typing.Optional[str] + base_url: typing.Optional[str] = None @property @pydantic.computed_field diff --git a/libs/agentc_core/tests/tool/resources/semantic_search/positive_1.yaml b/libs/agentc_core/tests/tool/resources/semantic_search/positive_1.yaml index dc18c186..a20b0813 100644 --- a/libs/agentc_core/tests/tool/resources/semantic_search/positive_1.yaml +++ b/libs/agentc_core/tests/tool/resources/semantic_search/positive_1.yaml @@ -23,7 +23,8 @@ vector_search: index: articles-index vector_field: vec text_field: text - embedding_model: sentence-transformers/all-MiniLM-L12-v2 + embedding_model: + name: sentence-transformers/all-MiniLM-L12-v2 secrets: - couchbase: diff --git a/libs/agentc_core/tests/tool/resources/semantic_search/positive_2.yaml b/libs/agentc_core/tests/tool/resources/semantic_search/positive_2.yaml index b26b2ad4..9ae9f734 100644 --- a/libs/agentc_core/tests/tool/resources/semantic_search/positive_2.yaml +++ b/libs/agentc_core/tests/tool/resources/semantic_search/positive_2.yaml @@ -23,7 +23,8 @@ vector_search: index: articles-index vector_field: vec text_field: text - embedding_model: sentence-transformers/all-MiniLM-L12-v2 + embedding_model: + name: sentence-transformers/all-MiniLM-L12-v2 annotations: gdpr_compliant: "true" diff --git a/libs/agentc_core/tests/tool/resources/semantic_search/positive_3.yaml b/libs/agentc_core/tests/tool/resources/semantic_search/positive_3.yaml index 1ff9aa27..8cd3baeb 100644 --- a/libs/agentc_core/tests/tool/resources/semantic_search/positive_3.yaml +++ b/libs/agentc_core/tests/tool/resources/semantic_search/positive_3.yaml @@ -23,7 +23,8 @@ vector_search: index: articles-index vector_field: vec text_field: text - embedding_model: sentence-transformers/all-MiniLM-L12-v2 + embedding_model: + name: sentence-transformers/all-MiniLM-L12-v2 num_candidates: 10 secrets: From ad1e7917851317acbab860611cf50afa2b8ddd10 Mon Sep 17 00:00:00 2001 From: ThejasNU Date: Thu, 6 Feb 2025 22:19:51 +0530 Subject: [PATCH 03/10] fix cli tests --- libs/agentc_core/agentc_core/catalog/descriptor.py | 2 +- libs/agentc_core/agentc_core/catalog/index.py | 10 +++++++++- libs/agentc_core/agentc_core/learned/embedding.py | 13 +++++++++---- libs/agentc_core/agentc_core/learned/model.py | 4 ++++ .../tool/generate/templates/semantic_q.jinja | 2 +- .../tool/resources/semantic_search/negative_1.yaml | 3 ++- .../tool/resources/semantic_search/negative_2.yaml | 3 ++- .../tool/resources/semantic_search/negative_3.yaml | 3 ++- .../tool/resources/semantic_search/negative_4.yaml | 3 ++- .../travel_agent/tools/blogs_from_interests.yaml | 3 ++- templates/tools/semantic_search.yaml | 3 ++- 11 files changed, 36 insertions(+), 13 deletions(-) diff --git a/libs/agentc_core/agentc_core/catalog/descriptor.py b/libs/agentc_core/agentc_core/catalog/descriptor.py index 7cdb3daf..be1a0211 100644 --- a/libs/agentc_core/agentc_core/catalog/descriptor.py +++ b/libs/agentc_core/agentc_core/catalog/descriptor.py @@ -49,7 +49,7 @@ class CatalogDescriptor(pydantic.BaseModel): kind: CatalogKind = pydantic.Field(description="The type of items within the catalog.") embedding_model: EmbeddingModel = pydantic.Field( - description="Embedding model used for all descriptions in the catalog.", + description="Embedding model used to generate embedding for tool/prompt descriptions to store in the catalogs.", ) version: VersionDescriptor = pydantic.Field( diff --git a/libs/agentc_core/agentc_core/catalog/index.py b/libs/agentc_core/agentc_core/catalog/index.py index a1d0e293..3424c47d 100644 --- a/libs/agentc_core/agentc_core/catalog/index.py +++ b/libs/agentc_core/agentc_core/catalog/index.py @@ -9,6 +9,7 @@ from ..indexer import AllIndexers from ..indexer import vectorize_descriptor from ..learned.embedding import EmbeddingModel +from ..learned.model import EmbeddingModel as CatalogDescriptorEmbeddingModel from ..record.descriptor import RecordDescriptor from .catalog.mem import CatalogMem from .descriptor import CatalogDescriptor @@ -167,13 +168,20 @@ def index_catalog_start( logger.error("Encountered error(s) while crawling source directories: " + "\n".join([str(e) for e in all_errs])) raise all_errs[0] + catalog_descriptor_embedding_model = ( + CatalogDescriptorEmbeddingModel(name=embedding_model.name, base_url=None, kind="sentence-transformers") + if embedding_model.embedding_model_url is None + else CatalogDescriptorEmbeddingModel( + name=embedding_model.name, base_url=embedding_model.embedding_model_url, kind="openai" + ) + ) next_catalog = CatalogMem( embedding_model=embedding_model, catalog_descriptor=CatalogDescriptor( schema_version=meta_version.schema_version, library_version=meta_version.library_version, version=catalog_version, - embedding_model=embedding_model.name, + embedding_model=catalog_descriptor_embedding_model, kind=kind, source_dirs=source_dirs, items=all_descriptors, diff --git a/libs/agentc_core/agentc_core/learned/embedding.py b/libs/agentc_core/agentc_core/learned/embedding.py index d4d586e0..a4ff86f4 100644 --- a/libs/agentc_core/agentc_core/learned/embedding.py +++ b/libs/agentc_core/agentc_core/learned/embedding.py @@ -126,10 +126,13 @@ def validate_embedding_model(self) -> "EmbeddingModel": elif len(collected_embedding_models) == 1: remote_embedding_model = collected_embedding_models.pop() logger.debug("Found embedding model %s in remote catalogs.", remote_embedding_model) - if from_catalog_embedding_model is not None and from_catalog_embedding_model != remote_embedding_model: + if ( + from_catalog_embedding_model is not None + and from_catalog_embedding_model.name != remote_embedding_model.name + ): raise ValueError( - f"Local embedding model {from_catalog_embedding_model} does not match " - f"remote embedding model {remote_embedding_model}!" + f"Local embedding model {from_catalog_embedding_model.name} does not match " + f"remote embedding model {remote_embedding_model.name}!" ) elif from_catalog_embedding_model is None: from_catalog_embedding_model = remote_embedding_model @@ -137,7 +140,9 @@ def validate_embedding_model(self) -> "EmbeddingModel": if self.embedding_model_name is None: self.embedding_model_name = from_catalog_embedding_model.name self.embedding_model_url = from_catalog_embedding_model.base_url - elif from_catalog_embedding_model is not None and self.embedding_model_name != from_catalog_embedding_model: + elif ( + from_catalog_embedding_model is not None and self.embedding_model_name != from_catalog_embedding_model.name + ): raise ValueError( f"Local embedding model {from_catalog_embedding_model.name} does not match " f"specified embedding model {self.embedding_model_name}!" diff --git a/libs/agentc_core/agentc_core/learned/model.py b/libs/agentc_core/agentc_core/learned/model.py index a84f9eee..d939c9e1 100644 --- a/libs/agentc_core/agentc_core/learned/model.py +++ b/libs/agentc_core/agentc_core/learned/model.py @@ -14,4 +14,8 @@ class EmbeddingModel(pydantic.BaseModel): description="The base URL of the embedding model." "This field must be specified is using a non-SentenceTransformers-based model.", examples=["https://12fs345d.apps.cloud.couchbase.com"], + default=None, ) + + def __hash__(self): + return self.name.__hash__() diff --git a/libs/agentc_core/agentc_core/tool/generate/templates/semantic_q.jinja b/libs/agentc_core/agentc_core/tool/generate/templates/semantic_q.jinja index 7d4d5827..34029766 100644 --- a/libs/agentc_core/agentc_core/tool/generate/templates/semantic_q.jinja +++ b/libs/agentc_core/agentc_core/tool/generate/templates/semantic_q.jinja @@ -55,7 +55,7 @@ def {{ tool.name }}(question: {{ input.type_name }}) -> list[str]: import sentence_transformers logger.debug("{{ tool.name }} is generating an embedding for: " + formatted_question + ".") embedding_model = sentence_transformers.SentenceTransformer( - '{{ vector_search.embedding_model }}', + '{{ vector_search.embedding_model.name }}', tokenizer_kwargs={'clean_up_tokenization_spaces': True}, cache_folder=DEFAULT_MODEL_CACHE_FOLDER, local_files_only=True diff --git a/libs/agentc_core/tests/tool/resources/semantic_search/negative_1.yaml b/libs/agentc_core/tests/tool/resources/semantic_search/negative_1.yaml index 3a96c617..e59dd859 100644 --- a/libs/agentc_core/tests/tool/resources/semantic_search/negative_1.yaml +++ b/libs/agentc_core/tests/tool/resources/semantic_search/negative_1.yaml @@ -23,7 +23,8 @@ vector_search: index: articles-index vector_field: vec text_field: text - embedding_model: sentence-transformers/all-MiniLM-L12-v2 + embedding_model: + name: sentence-transformers/all-MiniLM-L12-v2 secrets: - couchbase: diff --git a/libs/agentc_core/tests/tool/resources/semantic_search/negative_2.yaml b/libs/agentc_core/tests/tool/resources/semantic_search/negative_2.yaml index b2f7acc0..fe944181 100644 --- a/libs/agentc_core/tests/tool/resources/semantic_search/negative_2.yaml +++ b/libs/agentc_core/tests/tool/resources/semantic_search/negative_2.yaml @@ -23,7 +23,8 @@ vector_search: index: articles-index vector_field: vec text_field: text - embedding_model: sentence-transformers/all-MiniLM-L12-v2 + embedding_model: + name: sentence-transformers/all-MiniLM-L12-v2 secrets: - couchbase: diff --git a/libs/agentc_core/tests/tool/resources/semantic_search/negative_3.yaml b/libs/agentc_core/tests/tool/resources/semantic_search/negative_3.yaml index 5d405718..65d36c2a 100644 --- a/libs/agentc_core/tests/tool/resources/semantic_search/negative_3.yaml +++ b/libs/agentc_core/tests/tool/resources/semantic_search/negative_3.yaml @@ -16,7 +16,8 @@ vector_search: index: articles-index vector_field: vec text_field: text - embedding_model: sentence-transformers/all-MiniLM-L12-v2 + embedding_model: + name: sentence-transformers/all-MiniLM-L12-v2 secrets: - couchbase: diff --git a/libs/agentc_core/tests/tool/resources/semantic_search/negative_4.yaml b/libs/agentc_core/tests/tool/resources/semantic_search/negative_4.yaml index 93dc4fe0..8c8b3635 100644 --- a/libs/agentc_core/tests/tool/resources/semantic_search/negative_4.yaml +++ b/libs/agentc_core/tests/tool/resources/semantic_search/negative_4.yaml @@ -15,7 +15,8 @@ vector_search: index: articles-index vector_field: vec text_field: text - embedding_model: sentence-transformers/all-MiniLM-L12-v2 + embedding_model: + name: sentence-transformers/all-MiniLM-L12-v2 secrets: - couchbase: diff --git a/libs/agentc_testing/agentc_testing/resources/travel_agent/tools/blogs_from_interests.yaml b/libs/agentc_testing/agentc_testing/resources/travel_agent/tools/blogs_from_interests.yaml index a82c6906..4c215912 100644 --- a/libs/agentc_testing/agentc_testing/resources/travel_agent/tools/blogs_from_interests.yaml +++ b/libs/agentc_testing/agentc_testing/resources/travel_agent/tools/blogs_from_interests.yaml @@ -33,4 +33,5 @@ vector_search: index: articles-index vector_field: vec text_field: text - embedding_model: sentence-transformers/all-MiniLM-L12-v2 + embedding_model: + name: sentence-transformers/all-MiniLM-L12-v2 diff --git a/templates/tools/semantic_search.yaml b/templates/tools/semantic_search.yaml index 1ee87f5f..8252c410 100644 --- a/templates/tools/semantic_search.yaml +++ b/templates/tools/semantic_search.yaml @@ -68,7 +68,8 @@ vector_search: # The embedding model used to generate the vector_field. # This embedding model field value is directly passed to sentence transformers. # In the future, we will add support for other types of embedding models. - embedding_model: sentence-transformers/all-MiniLM-L12-v2 + embedding_model: + name: sentence-transformers/all-MiniLM-L12-v2 # The number of candidates (i.e., the K value) to request for when performing a vector top-k search. # This field is optional, and defaults to k=3 if not specified. From 3f18f5f97e29bcf4db2eaa0860a85ea52536e45e Mon Sep 17 00:00:00 2001 From: ThejasNU Date: Thu, 6 Feb 2025 22:33:52 +0530 Subject: [PATCH 04/10] fix sdk tests --- .../agentc_core/tool/generate/templates/semantic_q.jinja | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/agentc_core/agentc_core/tool/generate/templates/semantic_q.jinja b/libs/agentc_core/agentc_core/tool/generate/templates/semantic_q.jinja index 34029766..7740c4d0 100644 --- a/libs/agentc_core/agentc_core/tool/generate/templates/semantic_q.jinja +++ b/libs/agentc_core/agentc_core/tool/generate/templates/semantic_q.jinja @@ -51,8 +51,8 @@ def {{ tool.name }}(question: {{ input.type_name }}) -> list[str]: else: raise ValueError("Bad input given to tool!") - if import sentence_transformers + logger.debug("{{ tool.name }} is generating an embedding for: " + formatted_question + ".") embedding_model = sentence_transformers.SentenceTransformer( '{{ vector_search.embedding_model.name }}', From f11c9d2ff46b2dc825ec229c1805444bc0b93150 Mon Sep 17 00:00:00 2001 From: ThejasNU Date: Fri, 7 Feb 2025 10:48:33 +0530 Subject: [PATCH 05/10] update comments --- libs/agentc_core/agentc_core/learned/model.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/agentc_core/agentc_core/learned/model.py b/libs/agentc_core/agentc_core/learned/model.py index d939c9e1..95dcbc60 100644 --- a/libs/agentc_core/agentc_core/learned/model.py +++ b/libs/agentc_core/agentc_core/learned/model.py @@ -8,11 +8,11 @@ class EmbeddingModel(pydantic.BaseModel): ) name: str = pydantic.Field( description="The name of the embedding model being used.", - examples=["all-MiniLM-L12-v2", "https://12fs345d.apps.cloud.couchbase.com"], + examples=["all-MiniLM-L12-v2", "intfloat/e5-mistral-7b-instruct"], ) base_url: typing.Optional[str] = pydantic.Field( description="The base URL of the embedding model." - "This field must be specified is using a non-SentenceTransformers-based model.", + "This field must be specified if using a non-SentenceTransformers-based model.", examples=["https://12fs345d.apps.cloud.couchbase.com"], default=None, ) From 8b9347d9568dd5c4589376c7efd4e49392b90a19 Mon Sep 17 00:00:00 2001 From: ThejasNU Date: Mon, 10 Feb 2025 15:07:56 +0530 Subject: [PATCH 06/10] update sematic tool template --- .../agentc_core/tool/descriptor/secrets.py | 7 +++---- .../agentc_core/tool/generate/generator.py | 17 ++++++++++++++- .../tool/generate/templates/semantic_q.jinja | 21 ++++++++++++------- 3 files changed, 33 insertions(+), 12 deletions(-) diff --git a/libs/agentc_core/agentc_core/tool/descriptor/secrets.py b/libs/agentc_core/agentc_core/tool/descriptor/secrets.py index 33a40f84..4fe9e888 100644 --- a/libs/agentc_core/agentc_core/tool/descriptor/secrets.py +++ b/libs/agentc_core/agentc_core/tool/descriptor/secrets.py @@ -13,8 +13,7 @@ class Couchbase(pydantic.BaseModel): class EmbeddingModelSecrets(pydantic.BaseModel): - class OpenAI(pydantic.BaseModel): - username: typing.Optional[str] - password: typing.Optional[str] + class EmbeddingModel(pydantic.BaseModel): + auth: str - openai: OpenAI + embedding: EmbeddingModel diff --git a/libs/agentc_core/agentc_core/tool/generate/generator.py b/libs/agentc_core/agentc_core/tool/generate/generator.py index b71d27f2..baeddd85 100644 --- a/libs/agentc_core/agentc_core/tool/generate/generator.py +++ b/libs/agentc_core/agentc_core/tool/generate/generator.py @@ -15,6 +15,8 @@ from ..descriptor import HTTPRequestToolDescriptor from ..descriptor import SemanticSearchToolDescriptor from ..descriptor import SQLPPQueryToolDescriptor +from ..descriptor.secrets import CouchbaseSecrets +from ..descriptor.secrets import EmbeddingModelSecrets from .common import INPUT_MODEL_CLASS_NAME_IN_TEMPLATES from .common import OUTPUT_MODEL_CLASS_NAME_IN_TEMPLATES from .common import GeneratedCode @@ -114,13 +116,26 @@ def generate(self) -> typing.Iterable[str]: with (self.template_directory / "semantic_q.jinja").open("r") as tmpl_fp: template = jinja2.Template(source=tmpl_fp.read(), autoescape=True) generation_time = datetime.datetime.now().strftime("%I:%M%p on %B %d, %Y") + + cluster_secrets = None + for secret in self.record_descriptor.secrets: + if isinstance(secret, CouchbaseSecrets): + cluster_secrets = secret + break + embedding_secrets = None + for secret in self.record_descriptor.secrets: + if isinstance(secret, EmbeddingModelSecrets): + embedding_secrets = secret + break + rendered_code = template.render( { "time": generation_time, "tool": self.record_descriptor, "input": input_model, "vector_search": self.record_descriptor.vector_search, - "secrets": self.record_descriptor.secrets[0].couchbase, + "cluster_secrets": cluster_secrets.couchbase if cluster_secrets is not None else None, + "embedding_secrets": embedding_secrets.embedding if embedding_secrets is not None else None, } ) logger.debug("The following code has been generated:\n" + rendered_code) diff --git a/libs/agentc_core/agentc_core/tool/generate/templates/semantic_q.jinja b/libs/agentc_core/agentc_core/tool/generate/templates/semantic_q.jinja index 7740c4d0..33a1be54 100644 --- a/libs/agentc_core/agentc_core/tool/generate/templates/semantic_q.jinja +++ b/libs/agentc_core/agentc_core/tool/generate/templates/semantic_q.jinja @@ -21,11 +21,11 @@ logger = logging.getLogger(__name__) def _get_couchbase_cluster() -> couchbase.cluster.Cluster: authenticator = couchbase.auth.PasswordAuthenticator( - username=get_secret("{{ secrets.username }}").get_secret_value(), - password=get_secret("{{ secrets.password }}").get_secret_value(), - {% if secrets.certificate is none %}cert_path=get_secret("{{ secrets.certificate | safe }}").get_secret_value(){% endif %} + username=get_secret("{{ cluster_secrets.username }}").get_secret_value(), + password=get_secret("{{ cluster_secrets.password }}").get_secret_value(), + {% if cluster_secrets.certificate is none %}cert_path=get_secret("{{ cluster_secrets.certificate | safe }}").get_secret_value(){% endif %} ) - conn_string = get_secret("{{ secrets.conn_string }}").get_secret_value() + conn_string = get_secret("{{ cluster_secrets.conn_string }}").get_secret_value() return couchbase.cluster.Cluster(conn_string, couchbase.options.ClusterOptions(authenticator)) @@ -51,17 +51,24 @@ def {{ tool.name }}(question: {{ input.type_name }}) -> list[str]: else: raise ValueError("Bad input given to tool!") - import sentence_transformers - logger.debug("{{ tool.name }} is generating an embedding for: " + formatted_question + ".") + {% if vector_search.embedding_model.base_url is none %} + import sentence_transformers embedding_model = sentence_transformers.SentenceTransformer( - '{{ vector_search.embedding_model.name }}', + "{{ vector_search.embedding_model.name }}", tokenizer_kwargs={'clean_up_tokenization_spaces': True}, cache_folder=DEFAULT_MODEL_CACHE_FOLDER, local_files_only=True ) _embedding = embedding_model.encode(formatted_question) for_q = list(_embedding.astype('float64')) + {% else %} + import openai + open_ai_client = openai.OpenAI( + base_url="{{ vector_search.embedding_model.base_url }}", api_key=get_secret("{{ embedding_secrets.auth }}").get_secret_value() + ) + for_q=open_ai_client.embeddings.create(model="{{ vector_search.embedding_model.name }}", input=formatted_question, encoding_format="float").data[0].embedding + {% endif %} vector_req = couchbase.vector_search.VectorSearch.from_vector_query( couchbase.vector_search.VectorQuery('{{ vector_search.vector_field }}', for_q, num_candidates={{ vector_search.num_candidates }}) ) From 37c6102bef7d2c49784d7b257cff9a99a06362dd Mon Sep 17 00:00:00 2001 From: ThejasNU Date: Mon, 10 Feb 2025 15:51:28 +0530 Subject: [PATCH 07/10] add sentence transformers test --- .../tests/embedding/test_embedding_local.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 libs/agentc_core/tests/embedding/test_embedding_local.py diff --git a/libs/agentc_core/tests/embedding/test_embedding_local.py b/libs/agentc_core/tests/embedding/test_embedding_local.py new file mode 100644 index 00000000..c1848da4 --- /dev/null +++ b/libs/agentc_core/tests/embedding/test_embedding_local.py @@ -0,0 +1,24 @@ +import pytest + +from agentc_core.defaults import DEFAULT_EMBEDDING_MODEL +from agentc_core.learned.embedding import EmbeddingModel + + +@pytest.mark.smoke +def test_embedding_local_default(): + embedding_model = EmbeddingModel( + embedding_model_name=DEFAULT_EMBEDDING_MODEL, + ) + + embedding = embedding_model.encode("agentc") + assert len(embedding) == 384 + + +@pytest.mark.smoke +def test_embedding_local_pretrained(): + embedding_model = EmbeddingModel( + embedding_model_name="paraphrase-albert-small-v2", + ) + + embedding = embedding_model.encode("agentc") + assert len(embedding) == 768 From f8c535cf705c7d4fe3d4eb4f239e7eed57a8fcc8 Mon Sep 17 00:00:00 2001 From: ThejasNU Date: Mon, 10 Feb 2025 16:10:32 +0530 Subject: [PATCH 08/10] add openai test --- .github/workflows/tests.yaml | 4 ++++ libs/agentc_core/agentc_core/learned/embedding.py | 2 +- .../tests/embedding/test_embedding_openai.py | 14 ++++++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 libs/agentc_core/tests/embedding/test_embedding_openai.py diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 170d872e..687852b6 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -33,6 +33,10 @@ jobs: run: | poetry install --with dev + - name: Export OPENAI API KEY for tests + run: | + export AGENT_CATALOG_EMBEDDING_MODEL_AUTH=${{ secrets.AGENT_CATALOG_EMBEDDING_MODEL_AUTH }} + - run: echo "🔥 Starting the core tests (🤞 please don't break!)." - name: Verify Pytest installation run: poetry show pytest diff --git a/libs/agentc_core/agentc_core/learned/embedding.py b/libs/agentc_core/agentc_core/learned/embedding.py index a4ff86f4..247b555c 100644 --- a/libs/agentc_core/agentc_core/learned/embedding.py +++ b/libs/agentc_core/agentc_core/learned/embedding.py @@ -161,7 +161,7 @@ def name(self) -> str: def encode(self, text: str) -> list[float]: # Lazily-load the embedding model. if self._embedding_model is None: - if self.embedding_model_name.startswith("https://") or self.embedding_model_name.startswith("http://"): + if self.embedding_model_url is not None: import openai open_ai_client = openai.OpenAI( diff --git a/libs/agentc_core/tests/embedding/test_embedding_openai.py b/libs/agentc_core/tests/embedding/test_embedding_openai.py new file mode 100644 index 00000000..eea87e62 --- /dev/null +++ b/libs/agentc_core/tests/embedding/test_embedding_openai.py @@ -0,0 +1,14 @@ +import pytest + +from agentc_core.learned.embedding import EmbeddingModel + + +@pytest.mark.smoke +def test_embedding_openai(): + embedding_model = EmbeddingModel( + embedding_model_name="text-embedding-3-small", + embedding_model_url="https://api.openai.com/v1", + ) + + embedding = embedding_model.encode("agentc") + assert len(embedding) == 1536 From a858204e5e590c261719cc4a42bb5128c59d1dd1 Mon Sep 17 00:00:00 2001 From: ThejasNU Date: Mon, 10 Feb 2025 16:17:28 +0530 Subject: [PATCH 09/10] update workflow --- .github/workflows/tests.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 687852b6..2b5edbc8 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -34,8 +34,9 @@ jobs: poetry install --with dev - name: Export OPENAI API KEY for tests - run: | - export AGENT_CATALOG_EMBEDDING_MODEL_AUTH=${{ secrets.AGENT_CATALOG_EMBEDDING_MODEL_AUTH }} + env: + AGENT_CATALOG_EMBEDDING_MODEL_AUTH: ${{ secrets.AGENT_CATALOG_EMBEDDING_MODEL_AUTH }} + run: python -c 'import os;print(os.environ)' - run: echo "🔥 Starting the core tests (🤞 please don't break!)." - name: Verify Pytest installation From 49557ffdf582a98205a73cfa1a1af93122d4e82a Mon Sep 17 00:00:00 2001 From: ThejasNU Date: Mon, 10 Feb 2025 16:28:34 +0530 Subject: [PATCH 10/10] update workflow --- .github/workflows/tests.yaml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 2b5edbc8..fe43f7de 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -33,10 +33,8 @@ jobs: run: | poetry install --with dev - - name: Export OPENAI API KEY for tests - env: - AGENT_CATALOG_EMBEDDING_MODEL_AUTH: ${{ secrets.AGENT_CATALOG_EMBEDDING_MODEL_AUTH }} - run: python -c 'import os;print(os.environ)' + - name: Export OPENAI API KEY secret + run: echo "AGENT_CATALOG_EMBEDDING_MODEL_AUTH=${{ secrets.AGENT_CATALOG_EMBEDDING_MODEL_AUTH }}" >> $GITHUB_ENV - run: echo "🔥 Starting the core tests (🤞 please don't break!)." - name: Verify Pytest installation