Skip to content

Commit c415dac

Browse files
authored
Merge pull request #14 from ks6088ts-labs/feature/issue-13_cosmosdb
add foodies service
2 parents 0a6ec51 + 85fbf41 commit c415dac

File tree

14 files changed

+1887
-19
lines changed

14 files changed

+1887
-19
lines changed

.env.template

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Azure OpenAI Service
2+
AZURE_OPENAI_ENDPOINT="https://<YOUR_AOAI_NAME>.openai.azure.com/"
3+
AZURE_OPENAI_API_KEY="<YOUR_API_KEY>"
4+
AZURE_OPENAI_API_VERSION="2024-10-21"
5+
AZURE_OPENAI_MODEL_CHAT="gpt-4o"
6+
AZURE_OPENAI_MODEL_EMBEDDING="text-embedding-3-small"
7+
8+
# Azure CosmosDB
9+
AZURE_COSMOSDB_CONNECTION_STRING="AccountEndpoint=https://<YOUR_COSMOSDB_NAME>.documents.azure.com:443/;AccountKey=<ACCOUNT_KEY>;"
10+
AZURE_COSMOSDB_DATABASE_NAME="template_fastapi"
11+
AZURE_COSMOSDB_CONTAINER_NAME="items"

.vscode/mcp.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"servers": {
3+
// For debugging purpose, add a local MCP server
4+
"template_fastapi": {
5+
"type": "http",
6+
"url": "http://localhost:8000/mcp"
7+
}
8+
}
9+
}

datasets/foodies_restaurants.csv

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
id,name,description,price,latitude,longitude,tags
2+
1,さくら寿司,伝統的な日本の寿司レストラン。新鮮な魚介類を提供。,3500,35.6812,139.7671,"寿司,日本料理,高級"
3+
2,ベルラーメン,本格的な豚骨ラーメンが自慢のお店。,950,35.6598,139.7003,"ラーメン,豚骨,カジュアル"
4+
3,オステリアヴェルデ,イタリア人シェフが作る本場のパスタとピザ。,2800,35.6646,139.7139,"イタリアン,ピザ,パスタ"
5+
4,北京飯店,北京ダックが名物の老舗中華料理店。,3200,35.6941,139.7022,"中華料理,北京ダック,家族向け"
6+
5,タイランドキッチン,本格的なタイ料理を提供するアットホームな空間。,1800,35.6594,139.7005,"タイ料理,スパイシー,エスニック"
7+
6,マドリード,スペイン直送の生ハムとワインが楽しめる。,4000,35.6719,139.7137,"スペイン料理,タパス,ワイン"
8+
7,コリアンBBQ,自分で焼く韓国式バーベキューレストラン。,2500,35.6586,139.6983,"韓国料理,焼肉,グループ向け"
9+
8,ハンバーガーパラダイス,手作りパティのジューシーなハンバーガー。,1200,35.6628,139.6903,"ハンバーガー,アメリカン,カジュアル"
10+
9,シーフードマーケット,その日に水揚げされた新鮮な海鮮料理。,3800,35.6684,139.7727,"シーフード,魚介類,新鮮"
11+
10,カレーハウス,20種類以上のスパイスを使った本格カレー。,1100,35.6517,139.7093,"カレー,インド料理,ベジタリアン"
12+
11,ステーキハウスオークス,厳選された国産牛のステーキ。,5500,35.6731,139.7639,"ステーキ,高級,デート向け"
13+
12,メキシカンフィエスタ,カラフルで賑やかなメキシコ料理レストラン。,2200,35.6598,139.7273,"メキシコ料理,テキーラ,陽気"
14+
13,フレンチビストロ,カジュアルに楽しめるフランス料理。,3300,35.6656,139.7362,"フランス料理,ワイン,おしゃれ"
15+
14,ベトナムフォー,あっさりとした牛出汁のフォーが人気。,1050,35.6890,139.6917,"ベトナム料理,フォー,ヘルシー"
16+
15,地中海レストラン,ギリシャとトルコの伝統料理を提供。,2900,35.6808,139.7082,"地中海料理,健康的,オリーブオイル"
17+
16,寿司バー匠,カウンターで楽しむ江戸前寿司。,4500,35.6701,139.7627,"寿司,伝統,職人技"
18+
17,アジアンフュージョン,アジア各国の料理を現代風にアレンジ。,2400,35.6593,139.7029,"アジア料理,フュージョン,モダン"
19+
18,山の手うどん,手打ちうどんと季節の天ぷらが自慢。,1300,35.7147,139.7027,"うどん,和食,老舗"
20+
19,BBQリブス,アメリカ南部スタイルのバーベキューリブ。,2700,35.6627,139.7282,"BBQ,アメリカ料理,肉料理"
21+
20,パンケーキカフェ,ふわふわパンケーキとオーガニックコーヒー。,1600,35.6624,139.6698,"パンケーキ,カフェ,朝食"
22+
21,海鮮居酒屋,新鮮な海鮮料理と日本酒が楽しめる居酒屋。,3000,35.6945,139.6952,"居酒屋,海鮮,日本酒"
23+
22,ベーカリーカフェ,焼きたてのパンとこだわりのコーヒー。,950,35.6541,139.7353,"パン,カフェ,朝食"
24+
23,ベジタリアンガーデン,地元の有機野菜を使ったベジタリアン料理。,1800,35.6735,139.6990,"ベジタリアン,オーガニック,健康"
25+
24,火鍋専門店,しゃぶしゃぶ風の中国の鍋料理を提供。,2600,35.6937,139.7035,"火鍋,中華料理,グループ向け"
26+
25,ジェラート工房,イタリア直伝の手作りジェラート。,800,35.6634,139.7336,"ジェラート,デザート,イタリアン"
27+
26,串焼き専門店,炭火で焼く本格的な串焼き。,2200,35.6553,139.7384,"串焼き,居酒屋,日本酒"
28+
27,インドカレーハウス,南インド料理を中心としたスパイシーな料理。,1400,35.6713,139.7026,"インド料理,カレー,ナン"
29+
28,オイスターバー,生牡蠣と白ワインを楽しむ大人の空間。,3600,35.6697,139.7636,"牡蠣,シーフード,バー"
30+
29,ラテンアメリカン,ブラジル、ペルーなど南米料理のフュージョン。,2500,35.6645,139.7057,"南米料理,サンバ,情熱的"
31+
30,抹茶カフェ,抹茶スイーツと和のデザートが人気。,1200,35.6680,139.7148,"抹茶,カフェ,和スイーツ"

docs/index.md

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,49 @@
11
# template-fastapi
22

3+
## MCP
4+
5+
- [FastAPI-MCP](https://github.com/tadata-org/fastapi_mcp)
6+
- [FastAPI で作成した API エンドポイントをモデル コンテキスト プロトコル (MCP) ツールとして公開してみる](https://dev.classmethod.jp/articles/fastapi-api-mcp/)
7+
- [MCP Inspector > docs](https://modelcontextprotocol.io/docs/tools/inspector)
8+
- [MCP Inspector > codes](https://github.com/modelcontextprotocol/inspector)
9+
310
## FastAPI
411

512
- [FastAPI](https://fastapi.tiangolo.com/)
13+
- [Settings and Environment Variables](https://fastapi.tiangolo.com/advanced/settings/)
14+
15+
## OpenTelemetry
16+
17+
- [Docs > Language APIs & SDKs > Python > Getting Started](https://opentelemetry.io/docs/languages/python/getting-started/)
618

7-
## Azure Functions
19+
## Azure
20+
21+
### Azure Functions
822

923
- [Azure Functions で OpenTelemetry を使用する](https://learn.microsoft.com/ja-jp/azure/azure-functions/opentelemetry-howto?tabs=app-insights&pivots=programming-language-python)
1024
- [Using FastAPI Framework with Azure Functions](https://learn.microsoft.com/en-us/samples/azure-samples/fastapi-on-azure-functions/fastapi-on-azure-functions/)
1125
- [ks6088ts-labs/azure-functions-python](https://github.com/ks6088ts-labs/azure-functions-python)
1226

13-
## MCP
27+
### Azure Cosmos DB
1428

15-
- [FastAPI-MCP](https://github.com/tadata-org/fastapi_mcp)
16-
- [FastAPI で作成した API エンドポイントをモデル コンテキスト プロトコル (MCP) ツールとして公開してみる](https://dev.classmethod.jp/articles/fastapi-api-mcp/)
17-
- [MCP Inspector > docs](https://modelcontextprotocol.io/docs/tools/inspector)
18-
- [MCP Inspector > codes](https://github.com/modelcontextprotocol/inspector)
29+
- [VectorDistance() を使用したクエリによるベクトル検索を実行する](https://learn.microsoft.com/ja-jp/azure/cosmos-db/nosql/vector-search#perform-vector-search-with-queries-using-vectordistance)
30+
- [LangChain / Azure Cosmos DB No SQL](https://python.langchain.com/docs/integrations/vectorstores/azure_cosmos_db_no_sql/)
1931

20-
## Application Insights
32+
```shell
33+
# Azure Cosmos DB のローカル認証を有効にする
34+
# (注意: この操作はセキュリティ上のリスクを伴うため、開発環境でのみ使用してください。)
35+
az resource update \
36+
--resource-group $RESOURCE_GROUP \
37+
--name $COSMOS_DB_ACCOUNT_NAME \
38+
--resource-type "Microsoft.DocumentDB/databaseAccounts" \
39+
--set properties.disableLocalAuth=false
40+
```
41+
42+
### Application Insights
2143

2244
- [Application Insights の概要 - OpenTelemetry の可観測性](https://learn.microsoft.com/ja-jp/azure/azure-monitor/app/app-insights-overview)
2345
- [Azure Monitor OpenTelemetry を設定する](https://learn.microsoft.com/ja-jp/azure/azure-monitor/app/opentelemetry-configuration?tabs=python)
2446
- [open-telemetry/opentelemetry-python > Basic Trace](https://github.com/open-telemetry/opentelemetry-python/tree/main/docs/examples/basic_tracer)
2547
- [FastAPI のテレメトリデータを Azure Application Insights に送る](https://qiita.com/hoto17296/items/2f366dfabdbe3d1d4e97)
2648
- [【Azure Functions】 - Application Insights のログが表示されない問題](https://zenn.dev/headwaters/articles/ff19f7e1b99b44)
2749
- [opentelemetry-instrumentation-fastapi (python) から OpenTelemetry に入門する](https://zenn.dev/taxin/articles/opentelemetry-fast-api-instrumentation-basics)
28-
29-
## OpenTelemetry
30-
31-
- [Docs > Language APIs & SDKs > Python > Getting Started](https://opentelemetry.io/docs/languages/python/getting-started/)

pyproject.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,16 @@ description = "A GitHub template repository for Python"
55
readme = "README.md"
66
requires-python = ">=3.10"
77
dependencies = [
8+
"azure-cosmos>=4.9.0",
89
"azure-functions>=1.23.0",
910
"azure-monitor-opentelemetry>=1.6.10",
1011
"fastapi-mcp>=0.3.4",
1112
"fastapi[standard]>=0.115.12",
13+
"langchain-community>=0.3.27",
14+
"langchain-openai>=0.3.27",
1215
"opentelemetry-instrumentation-fastapi>=0.52b1",
16+
"pydantic-settings>=2.10.1",
17+
"typer>=0.16.0",
1318
]
1419

1520
[project.optional-dependencies]
@@ -58,3 +63,6 @@ environment = { python-version = "3.10" }
5863
[tool.ty.rules]
5964
unresolved-attribute = "ignore" # Ignore unresolved attributes in classes
6065
possibly-unbound-attribute = "ignore" # Ignore possibly unbound attributes in classes
66+
unknown-argument = "ignore" # Ignore unknown arguments in function calls
67+
invalid-assignment = "ignore" # Ignore invalid assignments
68+
invalid-argument-type = "ignore" # Ignore invalid argument types

scripts/foodies_restaurants.py

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
#!/usr/bin/env python
2+
# filepath: /Users/ks6088ts/src/github.com/ks6088ts-labs/template-fastapi/scripts/foodies_restaurant.py
3+
4+
import csv
5+
6+
import typer
7+
from azure.cosmos import CosmosClient, PartitionKey
8+
from langchain_core.documents import Document
9+
from langchain_openai import AzureOpenAIEmbeddings
10+
from rich.console import Console
11+
12+
from template_fastapi.settings.azure_cosmosdb import get_azure_cosmosdb_settings
13+
from template_fastapi.settings.azure_openai import get_azure_openai_settings
14+
15+
app = typer.Typer()
16+
console = Console()
17+
18+
19+
def read_csv_data(file_path: str) -> list[dict]:
20+
"""CSVファイルからレストランデータを読み込む"""
21+
restaurants = []
22+
with open(file_path, encoding="utf-8") as csvfile:
23+
csv_reader = csv.DictReader(csvfile)
24+
for row in csv_reader:
25+
# タグをリストに変換
26+
tags = row["tags"].strip('"').split(",")
27+
restaurant = {
28+
"id": row["id"],
29+
"name": row["name"],
30+
"description": row["description"],
31+
"price": int(row["price"]),
32+
"location": {
33+
"type": "Point",
34+
"coordinates": [float(row["longitude"]), float(row["latitude"])],
35+
},
36+
"tags": tags,
37+
}
38+
restaurants.append(restaurant)
39+
return restaurants
40+
41+
42+
def get_embeddings(texts: list[str]) -> list[list[float]]:
43+
"""Azure OpenAIを使用してテキストのベクトル埋め込みを生成する"""
44+
settings = get_azure_openai_settings()
45+
embedding_model = AzureOpenAIEmbeddings(
46+
azure_endpoint=settings.azure_openai_endpoint,
47+
api_key=settings.azure_openai_api_key,
48+
azure_deployment=settings.azure_openai_model_embedding,
49+
api_version=settings.azure_openai_api_version,
50+
)
51+
52+
documents = [Document(page_content=text) for text in texts]
53+
embeddings = embedding_model.embed_documents([doc.page_content for doc in documents])
54+
return embeddings
55+
56+
57+
def setup_cosmos_db():
58+
"""Azure Cosmos DBのセットアップと接続"""
59+
settings = get_azure_cosmosdb_settings()
60+
client = CosmosClient.from_connection_string(settings.azure_cosmosdb_connection_string)
61+
62+
# データベースが存在しなければ作成
63+
db = client.create_database_if_not_exists(id=settings.azure_cosmosdb_database_name)
64+
65+
# コンテナが存在しなければ作成(ベクトル検索用インデックスポリシー付き)
66+
indexing_policy = {
67+
"indexingMode": "consistent",
68+
"includedPaths": [{"path": "/*"}],
69+
"excludedPaths": [{"path": "/vector/*"}],
70+
"vectorIndexes": [
71+
{
72+
"path": "/vector",
73+
"type": "cosine",
74+
"numDimensions": 1536, # OpenAI Embedding modelのデフォルトサイズ
75+
"vectorSearchConfiguration": "vectorConfig",
76+
}
77+
],
78+
}
79+
80+
container = db.create_container_if_not_exists(
81+
id=settings.azure_cosmosdb_container_name,
82+
partition_key=PartitionKey(path="/id"),
83+
indexing_policy=indexing_policy,
84+
)
85+
86+
return container
87+
88+
89+
@app.command()
90+
def import_data(
91+
csv_file: str = typer.Option(..., "--csv-file", "-f", help="CSVファイルのパス"),
92+
batch_size: int = typer.Option(100, "--batch-size", "-b", help="一度に処理するバッチサイズ"),
93+
):
94+
"""CSVからデータをCosmosDBにインポートしてベクトル検索を設定する"""
95+
console.print(f"[bold green]CSVファイル[/bold green]: {csv_file}からデータを読み込みます")
96+
97+
# CSVデータの読み込み
98+
restaurants = read_csv_data(csv_file)
99+
console.print(f"[bold blue]{len(restaurants)}件[/bold blue]のレストランデータを読み込みました")
100+
101+
# 説明文を抽出してベクトル埋め込みを生成
102+
descriptions = [restaurant["description"] for restaurant in restaurants]
103+
console.print("[bold yellow]説明文のベクトル埋め込みを生成しています...[/bold yellow]")
104+
embeddings = get_embeddings(descriptions)
105+
106+
# Cosmos DBのセットアップ
107+
container = setup_cosmos_db()
108+
console.print(f"[bold green]Cosmos DBのコンテナ[/bold green]: {container.id}に接続しました")
109+
110+
# データの登録
111+
console.print("[bold yellow]レストランデータをCosmosDBに登録しています...[/bold yellow]")
112+
for i, restaurant in enumerate(restaurants):
113+
# ベクトルデータを追加
114+
restaurant["vector"] = embeddings[i]
115+
116+
# UpsertによりCosmosDBに登録
117+
container.upsert_item(body=restaurant)
118+
119+
console.print(f"ID: {restaurant['id']} - {restaurant['name']}を登録しました")
120+
121+
console.print("[bold green]すべてのデータが正常に登録されました![/bold green]")
122+
123+
124+
@app.command()
125+
def search(
126+
query: str = typer.Option(..., "--query", "-q", help="検索クエリ"),
127+
k: int = typer.Option(3, "--top-k", "-k", help="取得する上位結果の数"),
128+
):
129+
"""説明文のベクトル検索を実行する"""
130+
console.print(f"[bold green]クエリ[/bold green]: '{query}'で検索します")
131+
132+
# クエリテキストのベクトル埋め込みを生成
133+
query_embedding = get_embeddings([query])[0]
134+
135+
# Cosmos DBに接続
136+
container = setup_cosmos_db()
137+
138+
# ベクトル検索クエリの実行
139+
query_text = f"""
140+
SELECT TOP {k} r.id, r.name, r.description, r.price, r.tags
141+
FROM restaurants r
142+
ORDER BY VectorDistance(r.vector, @queryVector)
143+
"""
144+
145+
parameters = [{"name": "@queryVector", "value": query_embedding}]
146+
147+
items = list(container.query_items(query=query_text, parameters=parameters, enable_cross_partition_query=True))
148+
149+
# 結果の表示
150+
console.print(f"\n[bold blue]{len(items)}件[/bold blue]の検索結果:")
151+
for i, item in enumerate(items):
152+
console.print(f"\n[bold]{i + 1}. {item['name']}[/bold]")
153+
console.print(f" 説明: {item['description']}")
154+
console.print(f" 価格: ¥{item['price']}")
155+
console.print(f" タグ: {', '.join(item['tags'])}")
156+
157+
return items
158+
159+
160+
if __name__ == "__main__":
161+
app()

template_fastapi/app.py

Lines changed: 3 additions & 2 deletions
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 demo, games, items
14+
from template_fastapi.routers import demos, foodies, games, items
1515

1616
app = FastAPI()
1717

@@ -37,5 +37,6 @@ def server_request_hook(span: Span, scope: dict):
3737

3838
# Include routers
3939
app.include_router(items.router)
40-
app.include_router(demo.router)
40+
app.include_router(demos.router)
4141
app.include_router(games.router)
42+
app.include_router(foodies.router)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from pydantic import BaseModel
2+
3+
4+
class Restaurant(BaseModel):
5+
id: int
6+
name: str
7+
description: str | None = None
8+
price: float
9+
latitude: float | None = None
10+
longitude: float | None = None
11+
tags: list[str] = []
File renamed without changes.

0 commit comments

Comments
 (0)