Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 173
feat(clients): add GenericOpenAIAgent for any OpenAI Chat Completions API endpoint#145
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
a712ecfaa162593f65e903ecefb827ecf69c8696c23b1cc961b5764b7eb09d0File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| """Generic OpenAI-compatible chat client (with shell access) for AIOpsLab. | ||
| This agent works with any provider that implements the OpenAI Chat Completions | ||
| API endpoint (/v1/chat/completions), such as Poe | ||
| (https://creator.poe.com/docs/external-applications/openai-compatible-api), | ||
| standard OpenAI deployments, vLLM, LocalAI, or other compatible services. | ||
| Configure the endpoint and model via environment variables or constructor arguments: | ||
| OPENAI_COMPATIBLE_API_KEY — API key for the target endpoint | ||
| OPENAI_COMPATIBLE_BASE_URL — Base URL of the target endpoint (e.g. https://api.poe.com/llm/v1) | ||
| OPENAI_COMPATIBLE_MODEL — Model name to use (e.g. MiniMax-Text-01) | ||
| """ | ||
| import os | ||
| import asyncio | ||
| import wandb | ||
| from aiopslab.orchestrator import Orchestrator | ||
| from aiopslab.orchestrator.problems.registry import ProblemRegistry | ||
| from clients.utils.llm import GenericOpenAIClient | ||
| from clients.utils.templates import DOCS_SHELL_ONLY | ||
| from dotenv import load_dotenv | ||
| # Load environment variables from the .env file | ||
| load_dotenv() | ||
| class GenericOpenAIAgent: | ||
| def __init__( | ||
| self, | ||
| base_url: str | None = None, | ||
| model: str | None = None, | ||
| api_key: str | None = None, | ||
| ): | ||
| self.history = [] | ||
| self.llm = GenericOpenAIClient( | ||
| base_url=base_url, | ||
| model=model, | ||
| api_key=api_key, | ||
| ) | ||
| def init_context(self, problem_desc: str, instructions: str, apis: str): | ||
| """Initialize the context for the agent.""" | ||
| self.shell_api = self._filter_dict(apis, lambda k, _: "exec_shell" in k) | ||
| self.submit_api = self._filter_dict(apis, lambda k, _: "submit" in k) | ||
| stringify_apis = lambda apis: "\n\n".join( | ||
Comment on lines
+41
to
+46
CopilotAI | ||
| [f"{k}\n{v}" for k, v in apis.items()] | ||
| ) | ||
| self.system_message = DOCS_SHELL_ONLY.format( | ||
| prob_desc=problem_desc, | ||
| shell_api=stringify_apis(self.shell_api), | ||
| submit_api=stringify_apis(self.submit_api), | ||
| ) | ||
| self.task_message = instructions | ||
| self.history.append({"role": "system", "content": self.system_message}) | ||
| self.history.append({"role": "user", "content": self.task_message}) | ||
| async def get_action(self, input) -> str: | ||
| """Wrapper to interface the agent with AIOpsLab. | ||
| Args: | ||
| input (str): The input from the orchestrator/environment. | ||
| Returns: | ||
| str: The response from the agent. | ||
| """ | ||
| self.history.append({"role": "user", "content": input}) | ||
| response = self.llm.run(self.history) | ||
| model_name = self.llm.model | ||
| print(f"===== Agent (GenericOpenAI - {model_name}) ====\n{response[0]}") | ||
| self.history.append({"role": "assistant", "content": response[0]}) | ||
| return response[0] | ||
| def _filter_dict(self, dictionary, filter_func): | ||
| return {k: v for k, v in dictionary.items() if filter_func(k, v)} | ||
| if __name__ == "__main__": | ||
| # Load use_wandb from environment variable with a default of False | ||
| use_wandb = os.getenv("USE_WANDB", "false").lower() == "true" | ||
| if use_wandb: | ||
| wandb.init(project="AIOpsLab", entity="AIOpsLab") | ||
| problems = ProblemRegistry().PROBLEM_REGISTRY | ||
| for pid in problems: | ||
| agent = GenericOpenAIAgent() | ||
| orchestrator = Orchestrator() | ||
| orchestrator.register_agent(agent, name="generic-openai") | ||
| problem_desc, instructs, apis = orchestrator.init_problem(pid) | ||
| agent.init_context(problem_desc, instructs, apis) | ||
| asyncio.run(orchestrator.start_problem(max_steps=30)) | ||
| if use_wandb: | ||
| wandb.finish() | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -330,6 +330,84 @@ def run(self, payload: list[dict[str, str]]) -> list[str]: | ||||||||||||||||||||||||||||||
| return response | ||||||||||||||||||||||||||||||
| class GenericOpenAIClient: | ||||||||||||||||||||||||||||||
| """Generic client for any OpenAI Chat Completions compatible endpoint. | ||||||||||||||||||||||||||||||
| Uses the standard Chat Completions API (client.chat.completions.create), | ||||||||||||||||||||||||||||||
| making it compatible with any provider that implements the OpenAI Chat | ||||||||||||||||||||||||||||||
| Completions spec — including Poe, OpenRouter, vLLM, LocalAI, DeepSeek, | ||||||||||||||||||||||||||||||
| and standard OpenAI deployments, as well as Azure- or other cloud-hosted | ||||||||||||||||||||||||||||||
| gateways that expose an OpenAI-compatible `/v1/chat/completions` endpoint | ||||||||||||||||||||||||||||||
| via `base_url`. | ||||||||||||||||||||||||||||||
| Note: Native Azure OpenAI endpoints typically require the AzureOpenAI client | ||||||||||||||||||||||||||||||
| with an `azure_endpoint` and `api_version`, and are not used via `base_url` | ||||||||||||||||||||||||||||||
| in this class unless they are fronted by such a compatibility gateway. | ||||||||||||||||||||||||||||||
| Environment variables: | ||||||||||||||||||||||||||||||
| OPENAI_COMPATIBLE_API_KEY: API key for the target endpoint. | ||||||||||||||||||||||||||||||
| OPENAI_COMPATIBLE_BASE_URL: Base URL of the target endpoint. | ||||||||||||||||||||||||||||||
| OPENAI_COMPATIBLE_MODEL: Model name to use (default: gpt-4o). | ||||||||||||||||||||||||||||||
| All three can be overridden by passing explicit arguments to the constructor. | ||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||
| def __init__( | ||||||||||||||||||||||||||||||
| self, | ||||||||||||||||||||||||||||||
| base_url: Optional[str] = None, | ||||||||||||||||||||||||||||||
| model: Optional[str] = None, | ||||||||||||||||||||||||||||||
| api_key: Optional[str] = None, | ||||||||||||||||||||||||||||||
| max_tokens: int = 16000, | ||||||||||||||||||||||||||||||
| ): | ||||||||||||||||||||||||||||||
| self.cache = Cache() | ||||||||||||||||||||||||||||||
| self.model = model or os.getenv("OPENAI_COMPATIBLE_MODEL", "gpt-4o") | ||||||||||||||||||||||||||||||
| self.max_tokens = max_tokens | ||||||||||||||||||||||||||||||
Comment on lines
+359
to
+363
CopilotAI | ||||||||||||||||||||||||||||||
| max_tokens: int=16000, | |
| ): | |
| self.cache=Cache() | |
| self.model=modeloros.getenv("OPENAI_COMPATIBLE_MODEL", "gpt-4o") | |
| self.max_tokens=max_tokens | |
| max_tokens: Optional[int] =None, | |
| ): | |
| self.cache=Cache() | |
| self.model=modeloros.getenv("OPENAI_COMPATIBLE_MODEL", "gpt-4o") | |
| ifmax_tokensisnotNone: | |
| self.max_tokens=max_tokens | |
| else: | |
| env_max_tokens=os.getenv("OPENAI_COMPATIBLE_MAX_TOKENS") | |
| self.max_tokens=int(env_max_tokens) ifenv_max_tokensisnotNoneelse1024 |
Ash Manda (srmanda-cs)Feb 25, 2026 •
edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the max tokens isn't set that high, a lot of api endpoints (like Poe) that support thinking will not be able to initialize, as the minimum token requirement is 4096. And just because max token requirement is high does not imply that the full token context will be used. It is a good idea to take in an environment variable, but then we would have to update the implementation for every client. I think, there is enough flexibility offered here.
CopilotAIFeb 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the exception handler, raise e resets the traceback context in Python. Use a bare raise to preserve the original stack trace (and consider using the project’s logging approach instead of print for errors).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is standard implementation for every client, for consistency used the same block of code
CopilotAIFeb 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The PR title/description references the OpenAI “Responses API”, but this agent (and
GenericOpenAIClient) is implemented against the Chat Completions endpoint (/v1/chat/completions). Please align the PR wording/docs with what’s actually supported (either update the PR description/title to Chat Completions, or implement the Responses API).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated