交易平台AI代理

基于AI的交易平台投资组合事件账本API,提供REST和MCP接口,支持自然语言查询和自动化交易管理。

  • by drewelewis
  • Python
  • 本地部署
  • 更新于
在 GitHub 查看 drewelewis/ai-agent-starter-portfolio-manager

使用说明

交易平台AI代理

一个AI驱动的投资组合事件分类账API构建 Microsoft代理框架, 快速API,以及 Azure PostgreSQL --部署到 Azure容器应用 并作为一个 MCP服务器 通过 Azure API管理.


目录

  1. 概述
  2. 建筑
  3. 项目结构
  4. Azure基础架构
  5. 环境变量
  6. API终点
  7. 数据库模式
  8. APIM MCP网关
  9. 本地开发
  10. 码头工人
  11. 助手脚本
  12. 测试

概述

交易平台AI代理提供了两个与投资组合事件分类账交互的界面:

表面 协议 URL
REST API HTTP/JSON https://<aca-host>/
MCP服务器(AI网关) MCP/流式HTTP https://ai-learning-apim.azure-api.net/trading-platform-mcp-server/mcp

REST API公开结构化查询端点和自然语言 聊天 由Azure OpenAI支持的代理支持的端点。MCP服务器(托管在Azure API管理中)使所有11个端点都可以被任何MCP兼容客户端发现和调用,这些客户端包括Claude Desktop、VS Code Copilot、自定义代理框架,而无需订阅密钥。


建筑

┌──────────────────────────────────────────────────────────┐
│  MCP Clients                                             │
│  (Claude Desktop · VS Code Copilot · Custom Agents)      │
└────────────────┬─────────────────────────────────────────┘
                 │  MCP protocol (Streamable HTTP / SSE)
                 ▼
┌──────────────────────────────────────────────────────────┐
│  Azure API Management  (BasicV2+)                        │
│  ai-learning-apim.azure-api.net                          │
│                                                          │
│  ┌─────────────────────────────────────────────────┐     │
│  │ MCP Server: trading-platform-mcp-server         │     │
│  │ path: /trading-platform-mcp-server              │     │
│  │ type: mcp   subscriptionRequired: false         │     │
│  │ 11 tools: health, agentStatus, portfolioSummary │     │
│  │   latestPrice, tradeHistory, accountEvents,     │     │
│  │   tickerEvents, chat, clearSession, insertEvent │     │
│  │   root                                          │     │
│  └──────────────────────┬──────────────────────────┘     │
│                         │ REST proxy                      │
│  ┌────────────────────┐ └──────────────────────────────┐  │
│  │ REST API           │                               │  │
│  │ trading-platform-api                               │  │
│  │ path: /  (root)                                   │  │
│  │ subscriptionRequired: false                       │  │
│  └───────────────────────────────────────────────────┘  │
└────────────────┬─────────────────────────────────────────┘
                 │  HTTPS
                 ▼
┌──────────────────────────────────────────────────────────┐
│  Azure Container Apps                                    │
│  ai-learning-aca  (East US)                              │
│  min-replicas: 1  |  ingress: external  |  port: 8989   │
│                                                          │
│  Trading Platform FastAPI (Python 3.11)                  │
│  ├── GET  /health                                        │
│  ├── GET  /                                              │
│  ├── POST /chat                      ─┐                  │
│  ├── POST /clear_session              ├─ Agent endpoints │
│  ├── GET  /portfolio/{account_id}    ─┘                  │
│  ├── GET  /portfolio/{account_id}/trades                 │
│  ├── GET  /portfolio/{account_id}/events                 │
│  ├── GET  /ticker/{ticker_symbol}/price                  │
│  ├── GET  /ticker/{ticker_symbol}/events                 │
│  ├── POST /events                                        │
│  └── GET  /agent/status                                  │
└──────┬───────────────────┬────────────────────────────────┘
       │                   │
       │ asyncpg           │ azure-identity (DefaultAzureCredential)
       ▼                   ▼
┌─────────────┐   ┌────────────────────────────────────────┐
│  Azure      │   │  Azure OpenAI / AI Foundry             │
│  PostgreSQL │   │  (direct model endpoint)               │
│  Flexible   │   │  Model: gpt-4.1 (or configured)        │
│  Server     │   │  Auth: Managed Identity                │
│  (SSL)      │   └────────────────────────────────────────┘
└─────────────┘

请求流——MCP工具调用(例如。 portfolioSummary)

Client --[MCP call_tool "portfolioSummary" {account_id: "A100"}]--> APIM
APIM   --[GET /portfolio/A100]--> Container Apps
Container Apps --[asyncpg SELECT]--> PostgreSQL
PostgreSQL --[rows]--> Container Apps
Container Apps --[JSON {account_id, positions: [...]}]--> APIM
APIM   --[MCP tool result]--> Client

请求流——通过MCP进行NL聊天

Client --[MCP call_tool "chat" {session_id, message}]--> APIM
              ↓ inbound policy reconstructs JSON body
APIM   --[POST /chat {session_id, message}]--> Container Apps
Container Apps --[agent.run(message, thread)]--> Azure OpenAI
Azure OpenAI --[tool_calls: get_portfolio_summary, ...]-->
  Container Apps executes tools against PostgreSQL
  Container Apps --[final text response]--> APIM
APIM --[MCP tool result {response: "..."}]--> Client

项目结构

.
├── main.py                          # Uvicorn entry point (port 8989)
├── chat.py                          # Interactive CLI chat client
├── requirements.txt                 # Python dependencies
├── dockerfile                       # Production container image (python:3.11-slim)
├── docker-compose.yaml              # Local stack: API + PostgreSQL + Adminer
├── env.sample                       # Template — copy to .env
│
├── api/
│   ├── main.py                      # FastAPI app — all REST endpoints
│   └── main_with_proxy.py           # Alternative with APIM proxy headers
│
├── agents/
│   └── trading_platform_agent.py   # ChatAgent definition + system prompt
│
├── operations/
│   └── trading_platform_operations.py  # asyncpg queries (connection pool, retry)
│
├── tools/
│   └── trading_platform_tool.py    # ai_function wrappers (agent-callable tools)
│
├── models/
│   └── chat_models.py              # Pydantic request/response models
│
├── data/
│   ├── ddl.sql                      # PostgreSQL schema + indexes
│   ├── portfolio_event_ledger_500.csv  # Synthetic seed data (500 rows)
│   └── portfolio_event_ledger_schema.json
│
├── tests/
│   └── test_mcp.py → (root)        # (see test_mcp.py below)
│
├── test_mcp.py                      # End-to-end MCP server test (8 tools)
│
└── infra/
    └── apim-mcp-body.json          # APIM MCP ARM body reference

Azure基础架构

所需资源

资源 SKU/Tier 备注
资源组 — ai-learning-rg
Azure容器注册表 基础+ 商店 drewl/ai-agent-starter-portfolio-manager
Azure容器应用程序环境 消费 ai-learning-aca,美国东部
Azure容器应用 (app) -- 最小副本数:0,端口8989,外部入口
Azure PostgreSQL灵活服务器 需要可爆B1ms+ SSL, portfolio_event_ledger 桌子
Azure OpenAI 或 AI铸造厂 gpt-4.1(或gpt-4o) 直接模型端点
Azure API管理 基础V2+ BasicV2最低要求——MCP服务器功能所需

重要提示: APIM MCP服务器支持(type: "mcp")要求 Basic V2或更高级别。开发人员和消费层不支持此功能。

APIM配置

APIM中配置了两个API:

APIM对象 类型 路径 身份验证
trading-platform-api 休息 / (root) 匿名
trading-platform-mcp-server MCP /trading-platform-mcp-server 匿名

MCP API创建为 type: mcp 通过Azure门户网站(API管理→ APIs → + 添加API→ MCP服务器)。它自动发现REST后端OpenAPI规范中的11个工具。

管理身份

容器应用程序使用 系统分配的管理身份 具有以下角色分配:

角色 范围 目的
Cognitive Services OpenAI User Azure OpenAI资源 调用模型端点

身份验证使用 DefaultAzureCredential --Azure中的托管身份, az login 当地。


环境变量

复制 env.sample 到 .env 并填写:

# ── Azure OpenAI (direct model endpoint) ──────────────────
AZURE_OPENAI_API_ENDPOINT=https://your-resource.openai.azure.com/
MODEL_DEPLOYMENT_NAME=gpt-4.1

# ── Azure AI Foundry (alternative — if using Foundry project endpoint) ──
AZURE_PROJECT_ENDPOINT=https://your-resource.services.ai.azure.com/api/projects/your-project

# ── Azure PostgreSQL Flexible Server ──────────────────────
POSTGRES_HOST=your-server.postgres.database.azure.com
POSTGRES_PORT=5432
POSTGRES_DB=postgres
POSTGRES_USER=your-admin-user
POSTGRES_PASSWORD=your-password
POSTGRES_SSL_MODE=require

# ── API Server ─────────────────────────────────────────────
SERVER_HOST=0.0.0.0
SERVER_PORT=8989
SERVER_RELOAD=false          # true = uvicorn --reload (dev only)
SERVICE_NAME=trading-platform-api
SERVICE_VERSION=1.0.0
SERVER_URL=                  # Public base URL for Swagger UI (e.g. https://your-aca-host)

# ── Docker ─────────────────────────────────────────────────
DOCKER_REPO_NAME=drewl/ai-agent-starter-portfolio-manager

# ── APIM MCP ───────────────────────────────────────────────
APIM_MCP_SERVER_URL=https://ai-learning-apim.azure-api.net/trading-platform-mcp-server/mcp

# ── Azure Credentials (optional — for local dev without az login) ──
AZURE_CLIENT_ID=
AZURE_CLIENT_SECRET=
AZURE_TENANT_ID=

API终点

基本URL(生产): https://ai-learning-aca.ashycliff-5cba4403.eastus.azurecontainerapps.io
交互式文档: <base-url>/docs

获取 /

服务信息和端点图。

答复:

{
  "service": "trading-platform-api",
  "version": "1.0.0",
  "docs": "/docs",
  "endpoints": { ... }
}

获取 /health

服务健康检查,包括数据库连接。

答复:

{
  "status": "healthy",
  "service": "trading-platform-api",
  "version": "1.0.0",
  "agent": "ready",
  "database": "connected",
  "framework": "Microsoft Agent Framework"
}

status 是 "healthy" 只有当代理和数据库都准备就绪时。降级为 "degraded" 否则。


发布 /chat

与交易平台代理进行自然语言聊天。保持完整的会话上下文 session_id.

请求:

{ "session_id": "user-1", "message": "Give me a portfolio summary for account A100" }

答复:

{
  "session_id": "user-1",
  "response": "Account A100 holds 175 shares of MSFT ...",
  "agent": "TradingPlatformAgent"
}

代理可以访问7个工具,并将自动调用它们来回答问题。对话历史记录保存在内存中 session_id.


发布 /clear_session

清除会话的对话历史记录。

请求:

{ "session_id": "user-1" }

答复:

{ "status": "cleared", "session_id": "user-1" }

获取 /portfolio/{account_id}

账户的净股票头寸、净成本基础和每个股票代码的最后观察价格。

例子: GET /portfolio/A100

答复:

{
  "account_id": "A100",
  "positions": [
    {
      "account_id": "A100",
      "ticker_symbol": "MSFT",
      "net_shares": 175.0,
      "net_cost": 52830.0,
      "last_price": 416.10,
      "last_event_ts": "2026-02-20T15:00:00+00:00"
    }
  ]
}

计算: net_shares = SUM(BUY shares) - SUM(SELL shares), net_cost = SUM(BUY value) - SUM(SELL value).


获取 /portfolio/{account_id}/trades

买卖账户的交易历史记录。

查询参数:

参数 类型 默认值 描述
event_type string -- 筛选条件 BUY 或 SELL
limit int 100 最大行数

例子: GET /portfolio/A100/trades?event_type=BUY&limit=5

答复:

{
  "account_id": "A100",
  "event_type": "BUY",
  "trades": [
    {
      "id": 499,
      "account_id": "A100",
      "ticker_symbol": "MSFT",
      "event_ts": "2026-02-03T07:15:00+00:00",
      "event_type": "BUY",
      "shares": 10.0,
      "price_per_share": 349.5,
      "currency": "USD",
      "source": "synthetic"
    }
  ]
}

获取 /portfolio/{account_id}/events

账户的所有分类账事件(买入、卖出、价格),最新优先。

查询参数:

参数 类型 默认值 描述
limit int 100 最大行数

答复:

{
  "account_id": "A100",
  "count": 100,
  "events": [ { ... }, ... ]
}

获取 /ticker/{ticker_symbol}/price

最近观察到的股票市场价格(最新 PRICE 事件)。

例子: GET /ticker/MSFT/price

答复:

{
  "ticker_symbol": "MSFT",
  "price_per_share": 416.10,
  "currency": "USD",
  "event_ts": "2026-02-20T15:00:00+00:00"
}

退货 404 如果自动收报机不存在价格事件。


获取 /ticker/{ticker_symbol}/events

所有账户的自动收报机的所有分类账事件,最新事件优先。

查询参数:

参数 类型 默认值 描述
limit int 100 最大行数

答复:

{
  "ticker_symbol": "MSFT",
  "count": 100,
  "events": [ { ... }, ... ]
}

发布 /events

在分类账中插入新的投资组合事件。

请求:

{
  "account_id": "A100",
  "ticker_symbol": "MSFT",
  "event_ts": "2026-02-22T10:00:00Z",
  "event_type": "BUY",
  "shares": 5.0,
  "price_per_share": 420.0,
  "currency": "USD",
  "source": "api"
}

event_type 必须 BUY, SELL,或 PRICE.使用 shares: 0 价格事件。

答复(201):

{ "status": "created", "id": 501, "account_id": "A100", ... }

获取 /agent/status

代理功能和已注册的工具列表。

答复:

{
  "agent": "TradingPlatformAgent",
  "status": "ready",
  "tools": [
    { "name": "get_events_by_account", "description": "All events for an account" },
    { "name": "get_events_by_ticker", "description": "All events for a ticker" },
    { "name": "get_portfolio_summary", "description": "Net position + cost basis per ticker" },
    { "name": "get_latest_price", "description": "Most recent PRICE observation" },
    { "name": "get_trade_history", "description": "BUY/SELL history, filterable by type" },
    { "name": "insert_trade_event", "description": "Insert a new ledger event" },
    { "name": "check_database_health", "description": "DB connectivity probe" }
  ]
}

数据库模式

表: portfolio_event_ledger

CREATE TABLE portfolio_event_ledger (
    id               BIGSERIAL       PRIMARY KEY,
    account_id       VARCHAR(64)     NOT NULL,
    ticker_symbol    VARCHAR(16)     NOT NULL,
    event_ts         TIMESTAMPTZ     NOT NULL,
    event_type       VARCHAR(8)      NOT NULL CHECK (event_type IN ('BUY', 'SELL', 'PRICE')),
    shares           NUMERIC(18, 6)  NOT NULL DEFAULT 0,
    price_per_share  NUMERIC(18, 6)  NOT NULL,
    currency         VARCHAR(8)      NOT NULL,
    source           VARCHAR(128)    NOT NULL,
    created_at       TIMESTAMPTZ     NOT NULL DEFAULT NOW()
);

列

列 类型 注释
id 双周期 自动递增PK
account_id VARCHAR(64) 例如。 A100, ACC-001
ticker_symbol VARCHAR(16) 例如。 MSFT, AAPL
event_ts 时间戳 事件发生的时间
event_type VARCHAR(8) BUY / SELL / PRICE
shares NUMERIC(18,6) 股份数量; 0 价格活动
price_per_share NUMERIC(18,6) 交易价格或市场观察
currency VARCHAR(8) ISO代码,例如。 USD
source VARCHAR(128) broker, market-feed, api, synthetic
created_at TIMESTAMPTZ 行插入时间戳

索引

-- Primary query pattern: account + time
CREATE INDEX idx_pel_account_ts      ON portfolio_event_ledger (account_id, event_ts DESC);
-- Ticker market queries
CREATE INDEX idx_pel_ticker_ts       ON portfolio_event_ledger (ticker_symbol, event_ts DESC);
-- Event type filtering (P&L)
CREATE INDEX idx_pel_event_type      ON portfolio_event_ledger (event_type);
-- Position roll-up
CREATE INDEX idx_pel_account_ticker  ON portfolio_event_ledger (account_id, ticker_symbol, event_ts DESC);

应用架构: psql -h <host> -U <user> -d postgres -f data/ddl.sql
加载种子数据: psql -h <host> -U <user> -d postgres -c "\COPY portfolio_event_ledger FROM 'data/portfolio_event_ledger_500.csv' CSV HEADER"


APIM MCP网关

端点

https://ai-learning-apim.azure-api.net/trading-platform-mcp-server/mcp

不需要API密钥(subscriptionRequired: false).

MCP工具(11)

工具 映射到 描述
health GET /health 服务+数据库健康状况
agentStatus GET /agent/status 注册工具
portfolioSummary GET /portfolio/{account_id} 净头寸
latestPrice GET /ticker/{ticker_symbol}/price 最新市场价格
tradeHistory GET /portfolio/{account_id}/trades 买卖历史
accountEvents GET /portfolio/{account_id}/events 所有帐户事件
tickerEvents GET /ticker/{ticker_symbol}/events 所有股票交易事件
chat POST /chat NL与客服聊天
clearSession POST /clear_session 清除会话历史记录
insertEvent POST /events 插入新事件
root GET / 服务信息

使用Python

import asyncio, httpx, os
from dotenv import load_dotenv
from mcp import ClientSession
from mcp.client.streamable_http import streamable_http_client

load_dotenv()
MCP_URL = os.getenv("APIM_MCP_SERVER_URL")

async def main():
    async with httpx.AsyncClient(timeout=120.0) as http:
        async with streamable_http_client(MCP_URL, http_client=http) as (read, write, _):
            async with ClientSession(read, write) as session:
                await session.initialize()
                # List tools
                tools = await session.list_tools()
                # Call a tool
                result = await session.call_tool("latestPrice", {"ticker_symbol": "MSFT"})
                print(result.content[0].text)

asyncio.run(main())

APIM MCP预览——POST正文解决方法

问题: APIM-MCP(2025-03-01-preview)正确代理GET端点的路径/查询参数,但 不 将MCP工具参数序列化为POST端点的JSON请求体。FastAPI接收一个空正文并返回HTTP 422。

受影响的工具: chat, clearSession, insertEvent

修复: 每个POST操作上的APIM入站策略都会根据APIM转发的查询参数重建JSON正文:

<policies>
  <inbound>
    <base />
    <!-- APIM MCP preview: rebuild JSON body from query params -->
    <set-body>@{
      try {
        var body = context.Request.Body?.As&lt;JObject&gt;(true);
        if (body != null &amp;&amp; body.ContainsKey("session_id")) { return body.ToString(); }
      } catch {}
      var s = context.Request.Url.Query.GetValueOrDefault("session_id", "mcp-session");
      var m = context.Request.Url.Query.GetValueOrDefault("message", "");
      return new JObject(
        new JProperty("session_id", s),
        new JProperty("message", m)
      ).ToString();
    }</set-body>
    <set-header name="Content-Type" exists-action="override">
      <value>application/json</value>
    </set-header>
  </inbound>
  <backend><base /></backend>
  <outbound><base /></outbound>
  <on-error><base /></on-error>
</policies>

通过以下方式部署:

# Build policy_body.json: { "properties": { "value": "<policies>...</policies>", "format": "xml" } }
az rest --method PUT \
  --uri "https://management.azure.com/subscriptions/{subId}/resourceGroups/ai-learning-rg/providers/Microsoft.ApiManagement/service/ai-learning-apim/apis/trading-platform-api/operations/chat_chat_post/policies/policy?api-version=2022-08-01" \
  --body "@policy_body.json" \
  --headers "Content-Type=application/json"

本地开发

先决条件

  • Python 3.11+
  • Azure命令行界面(az login)
  • 访问Azure OpenAI或AI Foundry(AZURE_OPENAI_API_ENDPOINT 或 AZURE_PROJECT_ENDPOINT)
  • PostgreSQL连接(Azure或本地Docker)

设置

# 1. Create and activate virtual environment
_env_create.bat
_env_activate.bat

# 2. Install dependencies
_install.bat

# 3. Copy and fill in environment variables
copy env.sample .env
# Edit .env with your values

# 4. Start the API server locally
python main.py
# → http://localhost:8989/docs

交互式CLI聊天

python chat.py

呈现一个REPL,用于向以下对象发送消息 POST /chat 并打印响应。


码头工人

构建与运行

# Build image
_build.bat

# Start full stack (API + PostgreSQL + Adminer)
_up.bat

# View logs
_logs.bat

# Stop
_down.bat

服务开始于 _up.bat:

服务 端口 描述
ai-agent-starter-api 8989 交易平台API
ai-agent-starter-portfolio-manager-postgres 5000→5432 PostgreSQL 17
ai-agent-starter-portfolio-manager-adminer 8888 管理员数据库用户界面

推送到注册表

_push.bat
# Pushes image to DOCKER_REPO_NAME defined in .env

助手脚本

脚本 动作
_env_create.bat python -m venv .venv
_env_activate.bat 激活 .venv
_env_deactivate.bat 停用 .venv
_install.bat pip install -r requirements.txt
_build.bat docker build
_up.bat docker compose up -d
_down.bat docker compose down
_logs.bat docker compose logs -f
_push.bat docker push 到注册表

测试

MCP端到端测试

针对实时APIM端点测试所有8个核心MCP工具:

python test_mcp.py

预期产量:

============================================================
  Trading Platform MCP Server Test
  URL: https://ai-learning-apim.azure-api.net/trading-platform-mcp-server/mcp
============================================================
  Connected — MCP session initialized

MCP tools advertised (11): health, agentStatus, chat, ...

[health]           ✅  healthy
[agentStatus]      ✅  7 tools
[portfolioSummary] ✅  1 ticker(s)
[latestPrice]      ✅  MSFT @ 416.10
[tradeHistory]     ✅  67 trades
[accountEvents]    ✅  100 events
[tickerEvents]     ✅  100 events
[chat]             ✅  Agent replied (212 chars)
------------------------------------------------------------
  8/8 passed

需要 APIM_MCP_SERVER_URL 在 .env.

REST API(手动)

# Health
curl https://ai-learning-aca.ashycliff-5cba4403.eastus.azurecontainerapps.io/health

# Portfolio summary
curl https://ai-learning-aca.ashycliff-5cba4403.eastus.azurecontainerapps.io/portfolio/A100

# Latest price
curl https://ai-learning-aca.ashycliff-5cba4403.eastus.azurecontainerapps.io/ticker/MSFT/price

# NL chat
curl -X POST https://ai-learning-aca.ashycliff-5cba4403.eastus.azurecontainerapps.io/chat \
  -H "Content-Type: application/json" \
  -d '{"session_id": "test", "message": "Summarize portfolio A100"}'

许可证

本项目根据 许可证 文件。