diff --git a/contributing/samples/models/hello_world_daoxe/README.md b/contributing/samples/models/hello_world_daoxe/README.md new file mode 100644 index 0000000000..03a3616fe5 --- /dev/null +++ b/contributing/samples/models/hello_world_daoxe/README.md @@ -0,0 +1,35 @@ +# DaoXE (OpenAI-compatible multi-model gateway) with ADK + +This sample shows using [DaoXE](https://daoxe.com) through ADK's `LiteLlm` wrapper +and LiteLLM's OpenAI-compatible provider. + +DaoXE exposes Chat Completions at `https://daoxe.com/v1`. Model IDs are +**account-scoped** — use exact IDs from authenticated `GET /v1/models` or the +dashboard. DaoXE is not available in mainland China. + +## Setup + +```bash +export OPENAI_API_KEY=your_daoxe_api_key +export OPENAI_API_BASE=https://daoxe.com/v1 +# optional alias used by some LiteLLM paths: +export OPENAI_BASE_URL=https://daoxe.com/v1 +``` + +Edit `agent.py` and set `model=LiteLlm(model="openai/")` +to an exact model ID available on your DaoXE account. + +## Run + +```bash +# from repo root, with ADK installed +adk web +# or +python -m contributing.samples.models.hello_world_daoxe.main +``` + +## Notes + +- Uses OpenAI Chat Completions via LiteLLM (`openai/...` model prefix). +- DaoXE also supports other protocols (e.g. Anthropic Messages) for other clients. +- Contributor disclosure: this sample was contributed by a DaoXE affiliate. diff --git a/contributing/samples/models/hello_world_daoxe/__init__.py b/contributing/samples/models/hello_world_daoxe/__init__.py new file mode 100644 index 0000000000..044e24d388 --- /dev/null +++ b/contributing/samples/models/hello_world_daoxe/__init__.py @@ -0,0 +1,16 @@ +# Copyright 2026 Google LLC +# +# 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 . import agent diff --git a/contributing/samples/models/hello_world_daoxe/agent.py b/contributing/samples/models/hello_world_daoxe/agent.py new file mode 100644 index 0000000000..b547aa3a73 --- /dev/null +++ b/contributing/samples/models/hello_world_daoxe/agent.py @@ -0,0 +1,98 @@ +# Copyright 2026 Google LLC +# +# 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 random + +from google.adk.agents.llm_agent import Agent +from google.adk.models.lite_llm import LiteLlm + + +def roll_die(sides: int) -> int: + """Roll a die and return the rolled result. + + Args: + sides: The integer number of sides the die has. + + Returns: + An integer of the result of rolling the die. + """ + return random.randint(1, sides) + + +async def check_prime(nums: list[int]) -> str: + """Check if a given list of numbers are prime. + + Args: + nums: The list of numbers to check. + + Returns: + A str indicating which number is prime. + """ + primes = set() + for number in nums: + number = int(number) + if number <= 1: + continue + is_prime = True + for i in range(2, int(number**0.5) + 1): + if number % i == 0: + is_prime = False + break + if is_prime: + primes.add(number) + return ( + "No prime numbers found." + if not primes + else f"{', '.join(str(num) for num in primes)} are prime numbers." + ) + + +root_agent = Agent( + # model=LiteLlm(model="gemini/gemini-2.5-pro-exp-03-25"), + # model=LiteLlm(model="vertex_ai/gemini-2.5-pro-exp-03-25"), + # model=LiteLlm(model="vertex_ai/claude-3-5-haiku"), + # DaoXE OpenAI-compatible gateway. Requires: + # OPENAI_API_KEY= + # OPENAI_API_BASE=https://daoxe.com/v1 (or OPENAI_BASE_URL) + # Replace the model id with an exact account-scoped id (GET /v1/models). + model=LiteLlm(model="openai/your-account-model-id"), + # model=LiteLlm(model="anthropic/claude-3-sonnet-20240229"), + name="data_processing_agent", + description=( + "hello world agent that can roll a dice of 8 sides and check prime" + " numbers." + ), + instruction=""" + You roll dice and answer questions about the outcome of the dice rolls. + You can roll dice of different sizes. + You can use multiple tools in parallel by calling functions in parallel(in one request and in one round). + It is ok to discuss previous dice roles, and comment on the dice rolls. + When you are asked to roll a die, you must call the roll_die tool with the number of sides. Be sure to pass in an integer. Do not pass in a string. + You should never roll a die on your own. + When checking prime numbers, call the check_prime tool with a list of integers. Be sure to pass in a list of integers. You should never pass in a string. + You should not check prime numbers before calling the tool. + When you are asked to roll a die and check prime numbers, you should always make the following two function calls: + 1. You should first call the roll_die tool to get a roll. Wait for the function response before calling the check_prime tool. + 2. After you get the function response from roll_die tool, you should call the check_prime tool with the roll_die result. + 2.1 If user asks you to check primes based on previous rolls, make sure you include the previous rolls in the list. + 3. When you respond, you must include the roll_die result from step 1. + You should always perform the previous 3 steps when asking for a roll and checking prime numbers. + You should not rely on the previous history on prime results. + """, + tools=[ + roll_die, + check_prime, + ], +) diff --git a/contributing/samples/models/hello_world_daoxe/main.py b/contributing/samples/models/hello_world_daoxe/main.py new file mode 100644 index 0000000000..aacdded41e --- /dev/null +++ b/contributing/samples/models/hello_world_daoxe/main.py @@ -0,0 +1,76 @@ +# Copyright 2026 Google LLC +# +# 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 +import time + +import agent +from dotenv import load_dotenv +from google.adk.artifacts.in_memory_artifact_service import InMemoryArtifactService +from google.adk.cli.utils import logs +from google.adk.runners import Runner +from google.adk.sessions.in_memory_session_service import InMemorySessionService +from google.adk.sessions.session import Session +from google.genai import types + +load_dotenv(override=True) +logs.log_to_tmp_folder() + + +async def main(): + app_name = 'my_app' + user_id_1 = 'user1' + session_service = InMemorySessionService() + artifact_service = InMemoryArtifactService() + runner = Runner( + app_name=app_name, + agent=agent.root_agent, + artifact_service=artifact_service, + session_service=session_service, + ) + session_11 = await session_service.create_session( + app_name=app_name, user_id=user_id_1 + ) + + async def run_prompt(session: Session, new_message: str): + content = types.Content( + role='user', parts=[types.Part.from_text(text=new_message)] + ) + print('** User says:', content.model_dump(exclude_none=True)) + async for event in runner.run_async( + user_id=user_id_1, + session_id=session.id, + new_message=content, + ): + if event.content.parts and event.content.parts[0].text: + print(f'** {event.author}: {event.content.parts[0].text}') + + start_time = time.time() + print('Start time:', start_time) + print('------------------------------------') + await run_prompt(session_11, 'Hi, introduce yourself.') + await run_prompt( + session_11, 'Roll a die with 100 sides and check if it is prime' + ) + await run_prompt(session_11, 'Roll it again.') + await run_prompt(session_11, 'What numbers did I got?') + end_time = time.time() + print('------------------------------------') + print('End time:', end_time) + print('Total time:', end_time - start_time) + + +if __name__ == '__main__': + asyncio.run(main())