Xây Dựng Đại Lý Đầu Tư AI Chứng Khoán Của Riêng Bạn Với Agno + AG-UI: Hướng Dẫn Toàn Diện

Trong kỷ nguyên công nghệ số, việc tận dụng Trí tuệ Nhân tạo (AI) để tối ưu hóa quyết định đầu tư đang trở thành một xu hướng tất yếu. Bạn có bao giờ mơ ước sở hữu một đại lý AI cá nhân, có khả năng phân tích thị trường chứng khoán, quản lý danh mục và phản hồi theo thời gian thực? Bài viết này sẽ biến ước mơ đó thành hiện thực!

Chúng ta sẽ cùng nhau khám phá cách tích hợp mạnh mẽ giữa các tác nhân AI của Agno với giao thức AG-UI tiên tiến. Hơn thế nữa, chúng ta sẽ mở rộng khả năng này bằng cách kết nối chúng với CopilotKit, cho phép người dùng tương tác trực tiếp với đại lý AI thông qua giao diện trò chuyện thân thiện và nhận phản hồi trực tuyến trên frontend.

Hãy sẵn sàng để xây dựng một hệ thống đại lý đầu tư AI chứng khoán độc đáo và mạnh mẽ. Dưới đây là những gì chúng ta sẽ tìm hiểu:

  • AG-UI Protocol là gì và tại sao nó lại quan trọng?
  • Tích hợp các tác nhân Agno với giao thức AG-UI.
  • Xây dựng và tích hợp giao diện người dùng (frontend) với AG-UI + Agno Agents bằng CopilotKit.

Hãy cùng xem trước thành quả chúng ta sẽ đạt được:

(Hình ảnh minh họa giao diện ứng dụng AI Stock Portfolio)

AG-UI Protocol: Nền Tảng Giao Tiếp Thời Gian Thực Cho Đại Lý AI

Giao thức Tương tác Người dùng Đại lý (Agent User Interaction Protocol – AG-UI), được phát triển bởi CopilotKit, là một giao thức mã nguồn mở, nhẹ và dựa trên sự kiện. Mục tiêu chính của AG-UI là tạo điều kiện thuận lợi cho các tương tác phong phú, theo thời gian thực giữa giao diện người dùng (frontend) và các đại lý AI.

Trong một thế giới mà người dùng mong đợi phản hồi tức thì và trải nghiệm liền mạch, AG-UI nổi lên như một giải pháp đột phá. Nó cho phép giao tiếp dựa trên sự kiện, quản lý trạng thái hiệu quả, sử dụng công cụ linh hoạt và truyền tải phản hồi của đại lý AI một cách mượt mà. Đây là xương sống để xây dựng các ứng dụng AI có khả năng tương tác cao và năng động.

Để truyền tải thông tin giữa frontend và đại lý AI của bạn, AG-UI sử dụng các loại sự kiện khác nhau:

  • Sự kiện vòng đời (Lifecycle events): Đánh dấu sự bắt đầu hoặc kết thúc của quá trình thực thi nhiệm vụ của đại lý. Bao gồm các sự kiện RUN_STARTED (bắt đầu chạy) và RUN_FINISHED (kết thúc chạy).
  • Sự kiện tin nhắn văn bản (Text message events): Xử lý việc truyền tải phản hồi dạng văn bản từ đại lý đến frontend. Các sự kiện này bao gồm TEXT_MESSAGE_START (bắt đầu tin nhắn), TEXT_MESSAGE_CONTENT (nội dung tin nhắn) và TEXT_MESSAGE_END (kết thúc tin nhắn).
  • Sự kiện gọi công cụ (Tool call events): Quản lý việc thực thi các công cụ của đại lý. Bao gồm TOOL_CALL_START (bắt đầu gọi công cụ), TOOL_CALL_ARGS (tham số công cụ) và TOOL_CALL_END (kết thúc gọi công cụ).
  • Sự kiện quản lý trạng thái (State management events): Giúp đồng bộ trạng thái giữa frontend và đại lý AI. Bao gồm STATE_SNAPSHOT (ảnh chụp nhanh trạng thái) và STATE_DELTA (thay đổi trạng thái tăng dần).

Để tìm hiểu sâu hơn về giao thức AG-UI và kiến trúc của nó, bạn có thể tham khảo tài liệu AG-UI.

(Hình ảnh minh họa kiến trúc AG-UI Protocol)

Sau khi nắm vững các khái niệm cơ bản về AG-UI protocol, chúng ta hãy chuyển sang phần tích hợp nó với framework Agno agent.

Chuẩn Bị Nền Tảng: Các Yêu Cầu Tiên Quyết

Để có thể theo dõi hướng dẫn này một cách hiệu quả, bạn cần có kiến thức cơ bản về React hoặc Next.js. Ngoài ra, chúng ta sẽ sử dụng các công cụ và nền tảng sau:

  • Python: Ngôn ngữ lập trình phổ biến để xây dựng các đại lý AI, đặc biệt là với LangGraph. Hãy đảm bảo bạn đã cài đặt Python trên máy tính.
  • Agno: Một framework full-stack mạnh mẽ để xây dựng hệ thống đa tác nhân (Multi-Agent Systems) với khả năng ghi nhớ, kiến thức và suy luận.
  • OpenAI API Key: Khóa API cần thiết để truy cập các mô hình GPT. Đối với hướng dẫn này, hãy đảm bảo bạn có quyền truy cập vào mô hình GPT-4.
  • CopilotKit: Một framework copilot mã nguồn mở để xây dựng chatbot AI tùy chỉnh, đại lý AI trong ứng dụng và các khu vực văn bản thông minh.

Tích Hợp Đại Lý Agno Với Giao Thức AG-UI

Bây giờ, chúng ta sẽ bắt đầu quá trình tích hợp cốt lõi. Đầu tiên, hãy sao chép kho lưu trữ Open AG UI Demo, chứa một backend dựa trên Python (đại lý) và một frontend Next.js.

  1. Thiết Lập Môi Trường Backend

    Điều hướng đến thư mục backend:

    cd agent

    Cài đặt các thư viện phụ thuộc bằng Poetry:

    poetry install

    Tạo tệp .env và thêm khóa API OpenAI của bạn:

    OPENAI_API_KEY=<<your-OpenAI-key-here>>

    Chạy đại lý:

    poetry run python main.py
  2. Kiểm Tra Tích Hợp

    Để kiểm tra tích hợp AG-UI + Agno, bạn có thể gửi một lệnh curl tới API của đại lý:

    curl -X POST "http://localhost:8000/agno-agent" \
      -H "Content-Type: application/json" \
      -d '{
        "thread_id": "test_thread_123",
        "run_id": "test_run_456",
        "messages": [
          {
            "id": "msg_1",
            "role": "user",
            "content": "Analyze AAPL stock with a $10000 investment from 2023-01-01"
          }
        ],
        "tools": [],
        "context": [],
        "forwarded_props": {},
        "state": {}
      }'
  3. Bước 1: Tạo Workflow Đại Lý Agno Của Bạn

    Trước khi tích hợp AG-UI protocol, hãy định nghĩa workflow của đại lý Agno. Ví dụ về phân tích chứng khoán có thể được tìm thấy trong tệp agent/stock_analysis.py:

    # Import necessary libraries and modules for stock analysis workflow
    from agno.agent.agent import Agent  # Core agent functionality
    from agno.models.openai.chat import OpenAIChat  # OpenAI chat model integration
    from agno.workflow.v2 import Step, Workflow, StepOutput  # Workflow management components
    from ag_ui.core import EventType, StateDeltaEvent  # Event handling for UI updates
    from ag_ui.core import AssistantMessage, ToolMessage  # Message types for chat interface
    import uuid  # For generating unique identifiers
    import asyncio  # For asynchronous operations
    from openai import OpenAI  # OpenAI API client
    from dotenv import load_dotenv  # For loading environment variables
    import os  # Operating system interface
    import json  # JSON data handling
    import yfinance as yf  # Yahoo Finance API for stock data
    from datetime import datetime  # Date and time handling
    import numpy as np  # Numerical computing
    import pandas as pd  # Data manipulation and analysis
    from prompts import insights_prompt, system_prompt  # Custom prompt templates
    
    # Load environment variables from .env file (contains API keys, etc.)
    load_dotenv()
    
    # ... (các định nghĩa hàm và bước khác)
    
    # WORKFLOW DEFINITION: Complete stock analysis pipeline
    # This workflow orchestrates all the steps in sequence:
    # 1. Chat: Parse user input and extract parameters
    # 2. Simulation: Gather historical stock data
    # 3. Cash_allocation: Calculate portfolio performance and allocations
    # 4. Gather_insights: Generate market insights
    stock_analysis_workflow = Workflow(
        name="Mixed Execution Pipeline",
        steps=[chat, simultion, cash_allocation, gather_insights],  # Function
    )
    
    # ...
  4. Bước 2: Tạo Endpoint Với FastAPI

    Sau khi định nghĩa workflow Agno agent, bạn sẽ tạo một endpoint FastAPI và nhập workflow Agno agent vào, như trong tệp agent/main.py:

    # Import necessary libraries for FastAPI web server and async operations
    from fastapi import FastAPI  # Main FastAPI framework for web API
    from fastapi.responses import StreamingResponse  # For streaming real-time responses to the client
    import uuid  # For generating unique identifiers
    from typing import Any  # Type hints for better code documentation
    import os  # Operating system interface for environment variables
    import uvicorn  # ASGI server for running FastAPI applications
    import asyncio  # Asynchronous I/O operations and event loop management
    
    # Import event system components from ag_ui.core for real-time UI updates
    from ag_ui.core import (
        RunAgentInput,  # Input data structure for agent requests
        StateSnapshotEvent,  # Event for sending current state to UI
        EventType,  # Enumeration of all possible event types
        RunStartedEvent,  # Event signaling agent run has started
        RunFinishedEvent,  # Event signaling agent run has completed
        TextMessageStartEvent,  # Event for beginning text message streaming
        TextMessageEndEvent,  # Event for ending text message streaming
        TextMessageContentEvent,  # Event for streaming text content chunks
        ToolCallStartEvent,  # Event for beginning tool/function calls
        ToolCallEndEvent,  # Event for ending tool/function calls
        ToolCallArgsEvent,  # Event for streaming tool arguments
        StateDeltaEvent,  # Event for incremental state updates
    )
    
    # Import event encoder for formatting events for streaming
    from ag_ui.encoder import EventEncoder  # Encodes events for client consumption
    from typing import List  # Type hint for list types
    
    # Import the main stock analysis workflow from our custom module
    from stock_analysis import stock_analysis_workflow
    
    # Initialize FastAPI application instance
    app = FastAPI()
    
    # MAIN API ENDPOINT: Handle stock analysis agent requests
    # This endpoint receives investment queries and streams back real-time responses
    @app.post("/agno-agent")
    async def agno_agent(input_data: RunAgentInput):
    
        # ... (code implementation)
    
    # SERVER STARTUP FUNCTION: Initialize and run the FastAPI server
    def main():
        """Run the uvicorn server."""
        port = int(os.getenv("PORT", "8000"))
        uvicorn.run(
            "main:app",  # Module:app reference
            host="0.0.0.0",  # Listen on all network interfaces
            port=port,  # Port number
            reload=True,  # Auto-reload on code changes (development mode)
        )
    
    if __name__ == "__main__":
        main()  # Start the server
  5. Bước 3: Định Nghĩa Trình Tạo Sự Kiện (Event Generator)

    Tiếp theo, định nghĩa một trình tạo sự kiện tạo ra luồng sự kiện AG-UI protocol, khởi tạo bộ mã hóa sự kiện và trả về phản hồi streaming cho client/frontend. Xem trong tệp agent/main.py:

    @app.post("/agno-agent")
    async def agno_agent(input_data: RunAgentInput):
        try:
            async def event_generator():
                encoder = EventEncoder()
                event_queue = asyncio.Queue()
                def emit_event(event):
                    event_queue.put_nowait(event)
                message_id = str(uuid.uuid4())
    
                # ... (cấu hình các sự kiện khác)
    
        except Exception as e:
            print(e)
    
        return StreamingResponse(event_generator(), media_type="text/event-stream")
  6. Bước 4: Cấu Hình Sự Kiện Vòng Đời Của AG-UI Protocol

    Sau khi định nghĩa trình tạo sự kiện, hãy cấu hình các sự kiện vòng đời của AG-UI protocol, đại diện cho chu kỳ chạy của workflow AG-UI + Agno agent. Ví dụ trong tệp agent/main.py:

    @app.post("/agno-agent")
    async def agno_agent(input_data: RunAgentInput):
        try:
            async def event_generator():
                # ... (khởi tạo)
    
                # Gửi sự kiện "run started"
                yield encoder.encode(
                    RunStartedEvent(
                        type=EventType.RUN_STARTED,
                        thread_id=input_data.thread_id,
                        run_id=input_data.run_id,
                    )
                )
    
                # ... (logic workflow)
    
                # Gửi sự kiện "run finished"
                yield encoder.encode(
                    RunFinishedEvent(
                        type=EventType.RUN_FINISHED,
                        thread_id=input_data.thread_id,
                        run_id=input_data.run_id,
                    )
                )
    
        except Exception as e:
            print(e)
        return StreamingResponse(event_generator(), media_type="text/event-stream")
  7. Bước 5: Cấu Hình Sự Kiện Quản Lý Trạng Thái Của AG-UI Protocol

    Để quản lý trạng thái, tích hợp các sự kiện quản lý trạng thái AG-UI protocol sử dụng sự kiện STATE_DELTA trong các bước workflow của Agno agent, như trong agent/stock_analysis.py:

    async def chat(step_input):
        try:
            # ...
    
            # Phát sự kiện thay đổi trạng thái để cập nhật UI
            step_input.additional_data["emit_event"](
                StateDeltaEvent(
                    type=EventType.STATE_DELTA,
                    delta=[
                        {
                            "op": "add",
                            "path": "/tool_logs/-",
                            "value": {
                                "message": "Analyzing user query",
                                "status": "processing",
                                "id": tool_log_id,
                            },
                        }
                    ],
                )
            )
            await asyncio.sleep(0)
    
            # ... (các cập nhật trạng thái khác)
    
        except Exception as e:
            print(e)
            a_message = AssistantMessage(id=response.id, content="", role="assistant")
            step_input.additional_data["messages"].append(a_message)
            return "end"

    Sau đó, trong endpoint FastAPI, khởi tạo trạng thái workflow Agno agent của bạn bằng sự kiện quản lý trạng thái STATE_SNAPSHOT:

    @app.post("/agno-agent")
    async def agno_agent(input_data: RunAgentInput):
        try:
            async def event_generator():
                # ... (khởi tạo)
    
                # Gửi ảnh chụp nhanh trạng thái hiện tại
                yield encoder.encode(
                    StateSnapshotEvent(
                        type=EventType.STATE_SNAPSHOT,
                        snapshot={
                            "available_cash": input_data.state["available_cash"],
                            "investment_summary": input_data.state["investment_summary"],
                            "investment_portfolio": input_data.state["investment_portfolio"],
                            "tool_logs": [],
                        },
                    )
                )
    
                # ...
    
        except Exception as e:
            print(e)
        return StreamingResponse(event_generator(), media_type="text/event-stream")
  8. Bước 6: Cấu Hình Workflow Đại Lý Agno Với AG-UI Protocol

    Khi trạng thái workflow Agno agent đã được khởi tạo, hãy tích hợp workflow Agno agent của bạn với giao thức AG-UI, như trong tệp agent/main.py:

    @app.post("/agno-agent")
    async def agno_agent(input_data: RunAgentInput):
        try:
            async def event_generator():
                # ... (các sự kiện trước)
    
                # Bắt đầu workflow phân tích chứng khoán dưới dạng tác vụ bất đồng bộ
                agent_task = asyncio.create_task(
                        stock_analysis_workflow.arun(
                        additional_data= {
                            "tools": input_data.tools,
                            "messages": input_data.messages,
                            "emit_event": emit_event,
                            "available_cash": input_data.state["available_cash"],
                            "investment_portfolio": input_data.state["investment_portfolio"],
                            "tool_logs": [],
                        }
                    )
                )
    
                # Truyền tải các sự kiện từ workflow trong khi nó đang chạy
                while True:
                    try:
                        event = await asyncio.wait_for(event_queue.get(), timeout=0.1)
                        yield encoder.encode(event)
                    except asyncio.TimeoutError:
                        if agent_task.done():
                            break
    
                # Xóa nhật ký công cụ sau khi workflow hoàn thành
                yield encoder.encode(
                    StateDeltaEvent(
                        type=EventType.STATE_DELTA,
                        delta=[{"op": "replace", "path": "/tool_logs", "value": []}],
                    )
                )
    
                # ... (các sự kiện sau)
    
        except Exception as e:
            print(e)
        return StreamingResponse(event_generator(), media_type="text/event-stream")
  9. Bước 7: Cấu Hình Sự Kiện Công Cụ AG-UI Protocol Để Xử Lý Điểm Dừng Human-in-the-Loop (HITL)

    Để cho phép sự can thiệp của con người, hãy thêm một tin nhắn gọi công cụ với tên công cụ vào trạng thái, như trong bước phân bổ tiền mặt trong tệp agent/stock_analysis.py:

    async def cash_allocation(step_input):
        # ... (logic phân bổ)
    
        # Yêu cầu hiển thị biểu đồ thông qua gọi công cụ
        step_input.additional_data["messages"].append(
            AssistantMessage(
                role="assistant",
                tool_calls=[
                    {
                        "id": str(uuid.uuid4()),
                        "type": "function",
                        "function": {
                            "name": "render_standard_charts_and_table",
                            "arguments": json.dumps(
                                {"investment_summary": step_input.additional_data["investment_summary"]}
                            ),
                        },
                    }
                ],
                id=str(uuid.uuid4()),
            )
        )
    
        # ... (các cập nhật trạng thái)
        return

    Sau đó, định nghĩa các sự kiện gọi công cụ AG-UI protocol mà một đại lý có thể sử dụng để kích hoạt các hành động frontend bằng cách gọi hành động frontend với tên công cụ để yêu cầu phản hồi từ người dùng, như trong agent/main.py:

    @app.post("/agno-agent")
    async def agno_agent(input_data: RunAgentInput):
        try:
            async def event_generator():
                # ... (các sự kiện trước)
    
                if agent_task.result().step_responses[-1].content['messages'][-1].role == "assistant":
                    if agent_task.result().step_responses[-1].content['messages'][-1].tool_calls:
                        # Gửi sự kiện bắt đầu gọi công cụ
                        yield encoder.encode(
                            ToolCallStartEvent(
                                type=EventType.TOOL_CALL_START,
                                tool_call_id=agent_task.result().step_responses[-1].content['messages'][-1].tool_calls[0].id,
                                toolCallName=agent_task.result().step_responses[-1].content['messages'][-1]
                                .tool_calls[0]
                                .function.name,
                            )
                        )
    
                        # Gửi các tham số gọi công cụ
                        yield encoder.encode(
                            ToolCallArgsEvent(
                                type=EventType.TOOL_CALL_ARGS,
                                tool_call_id=agent_task.result().step_responses[-1].content['messages'][-1].tool_calls[0].id,
                                delta=agent_task.result().step_responses[-1].content['messages'][-1]
                                .tool_calls[0]
                                .function.arguments,
                            )
                        )
    
                        # Gửi sự kiện hoàn thành gọi công cụ
                        yield encoder.encode(
                            ToolCallEndEvent(
                                type=EventType.TOOL_CALL_END,
                                tool_call_id=agent_task.result().step_responses[-1].content['messages'][-1].tool_calls[0].id,
                            )
                        )
                    else:
                        # ... (xử lý tin nhắn văn bản)
    
                # ...
    
        except Exception as e:
            print(e)
        return StreamingResponse(event_generator(), media_type="text/event-stream")
  10. Bước 8: Cấu Hình Sự Kiện Tin Nhắn Văn Bản Của AG-UI Protocol

    Cuối cùng, định nghĩa các sự kiện tin nhắn văn bản của AG-UI protocol để xử lý việc truyền tải phản hồi từ đại lý đến frontend, như trong agent/main.py:

    @app.post("/agno-agent")
    async def agno_agent(input_data: RunAgentInput):
        try:
            async def event_generator():
                # ... (các sự kiện trước)
    
                if agent_task.result().step_responses[-1].content['messages'][-1].role == "assistant":
                    if agent_task.result().step_responses[-1].content['messages'][-1].tool_calls:
                        # ... (xử lý gọi công cụ)
                    else:
                        # Bắt đầu truyền tải tin nhắn văn bản
                        yield encoder.encode(
                            TextMessageStartEvent(
                                type=EventType.TEXT_MESSAGE_START,
                                message_id=message_id,
                                role="assistant",
                            )
                        )
    
                        # Truyền tải nội dung tin nhắn theo từng phần
                        if agent_task.result().step_responses[-1].content['messages'][-1].content:
                            content = agent_task.result().step_responses[-1].content['messages'][-1].content
                            n_parts = 100
                            part_length = max(1, len(content) // n_parts)
                            parts = [content[i : i + part_length] for i in range(0, len(content), part_length)]
                            if len(parts) > n_parts:
                                parts = parts[: n_parts - 1] + ["".join(parts[n_parts - 1:])]
    
                            for part in parts:
                                yield encoder.encode(
                                    TextMessageContentEvent(
                                        type=EventType.TEXT_MESSAGE_CONTENT,
                                        message_id=message_id,
                                        delta=part,
                                    )
                                )
                                await asyncio.sleep(0.05)
                        else:
                            yield encoder.encode(
                                TextMessageContentEvent(
                                    type=EventType.TEXT_MESSAGE_CONTENT,
                                    message_id=message_id,
                                    delta="Something went wrong! Please try again.",
                                )
                            )
    
                        # Kết thúc truyền tải tin nhắn văn bản
                        yield encoder.encode(
                            TextMessageEndEvent(
                                type=EventType.TEXT_MESSAGE_END,
                                message_id=message_id,
                            )
                        )
    
                # ...
    
        except Exception as e:
            print(e)
        return StreamingResponse(event_generator(), media_type="text/event-stream")

Chúc mừng! Bạn đã tích hợp thành công workflow của Agno agent với giao thức AG-UI. Giờ chúng ta hãy thêm một giao diện người dùng (frontend) vào workflow AG-UI + Agno agent này.

Tích Hợp Frontend Vào Workflow AG-UI + Agno Agent Bằng CopilotKit

Trong phần này, bạn sẽ học cách tạo kết nối giữa workflow AG-UI + Agno agent và giao diện người dùng (frontend) của bạn bằng CopilotKit.

Bắt đầu nào.

  1. Thiết Lập Môi Trường Frontend

    Điều hướng đến thư mục frontend:

    cd frontend

    Tạo tệp .env và thêm khóa API OpenAI của bạn:

    OPENAI_API_KEY=<<your-OpenAI-key-here>>

    Cài đặt các thư viện phụ thuộc:

    pnpm install

    Khởi động máy chủ phát triển:

    pnpm run dev

    Truy cập http://localhost:3000, bạn sẽ thấy giao diện người dùng của AG-UI + Agno agent đang hoạt động.

    (Hình ảnh minh họa giao diện frontend đã chạy)

  2. Bước 1: Tạo Một Thực Thể HttpAgent

    HttpAgent là một client từ Thư viện AG-UI, có nhiệm vụ kết nối ứng dụng frontend của bạn với bất kỳ máy chủ đại lý AI tương thích AG-UI nào. Để tạo một thực thể HttpAgent, định nghĩa nó trong một API route, như trong tệp src/app/api/copilotkit/route.ts:

    import {
      CopilotRuntime,
      copilotRuntimeNextJSAppRouterEndpoint,
      OpenAIAdapter,
    } from "@copilotkit/runtime";
    import { NextRequest } from "next/server";
    import { HttpAgent } from "@ag-ui/client";
    
    // STEP 1: Initialize HTTP Agent for Stock Analysis Backend
    const agnoAgent = new HttpAgent({
      url: process.env.NEXT_PUBLIC_AGNO_URL || "http://0.0.0.0:8000/agno-agent",
    });
    
    // STEP 2: Configure OpenAI Service Adapter
    const serviceAdapter = new OpenAIAdapter();
    
    // STEP 3: Initialize CopilotKit Runtime
    const runtime = new CopilotRuntime({
      agents: {
        // @ts-ignore
        agnoAgent: agnoAgent,
      },
    });
    
    // STEP 4: Define POST Request Handler
    export const POST = async (req: NextRequest) => {
      const { handleRequest } = copilotRuntimeNextJSAppRouterEndpoint({
        runtime,
        serviceAdapter,
        endpoint: "/api/copilotkit",
      });
      return handleRequest(req);
    };
  3. Bước 2: Thiết Lập CopilotKit Provider

    Để thiết lập CopilotKit Provider, component <CopilotKit> phải bao bọc các phần của ứng dụng có nhận biết Copilot. Trong hầu hết các trường hợp, việc bao bọc toàn bộ ứng dụng bằng CopilotKit provider trong tệp layout.tsx là phù hợp:

    import type { Metadata } from "next";
    import { Geist, Geist_Mono } from "next/font/google";
    import "./globals.css";
    import "@copilotkit/react-ui/styles.css";
    import { CopilotKit } from "@copilotkit/react-core";
    
    const geistSans = Geist({ variable: "--font-geist-sans", subsets: ["latin"], });
    const geistMono = Geist_Mono({ variable: "--font-geist-mono", subsets: ["latin"], });
    
    export const metadata: Metadata = {
      title: "AI Stock Portfolio",
      description: "AI Stock Portfolio",
    };
    
    export default function RootLayout({
      children,
    }: Readonly<{
      children: React.ReactNode;
    }>) {
      return (
        <html lang="en">
          <body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>
            {/* CopilotKit wrapper enables AI functionality throughout the app */}
            <CopilotKit runtimeUrl="/api/copilotkit" agent="agnoAgent">
              {children}
            </CopilotKit>
          </body>
        </html>
      );
    }
  4. Bước 3: Thiết Lập Component Copilot Chat

    CopilotKit cung cấp nhiều component chat tích hợp sẵn như CopilotPopup, CopilotSidebarCopilotChat. Để thiết lập một component Copilot chat, định nghĩa nó như trong tệp src/app/components/prompt-panel.tsx:

    "use client";
    
    import type React from "react";
    import { CopilotChat } from "@copilotkit/react-ui";
    
    interface PromptPanelProps {
      availableCash: number;
    }
    
    export function PromptPanel({ availableCash }: PromptPanelProps) {
      const formatCurrency = (amount: number) => {
        return new Intl.NumberFormat("en-US", {
          style: "currency",
          currency: "USD",
          minimumFractionDigits: 0,
          maximumFractionDigits: 0,
        }).format(amount);
      };
    
      return (
        <div className="h-full flex flex-col bg-white">
          <div className="p-4 border-b border-[#D8D8E5] bg-[#FAFCFA]">
            <div className="flex items-center gap-2 mb-2">
              <span className="text-xl">🪁</span>
              <div>
                <h1 className="text-lg font-semibold text-[#030507] font-['Roobert']">
                  Portfolio Chat
                </h1>
                <div className="inline-block px-2 py-0.5 bg-[#BEC9FF] text-[#030507] text-xs font-semibold uppercase rounded">
                  PRO
                </div>
              </div>
            </div>
            <p className="text-xs text-[#575758]">
              Interact with the LangGraph-powered AI agent for portfolio
              visualization and analysis
            </p>
            <div className="mt-3 p-2 bg-[#86ECE4]/10 rounded-lg">
              <div className="text-xs text-[#575758] font-medium">
                Available Cash
              </div>
              <div className="text-sm font-semibold text-[#030507] font-['Roobert']">
                {formatCurrency(availableCash)}
              </div>
            </div>
          </div>
          {/* CopilotKit chat interface with custom styling and initial message */}
          <CopilotChat
            className="h-[78vh] p-2"
            labels={{
              initial: `I am a Crew AI agent designed to analyze investment opportunities and track stock performance over time. How can I help you with your investment query? For example, you can ask me to analyze a stock like "Invest in Apple with 10k dollars since Jan 2023". \n\nNote: The AI agent has access to stock data from the past 4 years only.`
            }}
          />
        </div>
      );
    }
  5. Bước 4: Đồng Bộ Trạng Thái AG-UI + Agno Agent Với Frontend Bằng CopilotKit Hooks

    Trong CopilotKit, CoAgents duy trì một trạng thái chia sẻ kết nối liền mạch UI frontend của bạn với quá trình thực thi của đại lý. Hệ thống trạng thái chia sẻ này cho phép bạn:

    • Hiển thị tiến trình hiện tại và kết quả trung gian của đại lý.
    • Cập nhật trạng thái của đại lý thông qua tương tác UI.
    • Phản ứng với các thay đổi trạng thái theo thời gian thực trên toàn ứng dụng.

    Tìm hiểu thêm về trạng thái chia sẻ của CoAgents tại đây.

    (Hình ảnh minh họa hệ thống trạng thái chia sẻ của CopilotKit)

    Để đồng bộ trạng thái AG-UI + Agno agent với frontend, sử dụng hook useCoAgent của CopilotKit để chia sẻ trạng thái của AG-UI + Agno agent với frontend của bạn, như trong tệp src/app/page.tsx:

    "use client";
    
    import { useCoAgent } from "@copilotkit/react-core";
    import { useState } from "react";
    
    // ... (các định nghĩa interface)
    
    export default function OpenStocksCanvas() {
      const [totalCash, setTotalCash] = useState(1000000);
    
      const { state, setState } = useCoAgent({
        name: "agnoAgent",
        initialState: {
          available_cash: totalCash,
          investment_summary: {} as any,
          investment_portfolio: [] as InvestmentPortfolio[],
        },
      });
    
      // ... (logic khác)
    
      return (
        <div className="h-screen bg-[#FAFCFA] flex overflow-hidden">
           {/* ... */}
        </div>
      );
    }

    Sau đó, hiển thị trạng thái của AG-UI + Agno agent trong giao diện trò chuyện, điều này hữu ích để thông báo cho người dùng về trạng thái của đại lý một cách ngữ cảnh hơn. Bạn có thể sử dụng hook useCoAgentStateRender, như trong src/app/page.tsx:

    "use client";
    
    import { useCoAgentStateRender } from "@copilotkit/react-core";
    import { ToolLogs } from "./components/tool-logs";
    
    // ... (các định nghĩa interface)
    
    export default function OpenStocksCanvas() {
    
      // ...
    
      useCoAgentStateRender({
        name: "agnoAgent",
        render: ({ state }) => <ToolLogs logs={state.tool_logs} />,
      });
    
      // ...
    
      return (
        <div className="h-screen bg-[#FAFCFA] flex overflow-hidden">
          {/* ... */}
        </div>
      );
    }

    Nếu bạn thực hiện một truy vấn trong trò chuyện, bạn sẽ thấy việc thực thi tác vụ trạng thái của AG-UI + Agno agent được hiển thị trong giao diện trò chuyện.

    (Hình ảnh minh họa trạng thái tác vụ được hiển thị trong chat UI)

  6. Bước 5: Triển Khai Human-in-the-Loop (HITL) Trong Frontend

    Human-in-the-loop (HITL) cho phép các đại lý yêu cầu đầu vào hoặc sự chấp thuận của con người trong quá trình thực thi, làm cho các hệ thống AI đáng tin cậy và đáng tin cậy hơn. Mô hình này rất cần thiết khi xây dựng các ứng dụng AI cần xử lý các quyết định hoặc hành động phức tạp đòi hỏi sự phán đoán của con người.

    Bạn có thể tìm hiểu thêm về Human in the Loop tại đây.

    (Hình ảnh minh họa sơ đồ Human-in-the-Loop)

    Để triển khai Human-in-the-Loop (HITL) trong frontend, bạn cần sử dụng hook useCopilotAction của CopilotKit với phương thức renderAndWaitForResponse, cho phép trả về giá trị bất đồng bộ từ hàm render, như trong tệp src/app/page.tsx:

    "use client";
    
    import { useCopilotAction } from "@copilotkit/react-core";
    import { useEffect, useState } from "react";
    
    // ... (các định nghĩa interface và import components)
    
    export default function OpenStocksCanvas() {
    
      // ...
    
      useCopilotAction({
        name: "render_standard_charts_and_table",
        description:
          "This is an action to render a standard chart and table. The chart can be a bar chart or a line chart. The table can be a table of data.",
        renderAndWaitForResponse: ({ args, respond, status }) => {
          useEffect(() => {
            console.log(args, "argsargsargsargsargsaaa");
          }, [args]);
          return (
            <>
              {args?.investment_summary?.percent_allocation_per_stock &&
                args?.investment_summary?.percent_return_per_stock &&
                args?.investment_summary?.performanceData && (
                  <>
                    <div className="flex flex-col gap-4">
                      {/* ... (các component LineChartComponent, BarChartComponent, AllocationTableComponent) */}
                    </div>
    
                    <button
                      hidden={status == "complete"}
                      className="mt-4 rounded-full px-6 py-2 bg-green-50 text-green-700 border border-green-200 shadow-sm hover:bg-green-100 transition-colors font-semibold text-sm"
                      onClick={() => {
                        debugger;
                        if (respond) {
                          setTotalCash(args?.investment_summary?.cash);
                          // ... (cập nhật trạng thái)
                          respond(
                            "Data rendered successfully. Provide a summary of the investments by not making any tool calls."
                          );
                        }
                      }}>
                      Accept
                    </button>
                    <button
                      hidden={status == "complete"}
                      className="rounded-full px-6 py-2 bg-red-50 text-red-700 border border-red-200 shadow-sm hover:bg-red-100 transition-colors font-semibold text-sm ml-2"
                      onClick={() => {
                        debugger;
                        if (respond) {
                          respond(
                            "Data rendering rejected. Just give a summary of the rejected investments by not making any tool calls."
                          );
                        }
                      }}>
                      Reject
                    </button>
                  </>
                )}
            </>
          );
        },
      });
    
      // ...
    
      return (
        <div className="h-screen bg-[#FAFCFA] flex overflow-hidden">
          {/* ... */}
        </div>
      );
    }

    Khi một đại lý kích hoạt các hành động frontend bằng tên công cụ/hành động để yêu cầu đầu vào hoặc phản hồi của con người trong quá trình thực thi, người dùng cuối sẽ được nhắc nhở với một lựa chọn (được hiển thị bên trong giao diện trò chuyện). Sau đó, người dùng có thể chọn bằng cách nhấn một nút trong giao diện trò chuyện.

    (Hình ảnh minh họa giao diện Human-in-the-Loop trong chat UI)

  7. Bước 6: Truyền Tải Phản Hồi Từ AG-UI + Agno Agent Trong Frontend

    Để truyền tải phản hồi hoặc kết quả từ AG-UI + Agno agent trong frontend, hãy truyền các giá trị trường trạng thái của đại lý cho các component frontend, như trong tệp src/app/page.tsx:

    "use client";
    
    import { useEffect, useState } from "react";
    import { PromptPanel } from "./components/prompt-panel";
    import { GenerativeCanvas } from "./components/generative-canvas";
    import { ComponentTree } from "./components/component-tree";
    import { CashPanel } from "./components/cash-panel";
    
    // ... (các định nghĩa interface)
    
    export default function OpenStocksCanvas() {
      const [currentState, setCurrentState] = useState<PortfolioState>({
        id: "",
        trigger: "",
        performanceData: [],
        allocations: [],
        returnsData: [],
        bullInsights: [],
        bearInsights: [],
        currentPortfolioValue: 0,
        totalReturns: 0,
      });
      const [sandBoxPortfolio, setSandBoxPortfolio] = useState<
        SandBoxPortfolioState[]
      >([]);
      const [selectedStock, setSelectedStock] = useState<string | null>(null);
    
      return (
        <div className="h-screen bg-[#FAFCFA] flex overflow-hidden">
          {/* Left Panel - Prompt Input */}
          <div className="w-85 border-r border-[#D8D8E5] bg-white flex-shrink-0">
            <PromptPanel availableCash={totalCash} />
          </div>
    
          {/* Center Panel - Generative Canvas */}
          <div className="flex-1 relative min-w-0">
            {/* Top Bar with Cash Info */}
            <div className="absolute top-0 left-0 right-0 bg-white border-b border-[#D8D8E5] p-4 z-10">
              <CashPanel
                totalCash={totalCash}
                investedAmount={investedAmount}
                currentPortfolioValue={
                  totalCash + investedAmount + currentState.totalReturns || 0
                }
                onTotalCashChange={setTotalCash}
                onStateCashChange={setState}
              />
            </div>
    
            <div className="pt-20 h-full">
              <GenerativeCanvas
                setSelectedStock={setSelectedStock}
                portfolioState={currentState}
                sandBoxPortfolio={sandBoxPortfolio}
                setSandBoxPortfolio={setSandBoxPortfolio}
              />
            </div>
          </div>
    
          {/* Right Panel - Component Tree (Optional) */}
          {showComponentTree && (
            <div className="w-64 border-l border-[#D8D8E5] bg-white flex-shrink-0">
              <ComponentTree portfolioState={currentState} />
            </div>
          )}
        </div>
      );
    }

    Nếu bạn truy vấn đại lý của mình và chấp thuận yêu cầu phản hồi của nó, bạn sẽ thấy phản hồi hoặc kết quả của đại lý được truyền tải trong giao diện người dùng.

    (Hình ảnh minh họa phản hồi của đại lý được truyền tải trong UI)

Kết Luận

Trong hướng dẫn này, chúng ta đã cùng nhau trải qua các bước tích hợp đại lý Agno với giao thức AG-UI và sau đó bổ sung một giao diện người dùng (frontend) cho các đại lý bằng cách sử dụng CopilotKit. Đây là một hành trình thú vị mở ra cánh cửa cho việc phát triển các ứng dụng AI tương tác cao, đặc biệt trong lĩnh vực phân tích và quản lý danh mục chứng khoán.

Mặc dù chúng ta đã khám phá một vài tính năng, nhưng chúng ta mới chỉ chạm đến bề mặt của vô số trường hợp sử dụng mà CopilotKit mang lại. Từ việc xây dựng chatbot AI tương tác đến phát triển các giải pháp đại lý thông minh, CopilotKit cho phép bạn bổ sung hàng tấn khả năng AI hữu ích vào sản phẩm của mình chỉ trong vài phút. Hy vọng hướng dẫn này sẽ giúp bạn dễ dàng hơn trong việc tích hợp các Copilot được hỗ trợ bởi AI vào ứng dụng hiện có của mình.

Hãy theo dõi CopilotKit trên Twitter và tham gia cộng đồng Discord nếu bạn muốn xây dựng điều gì đó thú vị!

Xây dựng đại lý đầu tư AI chứng khoán của riêng bạn chưa bao giờ dễ dàng đến thế với Agno và AG-UI.

Chỉ mục