Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions examples/veadk-vanna-proj/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
23 changes: 23 additions & 0 deletions examples/veadk-vanna-proj/clean.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from veadk.cloud.cloud_app import CloudApp

def main() -> None:
cloud_app = CloudApp(vefaas_application_name="veadk-cloud-vanna-agent")
cloud_app.delete_self()


if __name__ == "__main__":
main()
6 changes: 6 additions & 0 deletions examples/veadk-vanna-proj/config.yaml.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
model:
agent:
provider: openai
name: doubao-1-5-pro-256k-250115
api_base: https://ark.cn-beijing.volces.com/api/v3/
api_key:
106 changes: 106 additions & 0 deletions examples/veadk-vanna-proj/deploy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import asyncio
from pathlib import Path

from a2a.types import TextPart
from fastmcp.client import Client

from veadk.cloud.cloud_agent_engine import CloudAgentEngine
from veadk.cloud.cloud_app import CloudApp, get_message_id

SESSION_ID = "cloud_app_test_session"
USER_ID = "cloud_app_test_user"


async def _send_msg_with_a2a(cloud_app: CloudApp, message: str) -> None:
print("===== A2A example =====")

response_message = await cloud_app.message_send(message, SESSION_ID, USER_ID)

if not response_message or not response_message.parts:
print(
"No response from VeFaaS application. Something wrong with cloud application."
)
return

print(f"Message ID: {get_message_id(response_message)}")

if isinstance(response_message.parts[0].root, TextPart):
print(
f"Response from {cloud_app.vefaas_endpoint}: {response_message.parts[0].root.text}"
)
else:
print(
f"Response from {cloud_app.vefaas_endpoint}: {response_message.parts[0].root}"
)


async def _send_msg_with_mcp(cloud_app: CloudApp, message: str) -> None:
print("===== MCP example =====")

endpoint = cloud_app._get_vefaas_endpoint()
print(f"MCP server endpoint: {endpoint}/mcp")

# Connect to MCP server
client = Client(f"{endpoint}/mcp")

async with client:
# List available tools
tools = await client.list_tools()
print(f"Available tools: {tools}")

# Call run_agent tool, pass user input and session information
res = await client.call_tool(
"run_agent",
{
"user_input": message,
"session_id": SESSION_ID,
"user_id": USER_ID,
},
)
print(f"Response from {cloud_app.vefaas_endpoint}: {res}")


async def main():
engine = CloudAgentEngine()

cloud_app = engine.deploy(
path=str(Path(__file__).parent / "src"),
application_name="veadk-cloud-vanna-agent",
gateway_name="dong-mcp-agent2",
gateway_service_name="",
gateway_upstream_name="",
use_adk_web=True,
auth_method="none",
identity_user_pool_name="",
identity_client_name="",
local_test=False, # Set to True for local testing before deploy to VeFaaS
)
print(f"VeFaaS application ID: {cloud_app.vefaas_application_id}")

if False:
print(f"Web is running at: {cloud_app.vefaas_endpoint}")
else:
# Test with deployed cloud application
message = "How is the weather like in Beijing?"
print(f"Test message: {message}")

# await _send_msg_with_a2a(cloud_app=cloud_app, message=message)
# await _send_msg_with_mcp(cloud_app=cloud_app, message=message)


if __name__ == "__main__":
asyncio.run(main())
Binary file added examples/veadk-vanna-proj/src/.adk/session.db
Binary file not shown.
13 changes: 13 additions & 0 deletions examples/veadk-vanna-proj/src/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
26 changes: 26 additions & 0 deletions examples/veadk-vanna-proj/src/agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from data_agent.agent import agent # type: ignore

from veadk.memory.short_term_memory import ShortTermMemory
from veadk.types import AgentRunConfig

# [required] instantiate the agent run configuration
agent_run_config = AgentRunConfig(
app_name="vanna_sql_agent",
agent=agent, # type: ignore
short_term_memory=ShortTermMemory(backend="local", local_database_path="/tmp/session.db"), # type: ignore
model_extra_config={"extra_body": {"thinking": {"type": "disabled"}}}
)
171 changes: 171 additions & 0 deletions examples/veadk-vanna-proj/src/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import os
from contextlib import asynccontextmanager
from typing import Callable

from agent import agent_run_config

from fastapi import FastAPI
from fastapi.routing import APIRoute

from fastmcp import FastMCP

from starlette.routing import Route

from google.adk.a2a.utils.agent_card_builder import AgentCardBuilder
from a2a.types import AgentProvider

from veadk.a2a.ve_a2a_server import init_app
from veadk.runner import Runner
from veadk.types import AgentRunConfig
from veadk.utils.logger import get_logger
from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
from opentelemetry import context

logger = get_logger(__name__)

assert isinstance(agent_run_config, AgentRunConfig), (
f"Invalid agent_run_config type: {type(agent_run_config)}, expected `AgentRunConfig`"
)

app_name = agent_run_config.app_name
agent = agent_run_config.agent
short_term_memory = agent_run_config.short_term_memory

VEFAAS_REGION = os.getenv("APP_REGION", "cn-beijing")
VEFAAS_FUNC_ID = os.getenv("_FAAS_FUNC_ID", "")
agent_card_builder = AgentCardBuilder(agent=agent, provider=AgentProvider(organization="Volcengine Agent Development Kit (VeADK)", url=f"https://console.volcengine.com/vefaas/region:vefaas+{VEFAAS_REGION}/function/detail/{VEFAAS_FUNC_ID}"))


def build_mcp_run_agent_func() -> Callable:
runner = Runner(
agent=agent,
short_term_memory=short_term_memory,
app_name=app_name,
user_id="",
)

async def run_agent(
user_input: str,
user_id: str = "mcp_user",
session_id: str = "mcp_session",
) -> str:
# Set user_id for runner
runner.user_id = user_id

# Running agent and get final output
final_output = await runner.run(
messages=user_input,
session_id=session_id,
)
return final_output

run_agent_doc = f"""{agent.description}
Args:
user_input: User's input message (required).
user_id: User identifier. Defaults to "mcp_user".
session_id: Session identifier. Defaults to "mcp_session".
Returns:
Final agent response as a string."""

run_agent.__doc__ = run_agent_doc

return run_agent


async def agent_card() -> dict:
agent_card = await agent_card_builder.build()
return agent_card.model_dump()

async def get_cozeloop_space_id() -> dict:
return {"space_id": os.getenv("OBSERVABILITY_OPENTELEMETRY_COZELOOP_SERVICE_NAME", default="")}

# Build a run_agent function for building MCP server
run_agent_func = build_mcp_run_agent_func()

a2a_app = init_app(
server_url="0.0.0.0",
app_name=app_name,
agent=agent,
short_term_memory=short_term_memory,
)

a2a_app.post("/run_agent", operation_id="run_agent", tags=["mcp"])(run_agent_func)
a2a_app.get("/agent_card", operation_id="agent_card", tags=["mcp"])(agent_card)
a2a_app.get("/get_cozeloop_space_id", operation_id="get_cozeloop_space_id", tags=["mcp"])(get_cozeloop_space_id)

# === Build mcp server ===

mcp = FastMCP.from_fastapi(app=a2a_app, name=app_name, include_tags={"mcp"})

# Create MCP ASGI app
mcp_app = mcp.http_app(path="/", transport="streamable-http")


# Combined lifespan management
@asynccontextmanager
async def combined_lifespan(app: FastAPI):
async with mcp_app.lifespan(app):
yield


# Create main FastAPI app with combined lifespan
app = FastAPI(
title=a2a_app.title,
version=a2a_app.version,
lifespan=combined_lifespan,
openapi_url=None,
docs_url=None,
redoc_url=None
)

@app.middleware("http")
async def otel_context_middleware(request, call_next):
carrier = {
"traceparent": request.headers.get("Traceparent"),
"tracestate": request.headers.get("Tracestate"),
}
logger.debug(f"carrier: {carrier}")
if carrier["traceparent"] is None:
return await call_next(request)
else:
ctx = TraceContextTextMapPropagator().extract(carrier=carrier)
logger.debug(f"ctx: {ctx}")
token = context.attach(ctx)
try:
response = await call_next(request)
finally:
context.detach(token)
return response

# Mount A2A routes to main app
for route in a2a_app.routes:
app.routes.append(route)

# Mount MCP server at /mcp endpoint
app.mount("/mcp", mcp_app)


# remove openapi routes
paths = ["/openapi.json", "/docs", "/redoc"]
new_routes = []
for route in app.router.routes:
if isinstance(route, (APIRoute, Route)) and route.path in paths:
continue
new_routes.append(route)
app.router.routes = new_routes

# === Build mcp server end ===
Binary file not shown.
18 changes: 18 additions & 0 deletions examples/veadk-vanna-proj/src/data_agent/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from .agent import agent

# required from Google ADK Web
root_agent = agent
Loading