feat: Add A2A Translator Agent with text and URL translation capabili… - #111
feat: Add A2A Translator Agent with text and URL translation capabili…#111Pasindu-Jayasundara wants to merge 1 commit into
Conversation
…ties - Implemented OpenAIAgentExecutor for handling OpenAI-based agent execution. - Created TranslatorToolset for translating text and web content. - Added models for translation requests and responses using Pydantic. - Developed methods for translating text, extracting content from URLs, and detecting languages. - Set up Dockerfile and docker-compose for easy deployment. - Included README with installation instructions, usage examples, and configuration details. - Added .gitignore to exclude unnecessary files from version control. - Created supporting files and structure for the A2A Translator Agent.
There was a problem hiding this comment.
Pull request overview
This PR introduces three new A2A-compatible agents under agents/ (Translator, GitHub, Compliance Checker), each shipped with its own src/ Python package, Dockerfile, docker-compose.yml, AgentCard.json, README and pyproject.toml. It also disables the healthcheck for an existing service in docker-compose.local.yml and renames the root virtual project package in uv.lock from arithmic-nasiko to nasiko. The PR description only mentions the Translator agent, but the diff is much broader than that.
Changes:
- Add A2A Translator agent (OpenAI-driven executor, Google-Translate-backed toolset, language detection).
- Add A2A GitHub agent and A2A Compliance Checker agent with similar OpenAI executor scaffolding.
- Disable healthcheck for an existing service in
docker-compose.local.ymland rename the root project package inuv.lock.
Reviewed changes
Copilot reviewed 37 out of 41 changed files in this pull request and generated 21 comments.
Show a summary per file
| File | Description |
|---|---|
| uv.lock | Renames root virtual project arithmic-nasiko → nasiko; unrelated to PR description |
| docker-compose.local.yml | Disables healthcheck for an existing service (regression) |
| agents/a2a-translator/v1.0.0/src/translator_toolset.py | Translator tooling using Google's internal endpoint; mixed sync/async API |
| agents/a2a-translator/v1.0.0/src/openai_agent_executor.py | OpenAI tool-calling loop; has duplicate-complete() bug and schema-inference limitations |
| agents/a2a-translator/v1.0.0/src/openai_agent.py | System prompt + tool registration |
| agents/a2a-translator/v1.0.0/src/main.py | Server entrypoint; references likely-nonexistent default model IDs |
| agents/a2a-translator/v1.0.0/{Dockerfile,docker-compose.yml,AgentCard.json,pyproject.toml,README.md,run_with_phoenix.sh,.gitignore} | Deployment + docs; placeholder Phoenix key, unused googletrans dep, wrong outputModes |
| agents/a2a-github-agent/v1.0.0/src/main.py | Entrypoint; imports non-existent poc_obser and has flawed provider selection |
| agents/a2a-github-agent/v1.0.0/src/github_toolset.py | PyGithub-based tooling; naive vs. aware datetime comparison bug |
| agents/a2a-github-agent/v1.0.0/src/openai_agent_executor.py / openai_agent.py | Same executor pattern as translator |
| agents/a2a-github-agent/v1.0.0/{Dockerfile,docker-compose.yml,AgentCard.json,pyproject.toml,README.md,run_with_phoenix.sh,.gitignore} | Deployment + docs; malformed environment: in compose, README points to wrong repo |
| agents/a2a-compliance-checker/v1.0.0/src/policy_agent.py | References missing BaseAgent / base_agent module — agent won't start |
| agents/a2a-compliance-checker/v1.0.0/src/compliance_toolset.py | Shared "a2a_session" and shared document_text across requests |
| agents/a2a-compliance-checker/v1.0.0/src/agent.py | Leftover translation agent code in compliance package |
| agents/a2a-compliance-checker/v1.0.0/src/openai_agent_executor.py / openai_agent.py / main.py / models.py / tools.py | Executor + scaffolding mirroring the other agents |
| agents/a2a-compliance-checker/v1.0.0/{Dockerfile,docker-compose.yml,AgentCard.json,pyproject.toml,README.md,.gitignore} | Deployment + docs; project name compliance-checker2, port mismatch with AgentCard |
Comments suppressed due to low confidence (5)
agents/a2a-translator/v1.0.0/src/translator_toolset.py:299
translate_textisasync, buttranslate_urlanddetect_languageare sync and perform blocking I/O (requests.get, BeautifulSoup parsing, blocking call to Google Translate). When called from within the async_process_requestloop inopenai_agent_executor.py(line 110:result = method(**function_args)), these will block the event loop for the duration of the network request, defeating the async server. Either make them async (and offload viarun_in_executor, liketranslate_textdoes), or have the executor consistently dispatch sync tools to a thread.
agents/a2a-translator/v1.0.0/src/openai_agent_executor.py:221_extract_function_schemadoes not consult typing generics orOptional/X | None, so parameters likesource_language: str | None = Noneare typed"string"in the OpenAI schema but never marked as required (correct), while a list/dict parameter declared via PEP 585 generics (list[str],dict[str, Any]) would be typed as the default"string"becauseparam.annotation == listis False forlist[str]. For the current toolsets parameters are mostlystr/int, but as new tools get added this will silently produce wrong schemas. Usetyping.get_origin/get_argsfor proper inference.
agents/a2a-translator/v1.0.0/src/openai_agent_executor.py:181inspectis already imported at the top of the file (line 3). Re-importing it inside this method shadows nothing but is unnecessary and out of place; drop the innerimport inspect.
agents/a2a-translator/v1.0.0/src/openai_agent_executor.py:177- When
max_iterationsis reached, this branch sends a final error artifact and callstask_updater.complete(), but the prior iteration of the loop already calledtask_updater.complete()(line 156) on a successful path is fine — however if the loop exits thewhilenormally viabreakafter the success path on line 157,iterationis still<= max_iterations, yet thisif iteration >= max_iterations(line 170) will beTruewhenever the loop completed its 10th iteration successfully, causing a duplicatecomplete()and a spurious "exceeded the maximum number of iterations" message after a valid final answer. Useelseon thewhileloop, or set acompletedflag.
agents/a2a-translator/v1.0.0/src/translator_toolset.py:251 - Translation result text is truncated to 5000 characters via slicing before being sent for translation (line 250-251 appends a literal
"..."to whatever happens to be the 5000th character — potentially mid-byte in a multi-byte UTF-8 sequence when the underlying string was just decoded). Pythonstrslicing is by codepoints so it won't break encoding, but it will routinely cut in the middle of a sentence/word and feed that to Google Translate, producing low-quality output and a trailing literal"..."in the source language. Consider sentence-aware truncation, or surfacing a warning to the user that the page was truncated.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| from base_agent import BaseAgent | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| class PolicyAgent(BaseAgent): | ||
| """Handles LLM-based Policy Inquiries""" | ||
|
|
||
| def get_response(self, query: str, session_id: str) -> str: | ||
| logger.info( | ||
| f"PolicyAgent generating response for session {session_id}, query='{query}'" | ||
| ) | ||
| system_prompt = f""" | ||
| You are a specialized Policy Compliance Checker Agent. | ||
| Your expertise is analyzing documents and content for policy violations and compliance issues. | ||
|
|
||
| DOCUMENT UNDER REVIEW: | ||
| \"\"\" | ||
| {self.document_parser.document_text} | ||
| \"\"\" |
| from poc_obser.tracing_utils import bootstrap_tracing | ||
|
|
||
| bootstrap_tracing(project_name="a2a-github-agent") | ||
|
|
| """ | ||
| Core agent logic for translation. | ||
| """ | ||
|
|
||
| import logging | ||
| import os | ||
| from typing import List, Dict, Any, Optional | ||
|
|
||
| from langchain_openai import ChatOpenAI | ||
| from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder | ||
| from langchain.agents import AgentExecutor, create_tool_calling_agent | ||
|
|
||
|
|
||
| from tools import extract_web_text | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| def _create_llm() -> ChatOpenAI: | ||
| """Create LLM instance, supporting OpenAI and MiniMax providers.""" | ||
| if os.getenv("MINIMAX_API_KEY") and not os.getenv("OPENAI_API_KEY"): | ||
| return ChatOpenAI( | ||
| model=os.getenv("MINIMAX_MODEL", "MiniMax-M2.7"), | ||
| temperature=1.0, | ||
| api_key=os.getenv("MINIMAX_API_KEY"), | ||
| base_url=os.getenv("MINIMAX_BASE_URL", "https://api.minimax.io/v1"), | ||
| ) | ||
| return ChatOpenAI(model="gpt-4o", temperature=0) | ||
|
|
||
|
|
||
| class Agent: | ||
| def __init__(self): | ||
| # Initialize your agent | ||
| self.name = "Translation Agent" | ||
|
|
||
| # Initialize Tools | ||
| self.tools = [extract_web_text] | ||
|
|
||
| # Initialize LangChain components | ||
| self.llm = _create_llm() | ||
|
|
||
| # System prompt tailored for text-to-text translation | ||
| self.system_prompt = """You are a helpful assistant whose primary objective is to help the user with language translation. | ||
|
|
||
| RULES: | ||
| - If the user provides a URL, use the 'extract_web_text' tool to get the content, then translate the extracted text. | ||
| - Detect the source language and the target language from the user's request. | ||
| - If the user specifies a target language, translate the text to that language. | ||
| - If the user provides text without specifying a target language, default to translating it to English (if it's not English) or ask for clarification if ambiguous. | ||
| - Translate the text fully and accurately. | ||
| - Preserve the original meaning and tone. | ||
| - Do NOT add explanations or commentary unless the translation requires context notes (which should be minimal). | ||
|
|
||
| RESPONSE FORMAT: | ||
| - Provide only the translated text. | ||
| """ | ||
|
|
||
| self.prompt = ChatPromptTemplate.from_messages( | ||
| [ | ||
| ("system", self.system_prompt), | ||
| ("user", "{input}"), | ||
| MessagesPlaceholder(variable_name="agent_scratchpad"), | ||
| ] | ||
| ) | ||
|
|
||
| # Create Tool Calling Agent | ||
| agent = create_tool_calling_agent(self.llm, self.tools, self.prompt) | ||
| self.agent_executor = AgentExecutor(agent=agent, tools=self.tools, verbose=True) | ||
|
|
||
| def process_message(self, message_text: str) -> str: | ||
| """ | ||
| Process the incoming message using LangChain. | ||
| """ | ||
| logger.info(f"Processing message: {message_text[:50]}...") | ||
| result = self.agent_executor.invoke({"input": message_text}) | ||
| return result["output"] |
| healthcheck: | ||
| test: ["CMD-SHELL", "python -c \"import requests; requests.get('http://localhost:8000/api/v1/healthcheck').raise_for_status()\" || exit 1"] | ||
| interval: 30s | ||
| timeout: 10s | ||
| retries: 3 | ||
| disable: true | ||
|
|
| def _translate_with_google( | ||
| self, text: str, src_lang: str, dest_lang: str | ||
| ) -> tuple[str, str]: | ||
| """Translate text using Google Translate API directly""" | ||
| try: | ||
| # Google Translate URL | ||
| url = "https://translate.googleapis.com/translate_a/single" | ||
|
|
||
| params = { | ||
| "client": "gtx", | ||
| "sl": src_lang, | ||
| "tl": dest_lang, | ||
| "dt": "t", | ||
| "q": text, | ||
| } | ||
|
|
||
| response = self.session.get(url, params=params, timeout=10) | ||
| response.raise_for_status() | ||
|
|
||
| result = response.json() | ||
|
|
||
| # Extract translated text | ||
| translated_text = "" | ||
| if result and len(result) > 0 and result[0]: | ||
| for sentence in result[0]: | ||
| if sentence and len(sentence) > 0: | ||
| translated_text += sentence[0] | ||
|
|
||
| # Extract detected source language | ||
| detected_src = src_lang | ||
| if len(result) > 2 and result[2]: | ||
| detected_src = result[2] | ||
|
|
||
| return translated_text.strip(), detected_src | ||
|
|
||
| except Exception as e: | ||
| raise Exception(f"Translation failed: {str(e)}") |
| - a2a-sdk: A2A framework for agent communication | ||
| - googletrans: Google Translate API wrapper | ||
| - beautifulsoup4: HTML parsing for web content extraction | ||
| - langdetect: Language detection library | ||
| - requests: HTTP client for web scraping | ||
| - OpenAI: For agent conversation handling | ||
|
|
| # Clone the repository | ||
| git clone https://github.com/a2aproject/a2a-samples.git | ||
| cd a2a-samples/samples/python/agents/github-agent | ||
|
|
||
| # Create virtual environment | ||
| uv venv | ||
| source .venv/bin/activate # On Windows: .venv\Scripts\activate |
| messages.append( | ||
| { | ||
| "role": "assistant", | ||
| "content": message.content, | ||
| "tool_calls": message.tool_calls, | ||
| } | ||
| ) |
| ports: | ||
| - "5000" | ||
| tty: true |
|
|
||
| ENV PYTHONUNBUFFERED=1 | ||
|
|
||
| CMD ["python", "__main__.py", "--host", "0.0.0.0", "--port", "5000", "--mongo-url", "mongodb://agents-mongo:27017", "--db-name", "compliance-checker-a2a"] No newline at end of file |
…ties