-
Notifications
You must be signed in to change notification settings - Fork 230
Request for comment: Use Org mode properties drawer to configure system prompt/temp for the whole subtree #141
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
Comments
C-u gptel-send
result in mini buffer message: Suffix gptel--infix-provider is not defined or autoloaded as a command
This works on a per-file basis in Org-mode right now when you save the buffer. (Except for Ollama models, where it's more annoying to capture state.) I'm not convinced it's worth adding per-heading support, but I'll think about it. |
@karthink I think it's worth adding. I like to have a mega file where I store all my chats, and each chat is a new heading. Opening a new file for small chats is not worth it. I also put chats under headings where they make sense. For example, I have a heading |
Take a look at "inline-tasks". That would allow to change the sysprompt by adding an "isolated" subheading, which means that you can end the body of the headline using a: "*************** END". I agree with the inheritance. An example, in an old suggestion: |
This sounds good to me, but I'm not sure I understand what you mean fully. You can already have an What's missing here is the specification of the model and backend parameters for this specific chat. So if these are not the default values, you would have to change them manually from elisp or from the transient menu. If other chat logs in the same file use different models/backends, you'd have to switch manually each time before continuing those chats. Is this the problem you want to solve? |
Yes, I like to be able to set all the parameters using property drawers. I want the chats to record all their inputs in the text file, so that years later, I can revisit them and know exactly what parameters etc. I used. Another problem is that the prefix ( |
There are some ambiguities in the behavior of gptel that I need to sort out to do this, I'll explain soon.
This (archival) is a different -- and simpler -- issue from being able to set the parameters. You can do this right now with a little elisp: (org-entry-put nil "GPTEL_TEMPERATURE" gptel-temperature)
(org-entry-put nil "GPTEL_MODEL" gptel-model)
(org-entry-put nil "GPTEL_BACKEND" gptel-backend) and so on, placed in a function in
I don't see how a regexp can work. What is the corresponding string that will be inserted? Note that the prompt prefix doesn't have to be a heading at all, it can be a string like "Prompt:" (with the corresponding response prefix as "Response:\n") and you can have chats at any heading depth. |
Okay, I gave this some thought, and there are many subtle ambiguities in the exact behavior of gptel in the presence of the suggested org properties that I need to resolve before I can add it. To explain these ambiguities, here is some background: gptel has slightly different behavior depending on whether gptel-mode is turned on. The main differences are the visual indicators (header bar or mode-line) and saving the state of the chat to disk when writing the buffer to disk. Presently ambiguous or undesirable behaviors:
There are a couple more ambiguities to resolve, but I'd like to get some input on the above to find the least-surprising behavior. |
Nothing. I can easily create the new heading using org-mode hotkeys itself, there is no need to insert it automatically.
This is a good workaround, but I prefer headings, as they integrate into org naturally. Plus, in the future, headings allow for some fancy features. E.g., we can use tags to exclude a subtree from the conversation history. We can use the natural branching of the org headings to create branching conversations. (By branching, I mean like when you edit a message in ChatGPT’s website and the UI creates a new branch of the conversation.) |
My understanding from talking to Org maintainers is that inline tasks should be avoided whenever possible -- they're having some trouble with parsing that syntax reliably. This is similar to how the preferred LaTeX math delimiters in Org are |
Then you can
This is a good idea, but I'd prefer not to add more special behavior, like tags with special meanings for gptel. Perhaps we can reuse a state that already has special meaning in Org, like excluding a subtree if it's commented (as in
I already do this often with EDIT: I might have misunderstood what you meant by "branching conversations". When in a branch, I'm guessing the context from before the branch point is included in the conversation. Setting |
Hi! But if you agree inline tasks could be the perfect tool, it would be necessary in the future just adjusting to the new syntax. |
Hi there, I'm a heavy user of gpt and I submitted a PR to another package gptel is extremely flexible, but when I'm working with gpt i need very precise control over the system prompt and temperature (and in the future, function calling, etc) I'm very used to Org mode inheritance properties workflow for a whole lot of tasks and it is ideal for this use case. (and not having a to repeat these settings is nice) This really shows lisp is a curse because it is so easy to craft a solution that fits your personal needs 99%, and now we have 20+ elisp packages for the gpt related function :) But still, I wish one or two solutions will emerge as the dominate project, so it will have a longer and stable life span, that's why I'm requesting for comments here. Even though I'm not using gptel as my main for the above reason, I wish it could have the best overall feature set. Thanks |
@doctorguile I am currently using a yas-snippet that expands into: #+begin_src jupyter-python :kernel py_base :session chatgpt_1 :async yes :exports both
res = openai_chat_complete(
##
# model="gpt-4-0314",
# model="gpt-4-0613",
model="gpt-4-turbo",
# model="gpt-3.5-turbo",
##
messages=[
# {"role": "system", "content": """You are a senior programmer. You are an expert in functional programming. You use design patterns as appropriate."""},
{"role": "user", "content": r"""
"""},
],
temperature=0,
interactive=True,
)
print_chat_streaming(res, copy_mode="chat2")
#+end_src I have import openai
import pynight.common_openai
from pynight.common_openai import (
openai_key_get,
setup_openai_key,
print_chat_streaming,
openai_chat_complete,
)
pynight.common_openai.openai_key = "..."
openai.api_key = pynight.common_openai.openai_key in my IPython startup files. Together with these two elisp functions which allow me to easily copy the answer as code to be added to the source cell: (defun night/org-babel-result-get ()
"Return the result of the current source block as a string.
Assumes that the source block has already been executed."
(interactive)
(save-excursion
(let ((result-beg (org-babel-where-is-src-block-result))
result-end)
(unless result-beg
(error "No result found for the current source block"))
(goto-char result-beg)
(setq result-end (org-babel-result-end))
(let* ((raw-result (buffer-substring-no-properties result-beg result-end))
(result (string-trim raw-result))
(lines (split-string result "\n"))
(first-relevant-line-index
(cl-position-if-not
(lambda (line)
(string-match-p "^\\(#\\+\\|:RESULTS:[ \t\n]*\\)" line))
lines))
(last-relevant-line-index
(cl-position-if
(lambda (line)
(not
(string-match-p "^\\(: ----+\\|:END:\\)[ \t]*$" line)))
lines :from-end t))
(result
(mapconcat
'identity
(cl-subseq
lines
(or first-relevant-line-index 0)
(cond
(last-relevant-line-index
(+ 1 last-relevant-line-index))
(t
(+ 0 (length lines)))))
"\n"))
(result (replace-regexp-in-string "^\\(: \\|:$\\)" "" result t t))
(result (string-trim result)))
;; (message "first: %s, last: %s, lines: %s" first-relevant-line-index last-relevant-line-index lines)
(when (called-interactively-p)
(kill-new result)
(message "%s" result))
result))))
(defun night/org-babel-copy-as-chat ()
"Copies the result section of the current source block as the last message in an LLM chat."
(interactive)
(let* (
(last-msg (night/org-babel-result-get))
(assistant
(concat
" {\"role\": \"assistant\", \"content\": r\"\"\""
last-msg
"\"\"\"},"))
(chat
(concat
assistant
"\n {\"role\": \"user\", \"content\": r\"\"\"\n \n \"\"\"},")))
(kill-new chat))) I am finding this solution the most extensible, and it's robust. |
@doctorguile I'm not opposed to using org properties in principle, but I need to resolve the behavioral ambiguities mentioned above before I can add it. There are also performance considerations with scanning the parse tree for each request. (This is assuming
This isn't a problem as I see it. gptel is opinionated about its interface and not to the taste of someone who prefers more structured interaction, for instance. They've got chatgpt-shell, org-ai and many more to choose from. The alternative is a dominant solution that fits everyone's personal needs at 66%, and no one's really satisfied with the experience. |
* gptel.el: Load gptel-org after Org is loaded. * gptel-org.el (gptel-org--send-with-props): Advise `gptel-send`, `gptel-request` and `gptel--suffix-send` to use gptel's local config (stored under the current Org heading) when applicable. Advice is a hacky way to do it, but this is the simplest without inserting explicit indirection or stuffing `derived-mode-p`-based dispatch code in many more places in gptel.
I finally had some time to think about this feature, so I've added support for "stateless" gptel configuration via Org properties. It's on the Here's how it works:
No command/variable names are final. The load order is a little funky since I try to avoid loading Org in gptel. Please let me know if it builds correctly for you. I would also appreciate everyone's feedback on the feature(s) -- again, it's on the |
* gptel.el: Load gptel-org after Org is loaded. * gptel-org.el (gptel-org--send-with-props): Advise `gptel-send`, `gptel-request` and `gptel--suffix-send` to use gptel's local config (stored under the current Org heading) when applicable. Advice is a hacky way to do it, but this is the simplest without inserting explicit indirection or stuffing `derived-mode-p`-based dispatch code in many more places in gptel.
* gptel.el: Load gptel-org after Org is loaded. * gptel-org.el (gptel-org--send-with-props): Advise `gptel-send`, `gptel-request` and `gptel--suffix-send` to use gptel's local config (stored under the current Org heading) when applicable. Advice is a hacky way to do it, but this is the simplest without inserting explicit indirection or stuffing `derived-mode-p`-based dispatch code in many more places in gptel.
* gptel.el: Load gptel-org after Org is loaded. * gptel-org.el (gptel-org--send-with-props): Advise `gptel-send`, `gptel-request` and `gptel--suffix-send` to use gptel's local config (stored under the current Org heading) when applicable. Advice is a hacky way to do it, but this is the simplest without inserting explicit indirection or stuffing `derived-mode-p`-based dispatch code in many more places in gptel.
Hi Karthik, I just tried out your feature-org branch. I tried to create a few org headers, create a thread, duplicate the thread to go a different direction, etc. It works so far, very impressed. Using text properties without explicit text delimiter is very clever and make the output clean. However, I keep wondering if there's any error when I save the buffer seeing the GPTEL_BOUNDS updated at the top of the org file as I can't easily eyeball and verify. I am sure someone might have asked before, is there a way to make the AI responded text rendered in a different shades of color so we can visually see the boundary of the user input / ai response? Thanks! |
There's nothing in gptel for this, but it would be very easy to add something. Here's an example: And here's the code if you want to try it: (defface gptel-response-face '((t :inherit mode-line-highlight))
"Face used to highlight LLM responses by gptel")
;; Emacs 28.1+ only, set to nil if you're on Emacs 27 or lower
(defvar gptel-use-response-divider t
"Whether LLM responses should be demarcated with lines.")
;; Highlight response region
(defun gptel-colorize-region (beg end)
(message "beg and end are %S:%S" beg end)
(when (and gptel-mode beg end)
(add-text-properties
beg end
'(font-lock-face (:inherit gptel-response-face :extend t)))
(when gptel-use-response-divider
(put-text-property
(1- beg) beg 'display (concat "\n" (make-separator-line)))
(unless (>= end (point-max))
(put-text-property
end (1+ end) 'display (concat "\n" (make-separator-line)))))))
;; Auto-highlight after inserting a response
(add-hook 'gptel-post-response-functions #'gptel-colorize-region)
;; Highlight from metadata on enabling gptel-mode
;; Org mode only, can write a more general version if necessary
(defun gptel-org-colorize-buffer ()
(when (and (derived-mode-p 'org-mode) gptel-mode)
(condition-case-unless-debug val
(save-restriction
(when-let ((bounds (org-entry-get (point-min) "GPTEL_BOUNDS")))
(mapc (pcase-lambda (`(,beg . ,end)) (gptel-colorize-region beg end))
(read bounds))))
(error "Coloring gptel responses failed with error: %S" val))))
(add-hook 'gptel-mode-hook #'gptel-org-colorize-buffer) Let me know what you think. |
* gptel.el: Load gptel-org after Org is loaded. * gptel-org.el (gptel-org--send-with-props): Advise `gptel-send`, `gptel-request` and `gptel--suffix-send` to use gptel's local config (stored under the current Org heading) when applicable. Advice is a hacky way to do it, but this is the simplest without inserting explicit indirection or stuffing `derived-mode-p`-based dispatch code in many more places in gptel.
that was awesome. completely addressed the visualization issue, you rock! btw, what do you think of adding image query support?
Not sure what syntax to use in other context. Maybe it's good enough to support in org and markdown Thanks |
The above is throwaway code for demonstration purposes, so it's fragile. You can delete the separator bars and have no way to get them back, for instance. I can fix it up and maybe add it as an optional feature to gptel instead. |
Hi @karthink I turned on debug log and noticed something minor.
will try to keep you posted if I find anything else interesting Thanks again. example org file (I edited the LLM output to save space here, so the bounds are off if you try to use it as is, but should be easily reproducible).
payload
|
* gptel.el: Load gptel-org after Org is loaded. * gptel-org.el (gptel-org--send-with-props): Advise `gptel-send`, `gptel-request` and `gptel--suffix-send` to use gptel's local config (stored under the current Org heading) when applicable. Advice is a hacky way to do it, but this is the simplest without inserting explicit indirection or stuffing `derived-mode-p`-based dispatch code in many more places in gptel.
* gptel.el: Load gptel-org after Org is loaded. * gptel-org.el (gptel-org--send-with-props): Advise `gptel-send`, `gptel-request` and `gptel--suffix-send` to use gptel's local config (stored under the current Org heading) when applicable. Advice is a hacky way to do it, but this is the simplest without inserting explicit indirection or stuffing `derived-mode-p`-based dispatch code in many more places in gptel.
* gptel.el: Load gptel-org after Org is loaded. * gptel-org.el (gptel-org--send-with-props): Advise `gptel-send`, `gptel-request` and `gptel--suffix-send` to use gptel's local config (stored under the current Org heading) when applicable. Advice is a hacky way to do it, but this is the simplest without inserting explicit indirection or stuffing `derived-mode-p`-based dispatch code in many more places in gptel.
* gptel.el: Load gptel-org after Org is loaded. * gptel-org.el (gptel-org--send-with-props): Advise `gptel-send`, `gptel-request` and `gptel--suffix-send` to use gptel's local config (stored under the current Org heading) when applicable. Advice is a hacky way to do it, but this is the simplest without inserting explicit indirection or stuffing `derived-mode-p`-based dispatch code in many more places in gptel.
Yes, I am aware of this. I'm trying to keep the mental model for the user as simple as possible, so gptel really does send everything above the cursor, including Org properties, keywords, tags etc. I don't want to add any special behavior yet, although I might in the future.
Is this the prompt prefix that gptel inserts after the LLM response? If yes, what is your value of |
It's the default
So it seems For the org properties, I have some sensitive info (that don't want to get logged) So I'll patch my own copy for now if this is a low priority in general. Thanks |
@karthink There is some warning in |
I'm not going to force the inheritance for gptel precisely because of the performance issues. This is what I was I worried about above, but it turns out it's even worse because the parse tree (as cached by org-element) does not contain this property info in most cases. If I force inheritance here, every user who's using gptel in Org mode will be paying the performance penalty, since the
If you are okay with the performance hit, you can selectively add the
So you could run (setopt org-use-property-inheritance "GPTEL_.*") |
* gptel.el: Load gptel-org after Org is loaded. * gptel-org.el (gptel-org--send-with-props): Advise `gptel-send`, `gptel-request` and `gptel--suffix-send` to use gptel's local config (stored under the current Org heading) when applicable. Advice is a hacky way to do it, but this is the simplest without inserting explicit indirection or stuffing `derived-mode-p`-based dispatch code in many more places in gptel.
* gptel.el: Load gptel-org after Org is loaded. * gptel-org.el (gptel-org--send-with-props): Advise `gptel-send`, `gptel-request` and `gptel--suffix-send` to use gptel's local config (stored under the current Org heading) when applicable. Advice is a hacky way to do it, but this is the simplest without inserting explicit indirection or stuffing `derived-mode-p`-based dispatch code in many more places in gptel.
* gptel.el: Load gptel-org after Org is loaded. * gptel-org.el (gptel-org--send-with-props): Advise `gptel-send`, `gptel-request` and `gptel--suffix-send` to use gptel's local config (stored under the current Org heading) when applicable. Advice is a hacky way to do it, but this is the simplest without inserting explicit indirection or stuffing `derived-mode-p`-based dispatch code in many more places in gptel.
* gptel.el: Load gptel-org after Org is loaded. * gptel-org.el (gptel-org--send-with-props): Advise `gptel-send`, `gptel-request` and `gptel--suffix-send` to use gptel's local config (stored under the current Org heading) when applicable. Advice is a hacky way to do it, but this is the simplest without inserting explicit indirection or stuffing `derived-mode-p`-based dispatch code in many more places in gptel.
* gptel.el: Load gptel-org after Org is loaded. * gptel-org.el (gptel-org--send-with-props): Advise `gptel-send`, `gptel-request` and `gptel--suffix-send` to use gptel's local config (stored under the current Org heading) when applicable. Advice is a hacky way to do it, but this is the simplest without inserting explicit indirection or stuffing `derived-mode-p`-based dispatch code in many more places in gptel.
* gptel.el: Load gptel-org after Org is loaded. * gptel-org.el (gptel-org--send-with-props): Advise `gptel-send`, `gptel-request` and `gptel--suffix-send` to use gptel's local config (stored under the current Org heading) when applicable. Advice is a hacky way to do it, but this is the simplest without inserting explicit indirection or stuffing `derived-mode-p`-based dispatch code in many more places in gptel.
* gptel.el: Load gptel-org after Org is loaded. * gptel-org.el (gptel-org--send-with-props): Advise `gptel-send`, `gptel-request` and `gptel--suffix-send` to use gptel's local config (stored under the current Org heading) when applicable. Advice is a hacky way to do it, but this is the simplest without inserting explicit indirection or stuffing `derived-mode-p`-based dispatch code in many more places in gptel.
* gptel.el: Load gptel-org after Org is loaded. * gptel-org.el (gptel-org--send-with-props): Advise `gptel-send`, `gptel-request` and `gptel--suffix-send` to use gptel's local config (stored under the current Org heading) when applicable. Advice is a hacky way to do it, but this is the simplest without inserting explicit indirection or stuffing `derived-mode-p`-based dispatch code in many more places in gptel.
* gptel.el: Load gptel-org after Org is loaded. * gptel-org.el (gptel-org--send-with-props): Advise `gptel-send`, `gptel-request` and `gptel--suffix-send` to use gptel's local config (stored under the current Org heading) when applicable. Advice is a hacky way to do it, but this is the simplest without inserting explicit indirection or stuffing `derived-mode-p`-based dispatch code in many more places in gptel.
* gptel.el: Load gptel-org after Org is loaded. * gptel-org.el (gptel-org--send-with-props): Advise `gptel-send`, `gptel-request` and `gptel--suffix-send` to use gptel's local config (stored under the current Org heading) when applicable. Advice is a hacky way to do it, but this is the simplest without inserting explicit indirection or stuffing `derived-mode-p`-based dispatch code in many more places in gptel.
* gptel.el: Load gptel-org after Org is loaded. * gptel-org.el (gptel-org--send-with-props): Advise `gptel-send`, `gptel-request` and `gptel--suffix-send` to use gptel's local config (stored under the current Org heading) when applicable. Advice is a hacky way to do it, but this is the simplest without inserting explicit indirection or stuffing `derived-mode-p`-based dispatch code in many more places in gptel.
* gptel.el: Load gptel-org after Org is loaded. * gptel-org.el (gptel-org--send-with-props): Advise `gptel-send` and `gptel--suffix-send` to use gptel's local config (stored under the current Org heading) when applicable. Advice is a hacky way to do it, but this is the simplest option without explicit indirection via `derived-mode-p`-based dispatch code in many more places in gptel.
The |
* gptel.el: Load gptel-org after Org is loaded. * gptel-org.el (gptel-org--send-with-props): Advise `gptel-send` and `gptel--suffix-send` to use gptel's local config (stored under the current Org heading) when applicable. Advice is a hacky way to do it, but this is the simplest option without explicit indirection via `derived-mode-p`-based dispatch code in many more places in gptel.
@doctorguile Property drawers are now stripped from the prompt before sending, see |
* gptel.el: Load gptel-org after Org is loaded. * gptel-org.el (gptel-org--send-with-props): Advise `gptel-send` and `gptel--suffix-send` to use gptel's local config (stored under the current Org heading) when applicable. Advice is a hacky way to do it, but this is the simplest option without explicit indirection via `derived-mode-p`-based dispatch code in many more places in gptel.
I ran into the same bug as #135
But ultimately even if I didn't run into this bug, I think the transient menu is not as convenient if you have a big list of predefined combos.
For an example of very convenient way to have access to a set of user defined system prompt/model/temperature, etc see
https://github.com/Bin-Huang/chatbox
where you can set a system prompt , temperature and model for each chat thread, and continue on that thread without having to repeat the context (sys prompt, etc)
the ideal way to do this in emacs is in org mode, having a header and set system prompt , temperature and model as property drawer.
then all subtree of that header will inherit these properties and you can have a lot of useful prompt / temperature always ready to go
example
Does anyone think this is a good addition? This is how org is used (inheriting parent props) for a lot of other use cases
The text was updated successfully, but these errors were encountered: