|
""" |
|
This code adapts the following code for Linear.app. It is not official code. |
|
https://github.com/huggingface/huggingface_hub/blob/main/src/huggingface_hub/_webhooks_server.py |
|
https://github.com/huggingface/huggingface_hub/blob/main/src/huggingface_hub/_webhooks_payload.py |
|
|
|
Currently, it only supports a small subset of the Issue Object. |
|
https://studio.apollographql.com/public/Linear-API/variant/current/schema/reference/objects/Issue |
|
|
|
You need to set `api_key = linear-api-key` and `webhook_secret = linear-webhook-secret` in your `.env` file. |
|
|
|
Since the local URL changes with each startup, the URL is updated in the Linear API at startup. |
|
At startup, it overwrites the webhook with the label specified by `target_webhook_label` (default value: Gradio). |
|
|
|
You need to pre-install gradio, fastapi, and pydantic. |
|
You need to describe your Linear API key and Linear webhook secret in the `.env` file. |
|
Also, since this example is only for Update, please create a Webhook with the label Gradio beforehand. |
|
|
|
** Copyright Notice for Linear.app Adaptation ** |
|
# Copyright 2025-present, Akihito Miyazaki |
|
# |
|
# 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. |
|
|
|
** License Notice for Hugging Face Hub Library ** |
|
# Copyright 2023-present, the HuggingFace Inc. team. |
|
# |
|
# 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. |
|
|
|
This code includes parts of the Hugging Face Hub library, which is licensed under the Apache License, Version 2.0. |
|
The full text of the license can be found at: http://www.apache.org/licenses/LICENSE-2.0 |
|
""" |
|
|
|
import os |
|
from pprint import pprint |
|
|
|
import gradio as gr |
|
from smolagents import CodeAgent, HfApiModel |
|
|
|
from linear_api_utils import execute_query |
|
from gradio_webhook_server import WebhooksServer |
|
from gradio_webhook_payload import WebhookPayload |
|
from sleep_per_last_token_model import SleepPerLastTokenModelLiteLLM |
|
|
|
|
|
""" |
|
LINEAR_API_KEY="lin_api_***" |
|
HF_TOKEN = "hf_***" |
|
LINEAR_WEBHOOK_KEY="lin_wh_***" |
|
GROQ_API_KEY = "gsk_***" |
|
""" |
|
|
|
|
|
def get_env_value(key, is_value_error_on_null=True): |
|
value = os.getenv(key) |
|
if value is None: |
|
from dotenv import load_dotenv |
|
|
|
load_dotenv() |
|
value = os.getenv(key) |
|
if is_value_error_on_null and value is None: |
|
raise ValueError(f"Need {key} on secret or .env(If running on local)") |
|
return value |
|
|
|
|
|
|
|
LINEAR_ISSUE_LABEL = "huggingface-public" |
|
LINEAR_WEBHOOK_LABEL = "Huggingface" |
|
|
|
|
|
groq_api_key = get_env_value("GROQ_API_KEY") |
|
api_key = get_env_value("LINEAR_API_KEY") |
|
webhook_key = get_env_value("LINEAR_WEBHOOK_KEY") |
|
|
|
|
|
if api_key is None: |
|
raise ValueError("Need LINEAR_API_KEY on secret") |
|
if webhook_key is None: |
|
raise ValueError("Need LINEAR_WEBHOOK_KEY on secret") |
|
|
|
webhook_query_text = """ |
|
query { |
|
webhooks{ |
|
nodes { |
|
id |
|
label |
|
url |
|
} |
|
} |
|
} |
|
""" |
|
target_webhook_label = LINEAR_WEBHOOK_LABEL |
|
target_webhook_id = None |
|
result = execute_query("webhook", webhook_query_text, api_key) |
|
for webhook in result["data"]["webhooks"]["nodes"]: |
|
if target_webhook_label == webhook["label"]: |
|
target_webhook_id = webhook["id"] |
|
|
|
|
|
app = None |
|
|
|
|
|
""" |
|
model = HfApiModel( |
|
max_tokens=100, |
|
temperature=0.5, |
|
model_id="google/gemma-2-2b-it", |
|
custom_role_conversions=None, |
|
token=hf_token, |
|
) |
|
""" |
|
|
|
|
|
def update(): |
|
|
|
|
|
return "", "" |
|
|
|
|
|
|
|
def load_text(path): |
|
with open(path, "r") as f: |
|
return f.read() |
|
|
|
|
|
def save_text(path, text): |
|
with open(path, "w") as f: |
|
f.write(text) |
|
|
|
|
|
def update_text(): |
|
return app.issue, app.output |
|
|
|
|
|
with gr.Blocks() as demo: |
|
gr.HTML("""<h1>Linear.app Webhook Server</h1> |
|
<p>This is Demo of Direct Webhook-triggered AIAgen</p> |
|
<p>it's still just simple code,you have to reload when webhooked.</p> |
|
<p><b>Imagine an agent, responding instantly.</b></p> |
|
<p>Technically Gradio have no way to update without action<p> |
|
<p></p><br> |
|
<p>I'm confused by Hugging Face's new pricing system. I'm worried about potentially massive inference API bills, so I switched to Groq.</p> |
|
<p>I believe my use of the Groq API is currently compliant with Hugging Face's Content Policy.</p> |
|
<p>If you have any questions, please disable the Space or contact me before taking any action against my account. Thank you for your understanding.</p> |
|
""") |
|
with gr.Row(): |
|
with gr.Column(): |
|
gr.Markdown("## Issue") |
|
|
|
issue = gr.Markdown("issue") |
|
with gr.Column(): |
|
gr.Markdown("## Agent advice(Don't trust them completely)") |
|
|
|
output = gr.Markdown("agent result") |
|
demo.load(update_text, inputs=None, outputs=[issue, output]) |
|
|
|
|
|
|
|
|
|
app = WebhooksServer( |
|
ui=demo, |
|
webhook_secret=webhook_key, |
|
) |
|
|
|
app.output = "join course" |
|
app.issue = "how to learn smolagent" |
|
|
|
|
|
@app.add_webhook("/linear_webhook") |
|
async def updated(payload: WebhookPayload): |
|
def generate_agent(): |
|
model = SleepPerLastTokenModelLiteLLM( |
|
max_tokens=250, |
|
temperature=0.5, |
|
model_id="groq/llama3-8b-8192", |
|
api_base="https://api.groq.com/openai/v1/", |
|
api_key=groq_api_key, |
|
) |
|
agent = CodeAgent( |
|
model=model, |
|
tools=[], |
|
max_steps=1, |
|
verbosity_level=1, |
|
grammar=None, |
|
planning_interval=None, |
|
name=None, |
|
description=None, |
|
) |
|
return agent |
|
|
|
pprint(payload.dict(), indent=4) |
|
|
|
data = payload.dict()["data"] |
|
has_label = True |
|
if LINEAR_ISSUE_LABEL: |
|
has_label = False |
|
for label in data["labels"]: |
|
if label["name"] == LINEAR_ISSUE_LABEL: |
|
has_label = True |
|
|
|
if has_label: |
|
text = data["description"] |
|
app.issue = text |
|
|
|
agent = generate_agent() |
|
result = agent.run(f"how to solve this issue:{text}") |
|
app.output = result |
|
|
|
return {"message": "ok"} |
|
|
|
|
|
def webhook_update(url): |
|
webhook_update_text = """ |
|
mutation { |
|
webhookUpdate( |
|
id: "%s" |
|
input:{ |
|
url:"%s" |
|
} |
|
) { |
|
success |
|
} |
|
} |
|
""" % (target_webhook_id, url) |
|
result = execute_query("webhook_update", webhook_update_text, api_key) |
|
|
|
|
|
if __name__ == "__main__": |
|
app.launch(webhook_update=webhook_update) |
|
|