Skip to content

feat: OpenRouter agent #73

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

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# AVCTL
.secrets
.env

# Misc
*.json
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[agent]
address = "agent1qvz3aqvmpjpypd7y2cffq02q8tydczcrgumq0x8lkmcd5k5lf9wdkre0z65"
include = ""
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
OPENROUTER_API_KEY=
MODEL_ENGINE=
159 changes: 159 additions & 0 deletions 6-deployed-agents/knowledge-base/openrouter-agent/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
# OpenRouter: Llama 4 Maverick Agent

![domain:knowledge-base](https://img.shields.io/badge/knowledge--base-3D8BD3?style=flat&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB3aWR0aD0iMTAiIGhlaWdodD0iOCIgdmlld0JveD0iMCAwIDEwIDgiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI%2BCjxwYXRoIGQ9Ik00Ljc1IDAuNTAwMDA4VjYuMzc1MDFMMS41IDUuNzUwMDFWMC41NDY4ODNDMS41IDAuMjM0MzgzIDEuNzY1NjIgNy44MzMzNWUtMDYgMi4wNzgxMiAwLjA2MjUwNzhMNC43NSAwLjUwMDAwOFpNMS4zOTA2MiA2LjM0Mzc2TDUgNy4wNjI1MUw4LjU5Mzc1IDYuMzQzNzZDOC44MjgxMiA2LjI5Njg4IDkgNi4wNzgxMyA5IDUuODQzNzZWMC40NTMxMzNMOS4zOTA2MiAwLjM3NTAwOEM5LjcwMzEyIDAuMzEyNTA4IDEwIDAuNTQ2ODgzIDEwIDAuODU5MzgzVjYuNTkzNzZDMTAgNi44NDM3NiA5LjgyODEyIDcuMDQ2ODggOS41OTM3NSA3LjA5Mzc2TDUgOC4wMDAwMUwwLjM5MDYyNSA3LjA5Mzc2QzAuMTU2MjUgNy4wNDY4OCAwIDYuODI4MTMgMCA2LjU5Mzc2VjAuODU5MzgzQzAgMC41NDY4ODMgMC4yODEyNSAwLjMxMjUwOCAwLjU5Mzc1IDAuMzc1MDA4TDEgMC40NTMxMzNWNS44NDM3NkMxIDYuMDkzNzYgMS4xNTYyNSA2LjI5Njg4IDEuMzkwNjIgNi4zNDM3NlpNNS4yNSA2LjM3NTAxVjAuNTAwMDA4TDcuOTA2MjUgMC4wNjI1MDc4QzguMjE4NzUgNy44MzMzNWUtMDYgOC41IDAuMjM0MzgzIDguNSAwLjU0Njg4M1Y1Ljc1MDAxTDUuMjUgNi4zNzUwMVoiIGZpbGw9IndoaXRlIi8%2BCjwvc3ZnPgo%3D)
![tech:llm](https://img.shields.io/badge/llm-E85D2E?style=flat&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB3aWR0aD0iMTAiIGhlaWdodD0iOCIgdmlld0JveD0iMCAwIDEwIDgiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI%2BCjxwYXRoIGQ9Ik00LjUgMUM0LjUgMS4yMTg3NSA0LjQyMTg4IDEuNDIxODggNC4zMTI1IDEuNTc4MTJMNC43NjU2MiAyLjU2MjVDNC45MjE4OCAyLjUzMTI1IDUuMDc4MTIgMi41IDUuMjUgMi41QzUuODEyNSAyLjUgNi4zMjgxMiAyLjcxODc1IDYuNzE4NzUgMy4wNjI1TDggMi4xMDkzOEM4IDIuMDc4MTIgOCAyLjA0Njg4IDggMkM4IDEuNDUzMTIgOC40Mzc1IDEgOSAxQzkuNTQ2ODggMSAxMCAxLjQ1MzEyIDEwIDJDMTAgMi41NjI1IDkuNTQ2ODggMyA5IDNDOC44NDM3NSAzIDguNzE4NzUgMi45ODQzOCA4LjU5Mzc1IDIuOTIxODhMNy4zMTI1IDMuODU5MzhDNy40MjE4OCA0LjE0MDYyIDcuNSA0LjQzNzUgNy41IDQuNzVDNy41IDUgNy40NTMxMiA1LjIzNDM4IDcuMzc1IDUuNDUzMTJMOC41IDYuMTI1QzguNjU2MjUgNi4wNDY4OCA4LjgxMjUgNiA5IDZDOS41NDY4OCA2IDEwIDYuNDUzMTIgMTAgN0MxMCA3LjU2MjUgOS41NDY4OCA4IDkgOEM4LjQzNzUgOCA4IDcuNTYyNSA4IDdWNi45ODQzOEw2Ljg1OTM4IDYuMzEyNUM2LjQ1MzEyIDYuNzM0MzggNS44NzUgNyA1LjI1IDdDNC4xNzE4OCA3IDMuMjgxMjUgNi4yNjU2MiAzLjA0Njg4IDUuMjVIMS44NTkzOEMxLjY4NzUgNS41NjI1IDEuMzU5MzggNS43NSAxIDUuNzVDMC40Mzc1IDUuNzUgMCA1LjMxMjUgMCA0Ljc1QzAgNC4yMDMxMiAwLjQzNzUgMy43NSAxIDMuNzVDMS4zNTkzOCAzLjc1IDEuNjg3NSAzLjk1MzEyIDEuODU5MzggNC4yNUgzLjA0Njg4QzMuMTcxODggMy43MzQzOCAzLjQ1MzEyIDMuMjk2ODggMy44NTkzOCAyLjk4NDM4TDMuNDA2MjUgMkMyLjg5MDYyIDEuOTUzMTIgMi41IDEuNTMxMjUgMi41IDFDMi41IDAuNDUzMTI1IDIuOTM3NSAwIDMuNSAwQzQuMDQ2ODggMCA0LjUgMC40NTMxMjUgNC41IDFaTTUuMjUgNS41QzUuNTE1NjIgNS41IDUuNzUgNS4zNTkzOCA1Ljg5MDYyIDUuMTI1QzYuMDMxMjUgNC45MDYyNSA2LjAzMTI1IDQuNjA5MzggNS44OTA2MiA0LjM3NUM1Ljc1IDQuMTU2MjUgNS41MTU2MiA0IDUuMjUgNEM0Ljk2ODc1IDQgNC43MzQzOCA0LjE1NjI1IDQuNTkzNzUgNC4zNzVDNC40NTMxMiA0LjYwOTM4IDQuNDUzMTIgNC45MDYyNSA0LjU5Mzc1IDUuMTI1QzQuNzM0MzggNS4zNTkzOCA0Ljk2ODc1IDUuNSA1LjI1IDUuNVoiIGZpbGw9IndoaXRlIi8%2BCjwvc3ZnPgo%3D)
[![link to source code](https://img.shields.io/badge/Source%20Code-E8ECF1?style=flat&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB3aWR0aD0iOCIgaGVpZ2h0PSI4IiB2aWV3Qm94PSIwIDAgOCA4IiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBkPSJNNCAwLjA5ODk5OUMxLjc5IDAuMDk4OTk5IDAgMS44OSAwIDQuMDk5QzAgNS44NjY2NyAxLjE0NiA3LjM2NTY2IDIuNzM1IDcuODk0QzIuOTM1IDcuOTMxNjYgMy4wMDgzMyA3LjgwOCAzLjAwODMzIDcuNzAxNjZDMy4wMDgzMyA3LjYwNjY2IDMuMDA1IDcuMzU1IDMuMDAzMzMgNy4wMjE2N0MxLjg5MDY3IDcuMjYzIDEuNjU2IDYuNDg1IDEuNjU2IDYuNDg1QzEuNDc0IDYuMDIzMzMgMS4yMTEgNS45IDEuMjExIDUuOUMwLjg0ODY2NyA1LjY1MiAxLjIzOSA1LjY1NyAxLjIzOSA1LjY1N0MxLjY0MDY3IDUuNjg1IDEuODUxNjcgNi4wNjkgMS44NTE2NyA2LjA2OUMyLjIwODMzIDYuNjgwNjcgMi43ODggNi41MDQgMy4wMTY2NyA2LjQwMTY2QzMuMDUyNjcgNi4xNDMgMy4xNTU2NyA1Ljk2NjY3IDMuMjcgNS44NjY2N0MyLjM4MTY3IDUuNzY2NjcgMS40NDggNS40MjI2NyAxLjQ0OCAzLjg5QzEuNDQ4IDMuNDUzMzMgMS42MDMgMy4wOTY2NyAxLjg1OTY3IDIuODE2NjdDMS44MTQ2NyAyLjcxNTY3IDEuNjc5NjcgMi4zMDkgMS44OTQ2NyAxLjc1OEMxLjg5NDY3IDEuNzU4IDIuMjI5NjcgMS42NTA2NyAyLjk5NDY3IDIuMTY4QzMuMzE0NjcgMi4wNzkgMy42NTQ2NyAyLjAzNSAzLjk5NDY3IDIuMDMzQzQuMzM0NjcgMi4wMzUgNC42NzQ2NyAyLjA3OSA0Ljk5NDY3IDIuMTY4QzUuNzU0NjcgMS42NTA2NyA2LjA4OTY3IDEuNzU4IDYuMDg5NjcgMS43NThDNi4zMDQ2NyAyLjMwOSA2LjE2OTY3IDIuNzE1NjcgNi4xMjk2NyAyLjgxNjY3QzYuMzg0NjcgMy4wOTY2NyA2LjUzOTY3IDMuNDUzMzMgNi41Mzk2NyAzLjg5QzYuNTM5NjcgNS40MjY2NyA1LjYwNDY3IDUuNzY1IDQuNzE0NjcgNS44NjMzM0M0Ljg1NDY3IDUuOTgzMzMgNC45ODQ2NyA2LjIyODY2IDQuOTg0NjcgNi42MDMzM0M0Ljk4NDY3IDcuMTM4NjYgNC45Nzk2NyA3LjU2ODY3IDQuOTc5NjcgNy42OTg2N0M0Ljk3OTY3IDcuODAzNjcgNS4wNDk2NyA3LjkyODY3IDUuMjU0NjcgNy44ODg2N0M2Ljg1NSA3LjM2NCA4IDUuODY0IDggNC4wOTlDOCAxLjg5IDYuMjA5IDAuMDk4OTk5IDQgMC4wOTg5OTlaIiBmaWxsPSIjNTU2NTc4Ii8%2BCjwvc3ZnPgo%3D)](https://github.com/fetchai/uAgent-Examples/tree/main/6-deployed-agents/knowledge-base/openrouter-agent)
[![live](https://img.shields.io/badge/Live-8A2BE2?style=flat&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB3aWR0aD0iMTAiIGhlaWdodD0iOCIgdmlld0JveD0iMCAwIDEwIDgiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI%2BCjxwYXRoIGQ9Ik0yLjI1IDcuNUMxIDcuNSAwIDYuNSAwIDUuMjVDMCA0LjI4MTI1IDAuNjI1IDMuNDM3NSAxLjUgMy4xNDA2MkMxLjUgMy4wOTM3NSAxLjUgMy4wNDY4OCAxLjUgM0MxLjUgMS42MjUgMi42MDkzOCAwLjUgNCAwLjVDNC45MjE4OCAwLjUgNS43MzQzOCAxLjAxNTYyIDYuMTU2MjUgMS43NjU2MkM2LjM5MDYyIDEuNTkzNzUgNi42ODc1IDEuNSA3IDEuNUM3LjgyODEyIDEuNSA4LjUgMi4xNzE4OCA4LjUgM0M4LjUgMy4yMDMxMiA4LjQ1MzEyIDMuMzc1IDguMzkwNjIgMy41NDY4OEM5LjMxMjUgMy43MzQzOCAxMCA0LjU0Njg4IDEwIDUuNUMxMCA2LjYwOTM4IDkuMDkzNzUgNy41IDggNy41SDIuMjVaTTYuNzY1NjIgMy43NjU2MkM2LjkwNjI1IDMuNjI1IDYuOTA2MjUgMy4zOTA2MiA2Ljc2NTYyIDMuMjVDNi42MDkzOCAzLjA5Mzc1IDYuMzc1IDMuMDkzNzUgNi4yMzQzOCAzLjI1TDQuNSA0Ljk4NDM4TDMuNzY1NjIgNC4yNUMzLjYwOTM4IDQuMDkzNzUgMy4zNzUgNC4wOTM3NSAzLjIzNDM4IDQuMjVDMy4wNzgxMiA0LjM5MDYyIDMuMDc4MTIgNC42MjUgMy4yMzQzOCA0Ljc2NTYyTDQuMjM0MzggNS43NjU2MkM0LjM3NSA1LjkyMTg4IDQuNjA5MzggNS45MjE4OCA0Ljc2NTYyIDUuNzY1NjJMNi43NjU2MiAzLjc2NTYyWiIgZmlsbD0id2hpdGUiLz4KPC9zdmc%2BCg%3D%3D)](https://agentverse.ai/agents/details/agent1q0h70caed8ax769shpemapzkyk65uscw4xwk6dc4t3emvp5jdcvqs9xs32y/profile)

## Example input 1

```python
ContextPrompt(
context="Find and fix the bug in the provided code snippet",
text="""
def do_something():
for i in range(10)
pass
""",
)
```

## Example output 1

```python
Response(
text="The code snippet has a syntax error due to a missing colon (`:`) at the end of the `for` statement. Here is the corrected version of the function: ```python def do_something(): for i in range(10): pass ``` Now the `for` loop is correctly defined with a colon at the end."
)
```

## Example input 2

```python
class Location(Model):
city: str
country: str
temperature: float

StructuredOutputPrompt(
prompt="What is the temperature in Paris?",
output_schema=Location.schema(),
)
```

## Example output 2

````python
StructuredOutputResponse(
output={
"city": "Paris",
"country": "France",
"temperature": 15.0,
}
)
````

## Usage Example

Copy and paste the following code into a new [Blank agent](https://agentverse.ai/agents/create/getting-started/blank-agent) for an example of how to interact with this agent.

```python
from typing import Any
from uagents import Agent, Context, Model


class ContextPrompt(Model):
context: str
text: str


class Response(Model):
text: str


class StructuredOutputPrompt(Model):
prompt: str
output_schema: dict[str, Any]


class StructuredOutputResponse(Model):
output: dict[str, Any]


agent = Agent()


AI_AGENT_ADDRESS = "<deployed_agent_address>"


code = """
def do_something():
for i in range(10)
pass
"""

prompt = ContextPrompt(
context="Find and fix the bug in the provided code snippet",
text=code,
)

class Location(Model):
city: str
country: str
temperature: float


prompts = [
ContextPrompt(
context="Find and fix the bug in the provided code snippet",
text=code,
),
StructuredOutputPrompt(
prompt="How is the weather in London today?",
output_schema=Location.schema(),
),
]


@agent.on_event("startup")
async def send_message(ctx: Context):
for prompt in prompts:
await ctx.send(AI_AGENT_ADDRESS, prompt)


@agent.on_message(Response)
async def handle_response(ctx: Context, sender: str, msg: Response):
ctx.logger.info(f"Received response from {sender}: {msg.text}")


@agent.on_message(StructuredOutputResponse)
async def handle_structured_output_response(ctx: Context, sender: str, msg: StructuredOutputResponse):
ctx.logger.info(f"[Received response from ...{sender[-8:]}]:")
response = Location.parse_obj(msg.output)
ctx.logger.info(response)


if __name__ == "__main__":
agent.run()
```

### Local Agent

1. Install the necessary packages:

```bash
pip install requests uagents
```

2. To interact with this agent from a local agent instead, replace `agent = Agent()` in the above with:

```python
agent = Agent(
name="user",
endpoint="http://localhost:8000/submit",
)
```

3. Run the agent:
```bash
python agent.py
```
124 changes: 124 additions & 0 deletions 6-deployed-agents/knowledge-base/openrouter-agent/agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import json
import os
from enum import Enum
from typing import Any

from ai import get_completion
from chat_proto import chat_proto
from uagents import Agent, Context, Model
from uagents.experimental.quota import Protocol, QuotaProtocol
from uagents_core.models import ErrorMessage

AGENT_SEED = os.getenv("AGENT_SEED", "openai-test-agent")
AGENT_NAME = os.getenv("AGENT_NAME", "OpenAI Agent")


class ContextPrompt(Model):
context: str
text: str


class Response(Model):
text: str


class StructuredOutputPrompt(Model):
prompt: str
output_schema: dict[str, Any]


class StructuredOutputResponse(Model):
output: dict[str, Any]


PORT = 8000
agent = Agent(
name=AGENT_NAME,
seed=AGENT_SEED,
port=PORT,
endpoint=f"http://localhost:{PORT}/submit",
)


proto = Protocol(
name="OpenRouter-LLM-Context-Response",
version="0.1.0",
)

struct_proto = Protocol(
name="OpenRouter-LLM-Structured-Response",
version="0.1.0",
)


@proto.on_message(ContextPrompt, replies={Response, ErrorMessage})
async def handle_request(ctx: Context, sender: str, msg: ContextPrompt):
response = get_completion(context=msg.context, prompt=msg.text)
await ctx.send(sender, Response(text=response))


@struct_proto.on_message(
StructuredOutputPrompt, replies={StructuredOutputResponse, ErrorMessage}
)
async def handle_structured_request(
ctx: Context, sender: str, msg: StructuredOutputPrompt
):
response = get_completion(
context="", prompt=msg.prompt, response_schema=msg.output_schema
)
await ctx.send(sender, StructuredOutputResponse(output=json.loads(response)))


agent.include(proto, publish_manifest=True)
agent.include(struct_proto, publish_manifest=True)
agent.include(chat_proto, publish_manifest=True)


### Health check related code
def agent_is_healthy() -> bool:
"""
Implement the actual health check logic here.

For example, check if the agent can connect to a third party API,
check if the agent has enough resources, etc.
"""
condition = True # TODO: logic here
return bool(condition)


class HealthCheck(Model):
pass


class HealthStatus(str, Enum):
HEALTHY = "healthy"
UNHEALTHY = "unhealthy"


class AgentHealth(Model):
agent_name: str
status: HealthStatus


health_protocol = QuotaProtocol(
storage_reference=agent.storage, name="HealthProtocol", version="0.1.0"
)


@health_protocol.on_message(HealthCheck, replies={AgentHealth})
async def handle_health_check(ctx: Context, sender: str, msg: HealthCheck):
status = HealthStatus.UNHEALTHY
try:
if agent_is_healthy():
status = HealthStatus.HEALTHY
except Exception as err:
ctx.logger.error(err)
finally:
await ctx.send(sender, AgentHealth(agent_name=AGENT_NAME, status=status))


agent.include(health_protocol, publish_manifest=True)


if __name__ == "__main__":
agent.run()
53 changes: 53 additions & 0 deletions 6-deployed-agents/knowledge-base/openrouter-agent/ai.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import os
from typing import Any
from openai import OpenAI, OpenAIError


MODEL_ENGINE = os.getenv("MODEL_ENGINE", "MODEL_NAME")
OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY", "YOUR_OPENAI_API_KEY")
HEADERS = {
"Content-Type": "application/json",
"Authorization": f"Bearer {OPENROUTER_API_KEY}",
}


if OPENROUTER_API_KEY is None or OPENROUTER_API_KEY == "YOUR_OPENAI_API_KEY":
raise ValueError(
"You need to provide an API key: https://platform.openai.com/api-keys"
)


client = OpenAI(base_url="https://openrouter.ai/api/v1", api_key=OPENROUTER_API_KEY)


# Send a prompt and context to the AI model and return the content of the completion
def get_completion(
context: str,
prompt: str,
response_schema: dict[str, Any] | None = None,
) -> str:
if response_schema is not None:
response_format = {
"type": "json_schema",
"json_schema": {
"name": response_schema["title"],
"strict": False,
"schema": response_schema,
},
}
else:
response_format = None

try:
response = client.chat.completions.create(
model=MODEL_ENGINE,
messages=[
{"role": "system", "content": context},
{"role": "user", "content": prompt},
],
response_format=response_format,
)
except OpenAIError as e:
return f"An error occurred: {e}"

return response.choices[0].message.content
Loading