Aracılığıyla paylaş


AG-UI ile Ön Uç Aracı İşleme

Bu öğreticide, AG-UI istemcilerinize ön uç işlev araçlarının nasıl ekleneceği gösterilir. Ön uç araçları, istemci tarafında yürütülen ve yapay zeka aracısının kullanıcının yerel ortamıyla etkileşim kurmasına, istemciye özgü verilere erişmesine veya kullanıcı arabirimi işlemleri gerçekleştirmesine olanak sağlayan işlevlerdir. Sunucu bu araçları ne zaman çağıracaklarını düzenler, ancak yürütme tamamen istemcide gerçekleşir.

Önkoşullar

Başlamadan önce Başlarken öğreticisini tamamladığınızdan ve aşağıdakilere sahip olduğunuzdan emin olun:

  • .NET 8.0 veya üzeri
  • Microsoft.Agents.AI.AGUI paket yüklendi
  • Microsoft.Agents.AI paket yüklendi
  • AG-UI istemci kurulumu hakkında temel bilgiler

Ön Uç Araçları nedir?

Ön uç araçları şunlara sahip işlev araçlarıdır:

  • İstemcide tanımlanır ve kaydedilir
  • İstemcinin ortamında yürüt (sunucuda değil)
  • Yapay zeka aracısının istemciye özgü kaynaklarla etkileşim kurmasına izin verme
  • Sonuçları aracının yanıtlara katabilmesi için sunucuya geri gönderin
  • Kişiselleştirilmiş, bağlama duyarlı deneyimleri etkinleştirme

Yaygın kullanım örnekleri:

  • Yerel algılayıcı verilerini okuma (GPS, sıcaklık vb.)
  • İstemci tarafı depolamaya veya tercihlere erişme
  • UI işlemlerini gerçekleştirme (temaları değiştirme, bildirimleri görüntüleme)
  • Cihaza özgü özelliklerle (kamera, mikrofon) etkileşim kurma

İstemciye Frontend Araçları Kaydetme

"Başlarken kılavuzundaki en önemli fark, araçları istemci ajanına kaydetmektir." Değişiklikler şunlardır:

// Define a frontend function tool
[Description("Get the user's current location from GPS.")]
static string GetUserLocation()
{
    // Access client-side GPS
    return "Amsterdam, Netherlands (52.37°N, 4.90°E)";
}

// Create frontend tools
AITool[] frontendTools = [AIFunctionFactory.Create(GetUserLocation)];

// Pass tools when creating the agent
AIAgent agent = chatClient.AsAIAgent(
    name: "agui-client",
    description: "AG-UI Client Agent",
    tools: frontendTools);

İstemci kodunuzun geri kalanı Başlarken öğreticisinde gösterildiği gibi kalır.

Araçlar Sunucuya Nasıl Gönderilir?

AsAIAgent() ile araçları kaydettiğinizde, AGUIChatClient otomatik olarak:

  1. Araç tanımlarını (adlar, açıklamalar, parametre şemaları) yakalar
  2. Her istekle birlikte araçları sunucu aracısına gönderir ve bunları ile eşler ChatAgentRunOptions.ChatOptions.Tools

Sunucu istemci aracı bildirimlerini alır ve yapay zeka modeli bunları ne zaman çağıracaklarına karar verebilir.

Ara Yazılım ile Araçları İnceleme ve Değiştirme

Aracı işlem sürecini incelemek veya değiştirmek için araçlara erişim sağlamak da dahil olmak üzere, aracı ara katman yazılımını kullanabilirsiniz.

// Create agent with middleware that inspects tools
AIAgent inspectableAgent = baseAgent
    .AsBuilder()
    .Use(runFunc: null, runStreamingFunc: InspectToolsMiddleware)
    .Build();

static async IAsyncEnumerable<AgentResponseUpdate> InspectToolsMiddleware(
    IEnumerable<ChatMessage> messages,
    AgentSession? session,
    AgentRunOptions? options,
    AIAgent innerAgent,
    CancellationToken cancellationToken)
{
    // Access the tools from ChatClientAgentRunOptions
    if (options is ChatClientAgentRunOptions chatOptions)
    {
        IList<AITool>? tools = chatOptions.ChatOptions?.Tools;
        if (tools != null)
        {
            Console.WriteLine($"Tools available for this run: {tools.Count}");
            foreach (AITool tool in tools)
            {
                if (tool is AIFunction function)
                {
                    Console.WriteLine($"  - {function.Metadata.Name}: {function.Metadata.Description}");
                }
            }
        }
    }

    await foreach (AgentResponseUpdate update in innerAgent.RunStreamingAsync(messages, session, options, cancellationToken))
    {
        yield return update;
    }
}

Bu ara yazılım düzeni şunları yapmanızı sağlar:

  • Yürütmeden önce araç tanımlarını doğrulama

Önemli Kavramlar

Ön uç araçlarına yönelik yeni kavramlar şunlardır:

  • İstemci tarafı kaydı: Araçlar önce AIFunctionFactory.Create() kullanılarak istemci üzerinde kaydedilir ve ardından AsAIAgent()'ye aktarılır.
  • Otomatik yakalama: Araçlar otomatik olarak yakalanır ve ChatAgentRunOptions.ChatOptions.Tools aracılığıyla gönderilir.

Ön Uç Araçları Nasıl Çalışır?

Server-Side Süreci

Sunucu, ön uç araçlarının uygulama ayrıntılarını bilmiyor. Yalnızca şu bilgileri bilir:

  1. Araç adları ve açıklamaları (istemci kaydından)
  2. Parametre şemaları
  3. Aracın ne zaman çalıştırılması istenmelidir?

Yapay zeka aracısı bir ön uç aracısını çağırmaya karar verince:

  1. Sunucu, SSE aracılığıyla istemciye bir araç çağrısı isteği gönderir
  2. Sunucu istemcinin aracı yürütmesini ve sonuçları döndürmesini bekler
  3. Sunucu sonuçları aracının bağlamı içine ekler
  4. Ajan, araç sonuçlarıyla işlemeye devam ediyor

Müşteri Tarafı Akışı

İstemci ön yüz aracının çalıştırılmasını yönetir.

  1. Sunucudan, araç çağrısı isteğini gösteren FunctionCallContent alır
  2. Araç adını yerel olarak kaydedilmiş bir işlevle eşleştirir
  3. İstekten parametreleri seri durumdan kaldırır
  4. İşlevi yerel olarak yürütür
  5. Sonucu serileştirir
  6. Sunucuya geri gönderir FunctionResultContent
  7. Aracı yanıtları almaya devam ediyor

Arayüz Araçlarıyla Beklenen Sonuç

Aracı ön uç araçlarını çağırdığında araç çağrısını görürsünüz ve akış çıkışıyla sonuçlanır:

User (:q or quit to exit): Where am I located?

[Client Tool Call - Name: GetUserLocation]
[Client Tool Result: Amsterdam, Netherlands (52.37°N, 4.90°E)]

You are currently in Amsterdam, Netherlands, at coordinates 52.37°N, 4.90°E.

Ön Uç Araçları için Sunucu Kurulumu

Sunucunun ön uç araçlarını desteklemek için özel yapılandırmaya ihtiyacı yoktur. Başlarken kılavuzundan standart AG-UI sunucusunu kullanın, otomatik olarak:

  • İstemci bağlantısı sırasında ön uç araç bildirimlerini alır
  • Yapay zeka aracısının ihtiyaç duyduğunda araç yürütme isteğinde bulunur
  • İstemciden sonuçları bekler
  • Sonuçları aracının karar alma sürecine dahil eder

Sonraki Adımlar

Ön uç araçlarını anladığınıza göre şunları yapabilirsiniz:

Ek Kaynaklar

Bu öğreticide, AG-UI istemcilerinize ön uç işlev araçlarının nasıl ekleneceği gösterilir. Ön uç araçları, istemci tarafında yürütülen ve yapay zeka aracısının kullanıcının yerel ortamıyla etkileşim kurmasına, istemciye özgü verilere erişmesine veya kullanıcı arabirimi işlemleri gerçekleştirmesine olanak sağlayan işlevlerdir.

Önkoşullar

Başlamadan önce Başlarken öğreticisini tamamladığınızdan ve aşağıdakilere sahip olduğunuzdan emin olun:

  • Python 3.10 veya üzeri
  • httpx HTTP istemcisi işlevselliği için yüklendi
  • AG-UI istemci kurulumu hakkında temel bilgiler
  • Azure OpenAI hizmeti yapılandırıldı

Ön Uç Araçları nedir?

Ön uç araçları şunlara sahip işlev araçlarıdır:

  • İstemcide tanımlanır ve kaydedilir
  • İstemcinin ortamında yürüt (sunucuda değil)
  • Yapay zeka aracısının istemciye özgü kaynaklarla etkileşim kurmasına izin verme
  • Sonuçları aracının yanıtlara katabilmesi için sunucuya geri gönderin

Yaygın kullanım örnekleri:

  • Yerel algılayıcı verilerini okuma
  • İstemci tarafı depolamaya veya tercihlere erişme
  • Kullanıcı arabirimi işlemlerini gerçekleştirme
  • Cihaza özgü özelliklerle etkileşim kurma

Ön Uç Araçları Oluşturma

Python'daki ön uç araçları arka uç araçlarına benzer şekilde tanımlanır ancak istemciye kaydedilir:

from typing import Annotated
from pydantic import BaseModel, Field


class SensorReading(BaseModel):
    """Sensor reading from client device."""
    temperature: float
    humidity: float
    air_quality_index: int


def read_climate_sensors(
    include_temperature: Annotated[bool, Field(description="Include temperature reading")] = True,
    include_humidity: Annotated[bool, Field(description="Include humidity reading")] = True,
) -> SensorReading:
    """Read climate sensor data from the client device."""
    # Simulate reading from local sensors
    return SensorReading(
        temperature=22.5 if include_temperature else 0.0,
        humidity=45.0 if include_humidity else 0.0,
        air_quality_index=75,
    )


def change_background_color(color: Annotated[str, Field(description="Color name")] = "blue") -> str:
    """Change the console background color."""
    # Simulate UI change
    print(f"\n🎨 Background color changed to {color}")
    return f"Background changed to {color}"

Ön Uç Araçları ile AG-UI İstemcisi Oluşturma

Ön uç araçlarıyla eksiksiz bir istemci uygulaması aşağıdadır:

"""AG-UI client with frontend tools."""

import asyncio
import json
import os
from typing import Annotated, AsyncIterator

import httpx
from pydantic import BaseModel, Field


class SensorReading(BaseModel):
    """Sensor reading from client device."""
    temperature: float
    humidity: float
    air_quality_index: int


# Define frontend tools
def read_climate_sensors(
    include_temperature: Annotated[bool, Field(description="Include temperature")] = True,
    include_humidity: Annotated[bool, Field(description="Include humidity")] = True,
) -> SensorReading:
    """Read climate sensor data from the client device."""
    return SensorReading(
        temperature=22.5 if include_temperature else 0.0,
        humidity=45.0 if include_humidity else 0.0,
        air_quality_index=75,
    )


def get_user_location() -> dict:
    """Get the user's current GPS location."""
    # Simulate GPS reading
    return {
        "latitude": 52.3676,
        "longitude": 4.9041,
        "accuracy": 10.0,
        "city": "Amsterdam",
    }


# Tool registry maps tool names to functions
FRONTEND_TOOLS = {
    "read_climate_sensors": read_climate_sensors,
    "get_user_location": get_user_location,
}


class AGUIClientWithTools:
    """AG-UI client with frontend tool support."""

    def __init__(self, server_url: str, tools: dict):
        self.server_url = server_url
        self.tools = tools
        self.thread_id: str | None = None

    async def send_message(self, message: str) -> AsyncIterator[dict]:
        """Send a message and handle streaming response with tool execution."""
        # Prepare tool declarations for the server
        tool_declarations = []
        for name, func in self.tools.items():
            tool_declarations.append({
                "name": name,
                "description": func.__doc__ or "",
                # Add parameter schema from function signature
            })

        request_data = {
            "messages": [
                {"role": "system", "content": "You are a helpful assistant with access to client tools."},
                {"role": "user", "content": message},
            ],
            "tools": tool_declarations,  # Send tool declarations to server
        }

        if self.thread_id:
            request_data["thread_id"] = self.thread_id

        async with httpx.AsyncClient(timeout=60.0) as client:
            async with client.stream(
                "POST",
                self.server_url,
                json=request_data,
                headers={"Accept": "text/event-stream"},
            ) as response:
                response.raise_for_status()

                async for line in response.aiter_lines():
                    if line.startswith("data: "):
                        data = line[6:]
                        try:
                            event = json.loads(data)

                            # Handle tool call requests from server
                            if event.get("type") == "TOOL_CALL_REQUEST":
                                await self._handle_tool_call(event, client)
                            else:
                                yield event

                            # Capture thread_id
                            if event.get("type") == "RUN_STARTED" and not self.thread_id:
                                self.thread_id = event.get("threadId")

                        except json.JSONDecodeError:
                            continue

    async def _handle_tool_call(self, event: dict, client: httpx.AsyncClient):
        """Execute frontend tool and send result back to server."""
        tool_name = event.get("toolName")
        tool_call_id = event.get("toolCallId")
        arguments = event.get("arguments", {})

        print(f"\n\033[95m[Client Tool Call: {tool_name}]\033[0m")
        print(f"  Arguments: {arguments}")

        try:
            # Execute the tool
            tool_func = self.tools.get(tool_name)
            if not tool_func:
                raise ValueError(f"Unknown tool: {tool_name}")

            result = tool_func(**arguments)

            # Convert Pydantic models to dict
            if hasattr(result, "model_dump"):
                result = result.model_dump()

            print(f"\033[94m[Client Tool Result: {result}]\033[0m")

            # Send result back to server
            await client.post(
                f"{self.server_url}/tool_result",
                json={
                    "tool_call_id": tool_call_id,
                    "result": result,
                },
            )

        except Exception as e:
            print(f"\033[91m[Tool Error: {e}]\033[0m")
            # Send error back to server
            await client.post(
                f"{self.server_url}/tool_result",
                json={
                    "tool_call_id": tool_call_id,
                    "error": str(e),
                },
            )


async def main():
    """Main client loop with frontend tools."""
    server_url = os.environ.get("AGUI_SERVER_URL", "http://127.0.0.1:8888/")
    print(f"Connecting to AG-UI server at: {server_url}\n")

    client = AGUIClientWithTools(server_url, FRONTEND_TOOLS)

    try:
        while True:
            message = input("\nUser (:q or quit to exit): ")
            if not message.strip():
                continue

            if message.lower() in (":q", "quit"):
                break

            print()
            async for event in client.send_message(message):
                event_type = event.get("type", "")

                if event_type == "RUN_STARTED":
                    print(f"\033[93m[Run Started]\033[0m")

                elif event_type == "TEXT_MESSAGE_CONTENT":
                    print(f"\033[96m{event.get('delta', '')}\033[0m", end="", flush=True)

                elif event_type == "RUN_FINISHED":
                    print(f"\n\033[92m[Run Finished]\033[0m")

                elif event_type == "RUN_ERROR":
                    error_msg = event.get("message", "Unknown error")
                    print(f"\n\033[91m[Error: {error_msg}]\033[0m")

            print()

    except KeyboardInterrupt:
        print("\n\nExiting...")
    except Exception as e:
        print(f"\n\033[91mError: {e}\033[0m")


if __name__ == "__main__":
    asyncio.run(main())

Ön Uç Araçları Nasıl Çalışır?

Protokol Akışı

  1. İstemci Kaydı: İstemci sunucuya araç bildirimleri (adlar, açıklamalar, parametreler) gönderir
  2. Sunucu Düzenlemesi: Kullanıcı isteğine göre ön uç araçlarının ne zaman çağrıleceğine yapay zeka aracısı karar verir
  3. Araç Çağrı İsteği: Sunucu SSE aracılığıyla istemciye TOOL_CALL_REQUEST olay gönderir
  4. İstemci Yürütmesi: İstemci aracı yerel olarak yürütür
  5. Sonuç Gönderimi: İstemci, POST isteği aracılığıyla sonucu sunucuya geri gönderir
  6. Aracı İşleme: Sunucu sonucu birleştirir ve yanıta devam eder

Önemli Olaylar

  • TOOL_CALL_REQUEST: Sunucu ön uç aracının yürütülmesini istemektedir
  • TOOL_CALL_RESULT: İstemci yürütme sonucunu gönderir (HTTP POST aracılığıyla)

Beklenen Çıkış

User (:q or quit to exit): What's the temperature reading from my sensors?

[Run Started]

[Client Tool Call: read_climate_sensors]
  Arguments: {'include_temperature': True, 'include_humidity': True}
[Client Tool Result: {'temperature': 22.5, 'humidity': 45.0, 'air_quality_index': 75}]

Based on your sensor readings, the current temperature is 22.5°C and the 
humidity is at 45%. These are comfortable conditions!
[Run Finished]

Sunucu Kurulumu

Başlangıç kılavuzundaki standart AG-UI sunucusu, frontend araçlarını otomatik olarak destekler. Sunucu tarafında değişiklik gerekmez; araç düzenlemesini otomatik olarak işler.

En İyi Yöntemler

Security

def access_sensitive_data() -> str:
    """Access user's sensitive data."""
    # Always check permissions first
    if not has_permission():
        return "Error: Permission denied"

    try:
        # Access data
        return "Data retrieved"
    except Exception as e:
        # Don't expose internal errors
        return "Unable to access data"

Hata İşleme

def read_file(path: str) -> str:
    """Read a local file."""
    try:
        with open(path, "r") as f:
            return f.read()
    except FileNotFoundError:
        return f"Error: File not found: {path}"
    except PermissionError:
        return f"Error: Permission denied: {path}"
    except Exception as e:
        return f"Error reading file: {str(e)}"

Asenkron İşlemler

async def capture_photo() -> str:
    """Capture a photo from device camera."""
    # Simulate camera access
    await asyncio.sleep(1)
    return "photo_12345.jpg"

Sorun giderme

Araçlar Çağrılmıyor

  1. Araç bildirimlerinin sunucuya gönderildiğinden emin olun
  2. Araç açıklamalarının amacı açıkça gösterdiğini doğrulama
  3. Araç kaydı için sunucu günlüklerini kontrol et

Yürütme Hataları

  1. Kapsamlı hata işleme ekleyin
  2. İşlemeden önce parametreleri doğrulama
  3. Kullanıcı dostu hata iletileri döndürme
  4. Hata ayıklama için hata kayıtları tutma

Tür Sorunları

  1. Karmaşık türler için Pydantic modellerini kullanma
  2. Serileştirmeden önce modelleri diktelere dönüştürme
  3. Tür dönüştürmelerini açıkça işleme

Sonraki Adımlar

Ek Kaynaklar