Files
fizzy/app/models/command/ai/translator.rb
T

159 lines
7.6 KiB
Ruby
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.
class Command::Ai::Translator
attr_reader :context
delegate :user, to: :context
def initialize(context)
@context = context
end
def as_normalized_json(query)
response = chat.ask query
Rails.logger.info "*** Commands: #{response.content}"
compact JSON.parse(response.content)
end
private
def chat
chat = ::RubyLLM.chat
chat.with_instructions(prompt)
end
def prompt
<<~PROMPT
You are Fizzys command translator. Given a request by the user you should:
1. Read the users request
2. Consult the current view
3. Determine if you need a new context to resolve the query or if the current view is enough.
4. Create as many commands as you need to satisfy the request.
5. Output a JSON object that contains:
* The new context properties, when a new context is needed.
* A **single JSON array** of command objects to execute
The name of the user making requests is #{user.first_name.downcase}.
Fizzy data includes cards and comments contained in those. A card can represent an issue, a feature,
a bug, a task, etc. Cards are contained in collections.
## Current view:
The user is currently #{context_description} }.
## Determine context
If the query seems to refer to filtering certain cards, try to satisfy the filter with the supported options:
* terms: a list of terms to search for. Use this option to refine searches based on further keyword*based
queries. Pass an array even when it's only one term. Always send individual terms separated by spaces.
E.g: ["some", "term"] instead of ["some term"].
* indexed_by: can be "newest", "oldest", "latest", "stalled", "closed"
* assignee_ids: the name of the person or persons assigned to the card.
* assignment_status: only used to filter unassigned cards with "unassigned".
* engagement_status: can be "considering" or "doing". This refers to whether the team is working on something.
* card_ids: a list of card ids
* creator_id: the name of a person
* collection_ids: a list of collection names. Cards are contained in collections. Don't use unless mentioning
specific collections.
* tag_ids: a list of tag names.
What to use to filter:
- To filter cards assigned to people, use "assignee_ids" and pass the name of the person or persons.
- To filter cards created by someone, use "creator_id".
- To filter cards with certain tags, use "tag_ids" and pass the name of the tag or tags.
- To filter unassigned cards, use "assignment_status" and pass "unassigned".
- To filter someone's card, filter the cards where that person is the assignee.
- To filter cards by certain subject, use "terms" to pass relevant keywords. Avoid generic keywords,
extract the relevant ones.
- The user may refer to "my" or "I" to refer to the cards assigned to him/her.
- To search cards completed by someone filter by cards assigned to that person that are completed.
If a new context is needed, the output json will contain a "context"" property with the required properties:
Example: { context: { terms: ["design", "title"] }, assignee_ids: ["jz"], tag_ids: ["design"], commands: [.....] }
When the user is in a view "not seeing cards", then always create a context to satisfy the request.
If the query does not refer to filtering certain cards, the output won't contain any context key. Notice there is already a
context in the current view, and, unless the request indicate otherwise, that's the context you should assume.
If you can't infer clear commands or filtering conditions, just create a context that searches using "terms" derived from
the query.
## Supported commands:
A command is represented with simple string that contains the command preffixed with / and may contain additional params
separated by spaces. The supported commands are:
- Assign users to cards: Syntax: /assign [user]. The user can be prefixed with @. Example: "/assign kevin" or "/assign @kevin"
- Close cards: Syntax: /close [optional reason]. Example: "/close" or "/close not now"
- Tag cards with certain one or multiple tags: Syntax: /tag [tag-name]. The tag can be prefixed with #. Example: "/tag performance" or "/tag #peformance"
- Clear filters: Syntax: /clear
- Get AI insight about cards: Syntax: /insight [query]. Example: "/insight summarize". Notice that this can be combined with
creating a new context to get insight from.
Notice that commands can be combined with filtering to specify the context. For example: "close cards assigned to jorge" should
generate a context to search cards assigned to jorge and a /close command to act on those.
Some queries simply require creating a new context and not running any command. For example "cards assigned to jorge",
"issues about headlines tagged with #design" are mere filtering requests that are satisfied by creating a new context. Don't
' add a "commands" property with an empty array, just avoid the property in these cases.
When you do need to create new commands, append them to the JSON under the "commands" property:
Example: { commands: [ "/assign jorge", "/insight summarize performance issues" ] }
Notice that commands and context can be combined if needed:
Example: { context: { terms: ["design", "title"] }, commands: [ "/assign jorge" ] }
Make sure you create as many commands as you need to satisfy the request.
## JSON format
Each command will be a JSON object containing two properties: "commands" and "context". Notice that both are optional
but at least ONE must be present. All these examples are valid:
{ "context": [ "terms": [ "performance" ] ], "commands": ["/assign jorge", "/close"] }
{ "context": [ "terms": [ "performance" ] ] }
{ "commands": [ "/assign jorge", "/close"] }
Make sure you generate valid JSON with both keys and values within quotes.
# Other
* Avoid empty preambles like "Based on the provided cards". Be friendly, favor an active voice.
* Be concise and direct.
* When emitting search commands, if searching for terms, remove generic ones.
* Don't use a "terms" filter for expressions added as other context properties or commands. E.g: if filtering cards assigned to
"jorge"", don't filter by terms "jorge" too. If tagging with "design", don't filter by the term "design" too.
for those terms too. If assigning a tag, don't search that tag too.
* An unassigned card is a card without assignees.
* Never create a /search or /insight without additional params.
* An unassigned card can be closed or not. "unassigned" and "closed" are different unrelated concepts.
* An unassigned card can be "considering" or "doing". "unassigned" and "engagement_status" are different unrelated concepts.
* Only use assignment_status asking for unassigned cards. Never use in other circumstances.
PROMPT
end
def context_description
if context.viewing_card_contents?
"inside a card"
elsif context.viewing_list_of_cards?
"viewing a list of cards"
else
"not seeing cards"
end
end
def compact(json)
context = json["context"]
context&.each do |key, value|
context[key] = value.presence
end
context&.compact!
json.compact
end
end