[Design Proposal] Standardized interface for LLM provider usage
thanks for this thoughtful proposal, really appreciate the clarity. I think standardizing model/model_name is a quick easy win.

of the other parts, what do you think the highest value ones are? @hwchase17 I think model discovery (use-case 2) is the next greatest value-add, though it is also the most ambiguous and tricky to implement. Although the scope of this proposal is large, I think it's small enough to be doable in a single PR. Since each of these suggestions requires implementing this for every LLM provider anyways, splitting the implementation of this proposal across multiple PRs seems to involve more complexity than not.
@hwchase17 Thank you for leading work on this repo! It's very clear you've put a lot of love into this project. 🤗 ❤️

My coworker, @3coins, with whom I work daily, is a regular contributor here.

I wanted to offer you a design proposal that I think would be a great addition to LangChain. Here goes:

# Motivation

LLM providers lack a unified interface for common use-cases:

- **Specifying a model ID to a model provider**
  - Users lack a unified interface for specifying the model they wish to use when instantiating a model provider. `openai` and `openai-chat` both expect the model ID in `model_name`, but `ai21` and `anthropic` expect `model`.

- **Discovering supported models**
  - Users don't have any way of determining valid model IDs for a given provider. For example, a user trying to invoke ChatGPT via `OpenAI(model_name="chatgpt")` will be quite confused, and there's no clear way for a user to look up what the allowed values are.
  - The only solution for this now is to look up upstream API reference documentation on what models are allowed, which is very tedious.

- **Determining prerequisites**
  - Users don't have any way of determining what packages they need without looking at the source code or encountering a runtime `ImportError`, even if they have knowledge of what providers they wish to use. There's no attribute/property on any of the model providers that lists what packages must be installed before using them.

- **Determining necessary authentication**
  - Similar to the use-case above, users don't have any way of knowing in advance what authentication they should supply and how to supply them before using a provider.

We have run into this pain points while [integrating LangChain with Jupyter AI](https://github.com/jupyterlab/jupyter-ai/pull/18). This proposal, if accepted, would allow for applications to build on top of LangChain LLM providers much more easily.

# Proposal

I propose:

1. We formalize the notion of model IDs and provider IDs, and enforce this naming convention broadly throughout the codebase and documentation.
2. We implement a unified interface for provider interaction by expanding the definition of `BaseLanguageModel`:

```python
class EnvAuthStrategy(BaseModel):
    type: Literal["env"] = 'env'
    name: str

# for sagemaker_endpoint expecting creds at ~/.aws/credentials
class FileAuthStrategy(BaseModel):
    type: Literal["file"] = 'file'
    path: str

AuthStrategy = Union[EnvAuthStrategy, FileAuthStrategy]

class BaseLanguageModel(BaseModel, ABC):
    # provider ID. this is currently bound to _llm_type()
    id: str
    
    # ID of the model to invoke by this provider
    model_id: str
    
    # List of supported models
    # For registry providers, this will just be ["*"]
    # See rest of the issue for explanation
    models: List[str]
    
    # List of package requirements
    package_reqs: List[str]
    
    # Authn/authz strategy
    auth_strategy: AuthStrategy
    
    ...
```

Subclasses should override these fields and [make all of them constant](https://docs.pydantic.dev/usage/schema/#field-customization) with the exception of `model_id`. For example, the `cohere` provider might look something like this:

```python
class CohereAuthStrategy(EnvAuthStrategy):
    name = "COHERE_API_KEY"

class Cohere(LLM, BaseModel):
  id = Field("cohere", const=True)
  
  # "medium" is just the default, still changeable at runtime
  model_id = "medium"
  
  # Cohere model provider supports any model available via
  # `cohere.Client#generate()`.`
  # Reference: https://docs.cohere.ai/reference/generate
  models: Field(["medium", "xlarge"], const=True)

  package_reqs: Field(["cohere"], const=True)
  
  auth_strategy: Field(CohereAuthStrategy, const=True)
```

This strategy also handles registry providers (see "Terminology" section below) nicely. **A model ID can be defined as any identifier for an instance of a model**. This is best illustrated with a few examples:

- `huggingface_hub`: the HF repo ID, e.g. `gpt2` or `google/flan-t5-xxl`
- `huggingface_endpoint`: the URL to the HF endpoint, e.g. `https://foo.us-west-2.aws.endpoints.huggingface.cloud/bar`
- `sagemaker_endpoint`: the endpoint name. Your region and authentication credentials should already be specified via boto3.

While the syntax of model IDs is indeed very different for registry providers, if you think about it, it still functions exactly as a model ID: it identifies one and only one model hosted elsewhere.

Because registry providers have a dynamic, unknown, and very large set of valid model IDs, they declare their supported models using a wildcard like so:

```
models: Field(["*"], const=True)
```

# Next steps

Let's first get consensus on this proposal and assign the work later. I'd be happy to kick off the PR and define the types, but I'll likely need to loop in other contributors to assist due to the scope of this proposal. There will also need to be some work done on the documentation as well.

# Appendix

## Terminology

- **Model provider / LLM provider**: A class that can provide one or more models. In LangChain, these currently inherit `BaseLanguageModel`.

  - **Registry provider**: A special subset of model providers that have a dynamic number of provided models.
    - These include but are not limited to: `huggingface_hub`, `huggingface_endpoint`, `sagemaker_endpoint`
    - These providers are unique in the sense that they do not have a static number of models they support. New models are being uploaded to HuggingFace Hub daily.
    - We call these providers "registry providers" (since they mimic the behavior of a package registry, like NPM/PyPi)

- **Model**: A model provided by a model provider (forgive the circular working definition, models are quite difficult to define strictly). For example, `text-davinci-003` is a model provided by the `openai` model provider.

- **Model provider ID**: a string identifier for a model provider. Currently this is retrieved from the `_llm_type` property.

- **Model ID**: a string identifier for a model.
