Multinut/multinut/explain.py
Dominik Krenn 12c078cd72 feat: add Explainable class for AI-powered code explanations and examples in README.md
chore: update version to 0.3.1 and add OpenAI dependency in setup.py
2025-09-24 11:06:22 +02:00

89 lines
3.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import inspect
import types
from openai import OpenAI
from .env import Environment
class ApiKeyMissingError(Exception):
pass
class Explainable:
API_KEY = None
PROMPT = """
Explain the following Python class code. Use these rules:
- Ignore the Explainable class it's just an internal utility to add the `.explain()` method.
It is not relevant to the explanation as it just adds the `.explain()` method to the class.
Don't even mention it in the explanation. Only mention other inherited classes if they are relevant.
- Focus on the class that is being explained.
- Focus only on what the class and its methods do, practically.
- Do not explain basic Python concepts like `self`, indentation, or decorators.
- Do not guess the purpose or intent of the class — just describe what the code does.
- Do not make suggestions for improvement or style.
- Keep the explanation clear, minimal, and to-the-point.
- Your audience is a competent Python developer.
- Use simple language and avoid jargon.
- Be concise and avoid unnecessary detail.
- Provide the explanation in a single paragraph or more if needed.
"""
@classmethod
def use_env(path: str = ".env"):
env = Environment(path)
Explainable.API_KEY = env.get("OPENAPI_KEY")
@classmethod
def explain(cls) -> str:
if cls.API_KEY is None:
raise ApiKeyMissingError("API key is missing. Please set it using `Explainable.use_env()` or by directly assigning `Explainable.API_KEY`.")
try:
code = inspect.getsource(cls)
client = OpenAI(api_key=cls.API_KEY)
response = client.chat.completions.create(
model="gpt-4",
messages=[
{"role": "system", "content": cls.PROMPT},
{"role": "user", "content": code}
]
)
return response.choices[0].message.content.strip()
except Exception as e:
return f"<could not get class source: {e}>"
def __init_subclass__(cls, **kwargs):
for name, obj in vars(cls).items():
if isinstance(obj, types.FunctionType):
setattr(cls, name, Explainable.wrap_with_explain(obj))
super().__init_subclass__(**kwargs)
@staticmethod
def wrap_with_explain(func):
def _with_explain(*args, **kwargs):
return func(*args, **kwargs)
_with_explain.__name__ = func.__name__
_with_explain.__doc__ = func.__doc__
def explain_func():
if Explainable.API_KEY is None:
raise ApiKeyMissingError("API key is missing. Please set it using `Explainable.use_env()` or by directly assigning `Explainable.API_KEY`.")
try:
code = inspect.getsource(func)
client = OpenAI(api_key=Explainable.API_KEY)
response = client.chat.completions.create(
model="gpt-4",
messages=[
{"role": "system", "content": Explainable.PROMPT},
{"role": "user", "content": code}
]
)
return response.choices[0].message.content.strip()
except Exception as e:
return f"<could not get function explanation: {e}>"
_with_explain.explain = explain_func
return _with_explain