Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/agents/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
- Browser localStorage for per-user built-in agent overrides; existing `config/agents.yaml` remains canonical shared standard profile source; backend in-memory sessions unchanged (005-baked-agent-customization)
- TypeScript 5.9 frontend; React 19; Python 3.12.6 backend unchanged + React, Vite 8, existing frontend API client, existing FastAPI skill endpoints, Playwright for visual verification (006-admin-skills-design-match)
- Existing filesystem-backed skills under `skills/<name>/SKILL.md`; no new storage (006-admin-skills-design-match)
- Python 3.12.6, TypeScript 5.9, React 19 + FastAPI, agent-framework-core/openai/azure-ai-search, Azure SDKs, React, Vite, react-markdown, MSAL (007-application-refactor)
- In-memory backend sessions, file-backed `skills/`, browser localStorage, Terraform-managed Azure App Service resources (007-application-refactor)

## Project Structure

Expand Down Expand Up @@ -52,6 +54,6 @@ uv run pytest # Run tests
- Package manager: uv only (never pip)

## Recent Changes
- 007-application-refactor: Added Python 3.12.6, TypeScript 5.9, React 19 + FastAPI, agent-framework-core/openai/azure-ai-search, Azure SDKs, React, Vite, react-markdown, MSAL
- 006-admin-skills-design-match: Added TypeScript 5.9 frontend; React 19; Python 3.12.6 backend unchanged + React, Vite 8, existing frontend API client, existing FastAPI skill endpoints, Playwright for visual verification
- 005-baked-agent-customization: Added Python 3.12.6 backend; TypeScript 5.9 frontend; React 19; Vite 8 + FastAPI, agent-framework-core/openai/azure-ai-search, React, MSAL, localStorage APIs
- 004-azd-deploy-frontend: Added Python 3.12 (backend), TypeScript (frontend) + FastAPI, React/Vite, Terraform (azurerm ~> 4.0)
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ wheels/

# Virtual environments
.venv
.venv/
venv/

.env
.env*
*.log

.ruff*

Expand All @@ -34,6 +37,8 @@ terraform.rc

.mypy_cache/
.pytest_cache/
coverage/
frontend/coverage/
# Certificates
certs/
.env.appsettings.json
Expand Down
138 changes: 11 additions & 127 deletions agent_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import re
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Sequence, overload
from typing import Any, Sequence

from agent_framework import CompactionProvider, InMemoryHistoryProvider, SkillsProvider
from agent_framework import Agent as RuntimeAgent
Expand All @@ -19,20 +19,10 @@
from dotenv import load_dotenv


from pydantic import BaseModel

from prompt_config import load_agent_profile
from mcp_servers import get_search_context_provider


class AgentBase(BaseModel):
name: str
instructions: str
description: str
token_budget: int = 16_000
tools: list[str] | None = None


@dataclass(frozen=True)
class ChatRuntime:
agent: RuntimeAgent
Expand Down Expand Up @@ -151,113 +141,6 @@ def _build_context_providers(
return providers


def _create_agent(
*,
name: str,
instructions: str,
description: str,
token_budget: int,
temperature: float,
tools: Sequence[Any] | None = None,
client: OpenAIChatClient | None = None,
summarizer_client: OpenAIChatClient | None = None,
logical_profile: str | None = None,
enable_search_context: bool = False,
skill_names: list[str] | None = None,
) -> RuntimeAgent:
runtime_client = client or OpenAIChatClient()

return runtime_client.as_agent(
name=_sanitize_agent_name(name),
instructions=instructions,
description=description,
tools=tools,
default_options={"temperature": temperature},
context_providers=_build_context_providers(
token_budget=token_budget,
summarizer_client=summarizer_client,
logical_profile=logical_profile,
enable_search_context=enable_search_context,
skill_names=skill_names,
),
)


@overload
def spawn_agent(agent: AgentBase, /) -> RuntimeAgent: ...


@overload
def spawn_agent(
*,
name: str,
instructions: str,
description: str,
token_budget: int = 16_000,
temperature: float | None = None,
tools: Sequence[Any] | None = None,
client: OpenAIChatClient | None = None,
summarizer_client: OpenAIChatClient | None = None,
logical_profile: str | None = None,
enable_search_context: bool = False,
skill_names: list[str] | None = None,
) -> RuntimeAgent: ...


def spawn_agent(
agent: AgentBase | None = None,
/,
*,
name: str | None = None,
instructions: str | None = None,
description: str | None = None,
token_budget: int = 16_000,
temperature: float | None = None,
tools: Sequence[Any] | None = None,
client: OpenAIChatClient | None = None,
summarizer_client: OpenAIChatClient | None = None,
logical_profile: str | None = None,
enable_search_context: bool = False,
skill_names: list[str] | None = None,
) -> RuntimeAgent:
if agent is not None:
if any(value is not None for value in (name, instructions, description)):
raise TypeError("Pass either an agent model or expanded agent fields, not both.")

return _create_agent(
name=agent.name,
instructions=agent.instructions,
description=agent.description,
token_budget=agent.token_budget,
temperature=temperature if temperature is not None else _get_default_temperature(),
tools=tools,
client=client,
summarizer_client=summarizer_client,
logical_profile=logical_profile,
enable_search_context=enable_search_context,
skill_names=skill_names,
)

if name is None or instructions is None or description is None:
raise TypeError(
"spawn_agent() requires either an agent model or name, instructions, and description keywords."
)

return _create_agent(
name=name,
instructions=instructions,
description=description,
token_budget=token_budget,
temperature=temperature if temperature is not None else _get_default_temperature(),
tools=tools,
client=client,
summarizer_client=summarizer_client,
logical_profile=logical_profile,
enable_search_context=enable_search_context,
skill_names=skill_names,
)


def _build_openai_clients() -> tuple[OpenAIChatClient, OpenAIChatClient]:
"""Create primary and summarizer OpenAI clients from environment variables.

Expand Down Expand Up @@ -344,18 +227,19 @@ def create_chat_runtime(

resolved_temperature = temperature if temperature is not None else _get_default_temperature()

agent = spawn_agent(
client=primary_client,
name=agent_name,
agent = primary_client.as_agent(
name=_sanitize_agent_name(agent_name),
instructions=runtime_instructions,
description=description,
token_budget=token_budget,
temperature=resolved_temperature,
tools=all_tools,
summarizer_client=summarizer_client,
logical_profile=logical_profile,
enable_search_context=enable_search_context,
skill_names=skill_names,
default_options={"temperature": resolved_temperature},
context_providers=_build_context_providers(
token_budget=token_budget,
summarizer_client=summarizer_client,
logical_profile=logical_profile,
enable_search_context=enable_search_context,
skill_names=skill_names,
),
)

logger.info(
Expand Down
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"type": "module",
"scripts": {
"dev": "vite",
"test": "tsc -b --pretty false",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
Expand Down
Loading