chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1 @@
|
||||
3.12
|
||||
@@ -0,0 +1,7 @@
|
||||
[project]
|
||||
name = "agent"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["agent-framework-ag-ui>=1.0.0b251117", "python-dotenv"]
|
||||
@@ -0,0 +1,126 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from textwrap import dedent
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import ChatAgent, ChatClientProtocol, ai_function
|
||||
from agent_framework_ag_ui import AgentFrameworkAgent
|
||||
from pydantic import Field
|
||||
|
||||
STATE_SCHEMA: dict[str, object] = {
|
||||
"proverbs": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": "Ordered list of the user's saved proverbs.",
|
||||
}
|
||||
}
|
||||
|
||||
PREDICT_STATE_CONFIG: dict[str, dict[str, str]] = {
|
||||
"proverbs": {
|
||||
"tool": "update_proverbs",
|
||||
"tool_argument": "proverbs",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ai_function(
|
||||
name="update_proverbs",
|
||||
description=(
|
||||
"Replace the entire list of proverbs with the provided values. "
|
||||
"Always include every proverb you want to keep."
|
||||
),
|
||||
)
|
||||
def update_proverbs(
|
||||
proverbs: Annotated[
|
||||
list[str],
|
||||
Field(
|
||||
description=(
|
||||
"The complete source of truth for the user's proverbs. "
|
||||
"Maintain ordering and include the full list on each call."
|
||||
)
|
||||
),
|
||||
],
|
||||
) -> str:
|
||||
"""Persist the provided set of proverbs."""
|
||||
return f"Proverbs updated. Tracking {len(proverbs)} item(s)."
|
||||
|
||||
|
||||
@ai_function(
|
||||
name="get_weather",
|
||||
description="Share a quick weather update for a location. Use this to render the frontend weather card.",
|
||||
)
|
||||
def get_weather(
|
||||
location: Annotated[
|
||||
str,
|
||||
Field(
|
||||
description="The city or region to describe. Use fully spelled out names."
|
||||
),
|
||||
],
|
||||
) -> str:
|
||||
"""Return a short natural language weather summary."""
|
||||
normalized = location.strip().title() or "the requested location"
|
||||
return (
|
||||
f"The weather in {normalized} is mild with a light breeze. "
|
||||
"Skies are mostly clear—perfect for planning something fun."
|
||||
)
|
||||
|
||||
|
||||
@ai_function(
|
||||
name="go_to_moon",
|
||||
description="Request a playful human-in-the-loop confirmation before launching a mission to the moon.",
|
||||
approval_mode="always_require",
|
||||
)
|
||||
def go_to_moon() -> str:
|
||||
"""Request human approval before continuing."""
|
||||
return "Mission control requested. Awaiting human approval for the lunar launch."
|
||||
|
||||
|
||||
def create_agent(chat_client: ChatClientProtocol) -> AgentFrameworkAgent:
|
||||
"""Instantiate the CopilotKit demo agent backed by Microsoft Agent Framework."""
|
||||
base_agent = ChatAgent(
|
||||
name="proverbs_agent",
|
||||
instructions=dedent(
|
||||
"""
|
||||
You help users brainstorm, organize, and refine proverbs while coordinating UI updates.
|
||||
|
||||
State sync:
|
||||
- The current list of proverbs is provided in the conversation context.
|
||||
- When you add, remove, or reorder proverbs, call `update_proverbs` with the full list.
|
||||
Never send partial updates—always include every proverb that should exist.
|
||||
- CRITICAL: When asked to "add" a proverb, you must:
|
||||
1. First, identify ALL existing proverbs from the conversation history
|
||||
2. Create EXACTLY ONE new proverb (never more than one unless explicitly requested)
|
||||
3. Call update_proverbs with: [all existing proverbs] + [the one new proverb]
|
||||
Example: Current: ["A", "B"] -> After adding: ["A", "B", "C"] (NOT ["A", "B", "C", "D", "E"])
|
||||
- When asked to "remove" a proverb, remove exactly ONE item unless user specifies otherwise.
|
||||
|
||||
Tool usage rules:
|
||||
- When user asks to go to the moon, you MUST call the `go_to_moon` tool immediately. Do NOT ask for approval
|
||||
yourself—the tool's approval workflow and the client UI will handle it.
|
||||
|
||||
Frontend integrations:
|
||||
- `get_weather` renders a weather card in the UI. Only call this tool when the user explicitly
|
||||
asks for weather. Do NOT call it after unrelated tasks or approvals.
|
||||
- `go_to_moon` requires explicit user approval before you proceed. Only use it when a
|
||||
user asks to launch or travel to the moon. Always call the tool instead of asking manually.
|
||||
|
||||
Conversation tips:
|
||||
- Reference the latest proverb list before suggesting changes.
|
||||
- Keep responses concise and friendly unless the user requests otherwise.
|
||||
- After you finish executing tools for the user's request, provide a brief, final assistant
|
||||
message summarizing exactly what changed. Do NOT call additional tools or switch topics
|
||||
after that summary unless the user asks. ALWAYS send this conversational summary so the message persists.
|
||||
""".strip()
|
||||
),
|
||||
chat_client=chat_client,
|
||||
tools=[update_proverbs, get_weather, go_to_moon],
|
||||
)
|
||||
|
||||
return AgentFrameworkAgent(
|
||||
agent=base_agent,
|
||||
name="CopilotKitMicrosoftAgentFrameworkAgent",
|
||||
description="Manages proverbs, weather snippets, and human-in-the-loop moon launches.",
|
||||
state_schema=STATE_SCHEMA,
|
||||
predict_state_config=PREDICT_STATE_CONFIG,
|
||||
require_confirmation=False, # Allow immediate state updates with follow-up messages
|
||||
)
|
||||
@@ -0,0 +1,79 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
import uvicorn
|
||||
from agent_framework._clients import ChatClientProtocol
|
||||
from azure.identity import DefaultAzureCredential
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
from agent_framework_ag_ui import add_agent_framework_fastapi_endpoint
|
||||
from dotenv import load_dotenv
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
|
||||
from agent import create_agent
|
||||
|
||||
load_dotenv()
|
||||
|
||||
|
||||
def _build_chat_client() -> ChatClientProtocol:
|
||||
try:
|
||||
if bool(os.getenv("AZURE_OPENAI_ENDPOINT")):
|
||||
# Azure OpenAI setup - uses environment variables by default
|
||||
# Optionally can pass deployment_name explicitly
|
||||
deployment_name = os.getenv(
|
||||
"AZURE_OPENAI_CHAT_DEPLOYMENT_NAME", "gpt-4o-mini"
|
||||
)
|
||||
return AzureOpenAIChatClient(
|
||||
credential=DefaultAzureCredential(),
|
||||
deployment_name=deployment_name,
|
||||
endpoint=os.getenv("AZURE_OPENAI_ENDPOINT"),
|
||||
)
|
||||
|
||||
if bool(os.getenv("OPENAI_API_KEY")):
|
||||
# OpenAI setup - requires explicit model_id and api_key
|
||||
return OpenAIChatClient(
|
||||
model_id=os.getenv("OPENAI_CHAT_MODEL_ID", "gpt-4o-mini"),
|
||||
api_key=os.getenv("OPENAI_API_KEY"),
|
||||
)
|
||||
|
||||
raise ValueError(
|
||||
"Either AZURE_OPENAI_ENDPOINT or OPENAI_API_KEY environment variable is required"
|
||||
)
|
||||
|
||||
except Exception as exc: # pragma: no cover
|
||||
raise RuntimeError(
|
||||
"Unable to initialize the chat client. Double-check your API credentials as documented in README.md."
|
||||
) from exc
|
||||
|
||||
|
||||
chat_client = _build_chat_client()
|
||||
my_agent = create_agent(chat_client)
|
||||
|
||||
app = FastAPI(title="CopilotKit + Microsoft Agent Framework (Python)")
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
|
||||
add_agent_framework_fastapi_endpoint(
|
||||
app=app,
|
||||
agent=my_agent,
|
||||
path="/",
|
||||
)
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
async def health():
|
||||
return {"status": "ok"}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
host = os.getenv("AGENT_HOST", "0.0.0.0")
|
||||
port = int(os.getenv("AGENT_PORT", "8000"))
|
||||
uvicorn.run("main:app", host=host, port=port, reload=True)
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user