Skip to content

Commit 549a8bb

Browse files
authored
Merge pull request #29 from ks6088ts-labs/copilot/fix-28
Agents サービスを FastAPI サーバに追加する
2 parents 5d9f27c + bb6c4b2 commit 549a8bb

File tree

10 files changed

+718
-4
lines changed

10 files changed

+718
-4
lines changed

.env.template

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ AZURE_BLOB_STORAGE_CONTAINER_NAME="files"
1818
AZURE_AI_SPEECH_API_KEY="<YOUR_AZURE_AI_SPEECH_API_KEY>"
1919
AZURE_AI_SPEECH_ENDPOINT="https://<speech-api-name>.cognitiveservices.azure.com/"
2020

21+
# Azure AI Foundry
22+
AZURE_AI_FOUNDRY_PROJECT_ENDPOINT="https://xxx.services.ai.azure.com/api/projects/yyy"
23+
AZURE_AI_FOUNDRY_API_KEY="<YOUR_API_KEY>"
24+
2125
# Chats WebSocket
2226
# Azure Container Apps: `wss://yourcontainerapps.japaneast.azurecontainerapps.io`
2327
CHATS_WEBSOCKET_URL="ws://localhost:8000"

docs/index.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,3 +194,27 @@ az resource update \
194194
### Azure AI Speech
195195

196196
- [バッチ文字起こしとは](https://learn.microsoft.com/ja-jp/azure/ai-services/speech-service/batch-transcription)
197+
198+
### Azure AI Foundry
199+
200+
- [Quickstart: Create a new agent](https://learn.microsoft.com/en-us/azure/ai-foundry/agents/quickstart?pivots=programming-language-python-azure)
201+
- [Deep Research tool (preview)](https://learn.microsoft.com/en-us/azure/ai-foundry/agents/quickstart?pivots=programming-language-python-azure)
202+
203+
#### CLI 実行例
204+
205+
```bash
206+
# エージェントを作成
207+
python scripts/agents.py create-agent "研究アシスタント" --description "研究をサポートするAIアシスタント" --instructions "あなたは研究者をサポートするAIアシスタントです。質問に対して詳細で正確な回答を提供してください。"
208+
209+
# エージェント一覧を取得
210+
python scripts/agents.py list-agents
211+
212+
# エージェントの詳細を取得
213+
python scripts/agents.py get-agent <agent_id>
214+
215+
# エージェントとチャット
216+
python scripts/agents.py chat <agent_id> "機械学習の最新トレンドについて教えてください"
217+
218+
# エージェントを削除
219+
python scripts/agents.py delete-agent <agent_id>
220+
```

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ description = "A GitHub template repository for Python"
55
readme = "README.md"
66
requires-python = ">=3.10"
77
dependencies = [
8+
"azure-ai-projects>=1.0.0b12",
89
"azure-cosmos>=4.9.0",
910
"azure-functions>=1.23.0",
1011
"azure-identity>=1.23.0",
@@ -70,3 +71,4 @@ possibly-unbound-attribute = "ignore" # Ignore possibly unbound attributes in cl
7071
unknown-argument = "ignore" # Ignore unknown arguments in function calls
7172
invalid-assignment = "ignore" # Ignore invalid assignments
7273
invalid-argument-type = "ignore" # Ignore invalid argument types
74+
invalid-return-type = "ignore" # Ignore invalid return types

scripts/agents.py

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
#!/usr/bin/env python
2+
# filepath: /home/runner/work/template-fastapi/template-fastapi/scripts/agents.py
3+
4+
import json
5+
6+
import typer
7+
from rich.console import Console
8+
from rich.table import Table
9+
10+
from template_fastapi.models.agent import AgentRequest, ChatRequest
11+
from template_fastapi.repositories.agents import AgentRepository
12+
13+
app = typer.Typer()
14+
console = Console()
15+
agent_repo = AgentRepository()
16+
17+
18+
@app.command()
19+
def create_agent(
20+
name: str = typer.Argument(..., help="エージェント名"),
21+
description: str | None = typer.Option(None, "--description", "-d", help="エージェントの説明"),
22+
instructions: str | None = typer.Option(None, "--instructions", "-i", help="エージェントの指示"),
23+
model: str = typer.Option("gpt-4o", "--model", "-m", help="使用するモデル"),
24+
):
25+
"""新しいエージェントを作成する"""
26+
console.print("[bold green]新しいエージェントを作成します[/bold green]")
27+
console.print(f"名前: {name}")
28+
console.print(f"説明: {description or 'なし'}")
29+
console.print(f"指示: {instructions or 'なし'}")
30+
console.print(f"モデル: {model}")
31+
32+
try:
33+
request = AgentRequest(
34+
name=name,
35+
description=description,
36+
instructions=instructions,
37+
model=model,
38+
)
39+
40+
agent = agent_repo.create_agent(request)
41+
42+
console.print("\n[bold blue]エージェントが正常に作成されました[/bold blue]:")
43+
console.print(f"ID: {agent.id}")
44+
console.print(f"名前: {agent.name}")
45+
console.print(f"説明: {agent.description}")
46+
console.print(f"指示: {agent.instructions}")
47+
console.print(f"モデル: {agent.model}")
48+
console.print(f"ステータス: {agent.status}")
49+
console.print(f"作成日時: {agent.created_at}")
50+
51+
except Exception as e:
52+
console.print(f"❌ [bold red]エラー[/bold red]: {str(e)}")
53+
54+
55+
@app.command()
56+
def get_agent(
57+
agent_id: str = typer.Argument(..., help="エージェントID"),
58+
):
59+
"""エージェントの情報を取得する"""
60+
console.print("[bold green]エージェント情報を取得します[/bold green]")
61+
console.print(f"エージェントID: {agent_id}")
62+
63+
try:
64+
agent = agent_repo.get_agent(agent_id)
65+
66+
console.print("\n[bold blue]エージェント情報[/bold blue]:")
67+
console.print(f"ID: {agent.id}")
68+
console.print(f"名前: {agent.name}")
69+
console.print(f"説明: {agent.description}")
70+
console.print(f"指示: {agent.instructions}")
71+
console.print(f"モデル: {agent.model}")
72+
console.print(f"ステータス: {agent.status}")
73+
console.print(f"作成日時: {agent.created_at}")
74+
console.print(f"更新日時: {agent.updated_at}")
75+
76+
if agent.tools:
77+
console.print(f"ツール: {json.dumps(agent.tools, indent=2, ensure_ascii=False)}")
78+
79+
except Exception as e:
80+
console.print(f"❌ [bold red]エラー[/bold red]: {str(e)}")
81+
82+
83+
@app.command()
84+
def list_agents(
85+
limit: int = typer.Option(10, "--limit", "-l", help="取得する件数"),
86+
):
87+
"""エージェントの一覧を取得する"""
88+
console.print("[bold green]エージェント一覧を取得します[/bold green]")
89+
console.print(f"取得件数: {limit}")
90+
91+
try:
92+
agents_response = agent_repo.list_agents(limit=limit)
93+
94+
if not agents_response.agents:
95+
console.print("[yellow]エージェントが見つかりません[/yellow]")
96+
return
97+
98+
table = Table(title="エージェント一覧")
99+
table.add_column("ID", style="cyan")
100+
table.add_column("名前", style="magenta")
101+
table.add_column("説明", style="green")
102+
table.add_column("モデル", style="blue")
103+
table.add_column("ステータス", style="yellow")
104+
table.add_column("作成日時", style="dim")
105+
106+
for agent in agents_response.agents:
107+
table.add_row(
108+
agent.id,
109+
agent.name,
110+
agent.description or "なし",
111+
agent.model,
112+
agent.status,
113+
agent.created_at,
114+
)
115+
116+
console.print(table)
117+
console.print(f"[bold blue]合計: {agents_response.total}件[/bold blue]")
118+
119+
except Exception as e:
120+
console.print(f"❌ [bold red]エラー[/bold red]: {str(e)}")
121+
122+
123+
@app.command()
124+
def delete_agent(
125+
agent_id: str = typer.Argument(..., help="エージェントID"),
126+
):
127+
"""エージェントを削除する"""
128+
console.print("[bold red]エージェントを削除します[/bold red]")
129+
console.print(f"エージェントID: {agent_id}")
130+
131+
confirm = typer.confirm("本当に削除しますか?")
132+
if not confirm:
133+
console.print("[yellow]削除をキャンセルしました[/yellow]")
134+
return
135+
136+
try:
137+
success = agent_repo.delete_agent(agent_id)
138+
139+
if success:
140+
console.print("[bold green]エージェントが正常に削除されました[/bold green]")
141+
else:
142+
console.print("[bold red]エージェントの削除に失敗しました[/bold red]")
143+
144+
except Exception as e:
145+
console.print(f"❌ [bold red]エラー[/bold red]: {str(e)}")
146+
147+
148+
@app.command()
149+
def chat(
150+
agent_id: str = typer.Argument(..., help="エージェントID"),
151+
message: str = typer.Argument(..., help="メッセージ"),
152+
thread_id: str | None = typer.Option(None, "--thread-id", "-t", help="スレッドID"),
153+
):
154+
"""エージェントとチャットする"""
155+
console.print("[bold green]エージェントとチャットします[/bold green]")
156+
console.print(f"エージェントID: {agent_id}")
157+
console.print(f"メッセージ: {message}")
158+
console.print(f"スレッドID: {thread_id or '新規作成'}")
159+
160+
try:
161+
request = ChatRequest(
162+
message=message,
163+
thread_id=thread_id,
164+
)
165+
166+
response = agent_repo.chat_with_agent(agent_id, request)
167+
168+
console.print("\n[bold blue]チャット結果[/bold blue]:")
169+
console.print(f"メッセージID: {response.id}")
170+
console.print(f"スレッドID: {response.thread_id}")
171+
console.print(f"あなた: {response.message}")
172+
console.print(f"エージェント: {response.response}")
173+
console.print(f"作成日時: {response.created_at}")
174+
175+
except Exception as e:
176+
console.print(f"❌ [bold red]エラー[/bold red]: {str(e)}")
177+
178+
179+
if __name__ == "__main__":
180+
app()

template_fastapi/app.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
1212
from opentelemetry.trace import Span
1313

14-
from template_fastapi.routers import chats, demos, files, foodies, games, items, speeches
14+
from template_fastapi.routers import agents, chats, demos, files, foodies, games, items, speeches
1515

1616
app = FastAPI()
1717

@@ -43,3 +43,4 @@ def server_request_hook(span: Span, scope: dict):
4343
app.include_router(files.router)
4444
app.include_router(speeches.router)
4545
app.include_router(chats.router)
46+
app.include_router(agents.router)

template_fastapi/models/agent.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
from enum import Enum
2+
from typing import Any
3+
4+
from pydantic import BaseModel
5+
6+
7+
class AgentStatus(str, Enum):
8+
"""Agent status enumeration"""
9+
10+
ACTIVE = "active"
11+
INACTIVE = "inactive"
12+
DELETED = "deleted"
13+
14+
15+
class AgentRequest(BaseModel):
16+
"""Request model for creating an agent"""
17+
18+
name: str = "Default Agent"
19+
description: str | None = "Hello Agent"
20+
instructions: str | None = "You are a helpful assistant."
21+
model: str = "gpt-4o"
22+
23+
24+
class AgentResponse(BaseModel):
25+
"""Response model for agent operations"""
26+
27+
id: str
28+
name: str
29+
description: str | None = None
30+
instructions: str | None = None
31+
model: str
32+
tools: list[dict[str, Any]] | None = None
33+
status: AgentStatus
34+
created_at: str
35+
updated_at: str
36+
37+
38+
class ThreadRequest(BaseModel):
39+
"""Request model for creating a chat thread"""
40+
41+
pass
42+
43+
44+
class ThreadResponse(BaseModel):
45+
"""Response model for chat thread"""
46+
47+
id: str
48+
created_at: str
49+
50+
51+
class ChatRequest(BaseModel):
52+
"""Request model for chat with agent"""
53+
54+
message: str
55+
thread_id: str | None = None
56+
57+
58+
class ChatResponse(BaseModel):
59+
"""Response model for chat with agent"""
60+
61+
id: str
62+
agent_id: str
63+
thread_id: str
64+
message: str
65+
response: str
66+
created_at: str
67+
68+
69+
class AgentListResponse(BaseModel):
70+
"""Response model for listing agents"""
71+
72+
agents: list[AgentResponse]
73+
total: int
74+
75+
76+
class ThreadListResponse(BaseModel):
77+
"""Response model for listing threads"""
78+
79+
threads: list[ThreadResponse]
80+
total: int

0 commit comments

Comments
 (0)