Skip to content

Commit

Permalink
"Live" validates config in the UI (#89)
Browse files Browse the repository at this point in the history
And shows an indicator if the config is invalid.

Additionally:
- improves the openai-client config models to improve validation of
endpoint fields
- return errors list on /config for other clients who don't want to
validate on their own
- simplifies assistant launch by picking random port
- now that assistants advertise their callback URL to the workbench,
they can use a random available port at startup.
- which means we don't have to pick a port for each assistant when we
start them, or make sure that none of their launch configurations
conflict
- this highlighted a bug in the workbench-service that wasn't
re-creating the client when the URL changed - this also fixes that bug
  • Loading branch information
markwaddle authored Oct 5, 2024
1 parent 4167169 commit c225dd4
Show file tree
Hide file tree
Showing 46 changed files with 253 additions and 124 deletions.
5 changes: 5 additions & 0 deletions assistants/prospector-assistant/.env.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# Description: Example of .env file
# Usage: Copy this file to .env and set the values

# NOTE:
# - Environment variables in the host environment will take precedence over values in this file.
# - When running with VS Code, you must 'stop' and 'start' the process for changes to take effect.
# It is not enough to just use the VS Code 'restart' button

# Assistant Service
ASSISTANT__AZURE_OPENAI_ENDPOINT=https://<YOUR-RESOURCE-NAME>.openai.azure.com/
ASSISTANT__AZURE_CONTENT_SAFETY_ENDPOINT=https://<YOUR-RESOURCE-NAME>.cognitiveservices.azure.com/
2 changes: 1 addition & 1 deletion assistants/prospector-assistant/.vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"name": "assistants: prospector-assistant",
"cwd": "${workspaceFolder}",
"module": "semantic_workbench_assistant.start",
"args": ["assistant.chat:app", "--port", "3011"],
"args": ["assistant.chat:app"],
"consoleTitle": "${workspaceFolderBasename}"
}
]
Expand Down
2 changes: 1 addition & 1 deletion assistants/prospector-assistant/assistant/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
#
# create the configuration provider, using the extended configuration model
#
assistant_config = BaseModelAssistantConfig(AssistantConfigModel())
assistant_config = BaseModelAssistantConfig(AssistantConfigModel)


# define the content safety evaluator factory
Expand Down
2 changes: 1 addition & 1 deletion assistants/prospector-assistant/assistant/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ class AssistantConfigModel(BaseModel):
),
] = RequestConfig()

service_config: openai_client.ServiceConfig = openai_client.AzureOpenAIServiceConfig()
service_config: openai_client.ServiceConfig

content_safety_config: Annotated[
CombinedContentSafetyEvaluatorConfig,
Expand Down
5 changes: 5 additions & 0 deletions assistants/skill-assistant/.env.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# Description: Example of .env file
# Usage: Copy this file to .env and set the values

# NOTE:
# - Environment variables in the host environment will take precedence over values in this file.
# - When running with VS Code, you must 'stop' and 'start' the process for changes to take effect.
# It is not enough to just use the VS Code 'restart' button

# Assistant Service
ASSISTANT__AZURE_OPENAI_ENDPOINT=https://<YOUR-RESOURCE-NAME>.openai.azure.com/
ASSISTANT__AZURE_CONTENT_SAFETY_ENDPOINT=https://<YOUR-RESOURCE-NAME>.cognitiveservices.azure.com/
2 changes: 1 addition & 1 deletion assistants/skill-assistant/.vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"name": "assistants: skill-assistant",
"cwd": "${workspaceFolder}",
"module": "semantic_workbench_assistant.start",
"args": ["assistant.skill_assistant:app", "--port", "3012"],
"args": ["assistant.skill_assistant:app"],
"consoleTitle": "${workspaceFolderBasename}"
}
]
Expand Down
2 changes: 1 addition & 1 deletion assistants/skill-assistant/assistant/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ class AssistantConfigModel(BaseModel):
),
] = ChatDriverConfig()

service_config: openai_client.ServiceConfig = openai_client.AzureOpenAIServiceConfig()
service_config: openai_client.ServiceConfig

content_safety_config: Annotated[
CombinedContentSafetyEvaluatorConfig,
Expand Down
6 changes: 4 additions & 2 deletions examples/python/python-01-echo-bot/.env.example
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# Description: Example of .env file
# Usage: Copy this file to .env and set the values

# NOTE: Changes to this file will not take effect until the project service is 'stopped' and 'started'
# It is not enough to just use the VS Code 'restart' button
# NOTE:
# - Environment variables in the host environment will take precedence over values in this file.
# - When running with VS Code, you must 'stop' and 'start' the process for changes to take effect.
# It is not enough to just use the VS Code 'restart' button

# Assistant Service
# The ASSISTANT__ prefix is used to group all the environment variables related to the assistant service.
Expand Down
2 changes: 1 addition & 1 deletion examples/python/python-01-echo-bot/.vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"name": "examples: python-01-echo-bot",
"cwd": "${workspaceFolder}",
"module": "semantic_workbench_assistant.start",
"args": ["assistant.chat:app", "--port", "3001"],
"args": ["assistant.chat:app"],
"consoleTitle": "${workspaceFolderBasename}"
}
]
Expand Down
4 changes: 1 addition & 3 deletions examples/python/python-01-echo-bot/assistant/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,7 @@
#
# create the configuration provider, using the extended configuration model
#
assistant_config = BaseModelAssistantConfig(
default=AssistantConfigModel(),
)
assistant_config = BaseModelAssistantConfig(AssistantConfigModel)

content_safety = ContentSafety(AlwaysWarnContentSafetyEvaluator.factory)

Expand Down
6 changes: 4 additions & 2 deletions examples/python/python-02-simple-chatbot/.env.example
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# Description: Example of .env file
# Usage: Copy this file to .env and set the values

# NOTE: Changes to this file will not take effect until the project service is 'stopped' and 'started'
# It is not enough to just use the VS Code 'restart' button
# NOTE:
# - Environment variables in the host environment will take precedence over values in this file.
# - When running with VS Code, you must 'stop' and 'start' the process for changes to take effect.
# It is not enough to just use the VS Code 'restart' button

# Assistant Service
ASSISTANT__AZURE_OPENAI_ENDPOINT=https://<YOUR-RESOURCE-NAME>.openai.azure.com/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"name": "examples: python-02-simple-chatbot",
"cwd": "${workspaceFolder}",
"module": "semantic_workbench_assistant.start",
"args": ["assistant.chat:app", "--port", "3002"],
"args": ["assistant.chat:app"],
"consoleTitle": "${workspaceFolderBasename}"
}
]
Expand Down
2 changes: 1 addition & 1 deletion examples/python/python-02-simple-chatbot/assistant/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
#
# create the configuration provider, using the extended configuration model
#
assistant_config = BaseModelAssistantConfig(AssistantConfigModel())
assistant_config = BaseModelAssistantConfig(AssistantConfigModel)


# define the content safety evaluator factory
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ class AssistantConfigModel(BaseModel):
),
] = RequestConfig()

service_config: openai_client.ServiceConfig = openai_client.AzureOpenAIServiceConfig()
service_config: openai_client.ServiceConfig

content_safety_config: Annotated[
CombinedContentSafetyEvaluatorConfig,
Expand Down
6 changes: 4 additions & 2 deletions examples/python/python-03-multimodel-chatbot/.env.example
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# Description: Example of .env file
# Usage: Copy this file to .env and set the values

# NOTE: Changes to this file will not take effect until the project service is 'stopped' and 'started'
# It is not enough to just use the VS Code 'restart' button
# NOTE:
# - Environment variables in the host environment will take precedence over values in this file.
# - When running with VS Code, you must 'stop' and 'start' the process for changes to take effect.
# It is not enough to just use the VS Code 'restart' button

# Assistant Service
ASSISTANT__AZURE_OPENAI_ENDPOINT=https://<YOUR-RESOURCE-NAME>.openai.azure.com/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"name": "examples: python-03-multimodel-chatbot",
"cwd": "${workspaceFolder}",
"module": "semantic_workbench_assistant.start",
"args": ["assistant.chat:app", "--port", "3003"],
"args": ["assistant.chat:app"],
"consoleTitle": "${workspaceFolderBasename}",
"justMyCode": false
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
#
# create the configuration provider, using the extended configuration model
#
assistant_config = BaseModelAssistantConfig(AssistantConfigModel())
assistant_config = BaseModelAssistantConfig(AssistantConfigModel)


# define the content safety evaluator factory
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ class AssistantConfigModel(BaseModel):
discriminator="service_type",
),
UISchema(widget="radio", hide_title=True),
] = AzureOpenAIServiceConfig()
] = AzureOpenAIServiceConfig.model_construct()

content_safety_config: Annotated[
CombinedContentSafetyEvaluatorConfig,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from azure.core.credentials import AzureKeyCredential
from azure.identity import DefaultAzureCredential
from pydantic import BaseModel, ConfigDict, Field
from pydantic import BaseModel, ConfigDict, Field, HttpUrl
from semantic_workbench_assistant import config
from semantic_workbench_assistant.config import ConfigSecretStr, UISchema

Expand Down Expand Up @@ -61,11 +61,9 @@ class AzureServiceKeyAuthConfig(BaseModel):
# It will hide the value in the UI.
ConfigSecretStr,
Field(
default="",
title="Azure Service API Key",
description="The service API key for your resource instance.",
),
UISchema(placeholder="[optional]"),
]


Expand All @@ -87,7 +85,9 @@ def get_azure_default_credential() -> DefaultAzureCredential:


class AzureContentSafetyEvaluatorConfig(BaseModel):
model_config = ConfigDict(title="Azure Content Safety Evaluator")
model_config = ConfigDict(
title="Azure Content Safety Evaluator", json_schema_extra={"required": ["azure_content_safety_endpoint"]}
)

service_type: Annotated[Literal["azure-content-safety"], UISchema(widget="hidden")] = "azure-content-safety"

Expand Down Expand Up @@ -129,12 +129,14 @@ class AzureContentSafetyEvaluatorConfig(BaseModel):
] = AzureIdentityAuthConfig()

azure_content_safety_endpoint: Annotated[
str,
HttpUrl,
Field(
title="Azure Content Safety Service Endpoint",
description="The endpoint to use for the Azure Content Safety service.",
default=config.first_env_var("azure_content_safety_endpoint", "assistant__azure_content_safety_endpoint")
or "",
),
] = config.first_env_var("azure_content_safety_endpoint", "assistant__azure_content_safety_endpoint") or ""
]

# set on the class to avoid re-authenticating for each request
_azure_default_credential: DefaultAzureCredential | None = None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ async def _evaluate_batch(self, text: str) -> ContentSafetyEvaluation:
# send the text to the Azure Content Safety service for evaluation
try:
response = ContentSafetyClient(
endpoint=self.config.azure_content_safety_endpoint,
endpoint=str(self.config.azure_content_safety_endpoint),
credential=self.config._get_azure_credentials(),
).analyze_text(AnalyzeTextOptions(text=text))
except Exception as e:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ class CombinedContentSafetyEvaluatorConfig(BaseModel):
title="Content Safety Evaluator",
),
UISchema(widget="radio", hide_title=True),
] = AzureContentSafetyEvaluatorConfig()
] = AzureContentSafetyEvaluatorConfig.model_construct()
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
class OpenAIContentSafetyEvaluatorConfig(BaseModel):
model_config = ConfigDict(
title="OpenAI Content Safety Evaluator",
json_schema_extra={
"required": ["openai_api_key"],
},
)

service_type: Annotated[
Expand Down Expand Up @@ -60,7 +63,6 @@ class OpenAIContentSafetyEvaluatorConfig(BaseModel):
openai_api_key: Annotated[
ConfigSecretStr,
Field(
default="",
title="OpenAI API Key",
description="The API key to use for the OpenAI API.",
),
Expand Down
36 changes: 36 additions & 0 deletions libraries/python/openai-client/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"editor.bracketPairColorization.enabled": true,
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit",
"source.fixAll": "explicit"
},
"editor.guides.bracketPairs": "active",
"editor.formatOnPaste": true,
"editor.formatOnType": true,
"editor.formatOnSave": true,
"files.eol": "\n",
"files.trimTrailingWhitespace": true,
"python.analysis.autoFormatStrings": true,
"python.analysis.autoImportCompletions": true,
"python.analysis.diagnosticMode": "workspace",
"python.analysis.fixAll": ["source.unusedImports"],
"python.analysis.inlayHints.functionReturnTypes": true,
"python.analysis.typeCheckingMode": "basic",
"python.defaultInterpreterPath": "${workspaceFolder}/.venv",
"[python]": {
"editor.defaultFormatter": "charliermarsh.ruff",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll": "explicit",
"source.unusedImports": "explicit",
"source.organizeImports": "explicit",
"source.formatDocument": "explicit"
}
},
"ruff.nativeServer": "on",
"search.exclude": {
"**/.venv": true,
"**/.data": true,
"**/__pycache__": true
}
}
22 changes: 12 additions & 10 deletions libraries/python/openai-client/openai_client/client.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
from azure.identity import DefaultAzureCredential, get_bearer_token_provider
from openai import AsyncAzureOpenAI, AsyncOpenAI
from openai.lib.azure import AsyncAzureADTokenProvider
from .config import ServiceConfig, AzureOpenAIApiKeyAuthConfig, AzureOpenAIAzureIdentityAuthConfig, AzureOpenAIServiceConfig, OpenAIServiceConfig
from .config import (
ServiceConfig,
AzureOpenAIApiKeyAuthConfig,
AzureOpenAIAzureIdentityAuthConfig,
AzureOpenAIServiceConfig,
OpenAIServiceConfig,
)


def create_client(
service_config: ServiceConfig, *, api_version: str = "2024-08-01-preview"
) -> AsyncOpenAI:
def create_client(service_config: ServiceConfig, *, api_version: str = "2024-08-01-preview") -> AsyncOpenAI:
"""
Creates an AsyncOpenAI client based on the provided service configuration.
"""
Expand All @@ -17,21 +21,20 @@ def create_client(
return AsyncAzureOpenAI(
api_key=service_config.auth_config.azure_openai_api_key,
azure_deployment=service_config.azure_openai_deployment,
azure_endpoint=service_config.azure_openai_endpoint,
azure_endpoint=str(service_config.azure_openai_endpoint),
api_version=api_version,
)

case AzureOpenAIAzureIdentityAuthConfig():
return AsyncAzureOpenAI(
azure_ad_token_provider=_get_azure_bearer_token_provider(),
azure_deployment=service_config.azure_openai_deployment,
azure_endpoint=service_config.azure_openai_endpoint,
azure_endpoint=str(service_config.azure_openai_endpoint),
api_version=api_version,
)

case _:
raise ValueError(
f"Invalid auth method type: {type(service_config.auth_config)}")
raise ValueError(f"Invalid auth method type: {type(service_config.auth_config)}")

case OpenAIServiceConfig():
return AsyncOpenAI(
Expand All @@ -40,8 +43,7 @@ def create_client(
)

case _:
raise ValueError(
f"Invalid service config type: {type(service_config)}")
raise ValueError(f"Invalid service config type: {type(service_config)}")


_lazy_initialized_azure_bearer_token_provider = None
Expand Down
Loading

0 comments on commit c225dd4

Please sign in to comment.