1
0
mirror of https://github.com/hustcer/deepseek-review.git synced 2026-05-13 05:16:05 +08:00

7 Commits
v1.9 ... v1.10

Author SHA1 Message Date
hustcer
06d80785b1 Bump to v1.10 2025-02-12 23:30:23 +08:00
Justin Ma
84dbd263c9 fix: Improve error handling for API response in streaming-output (#117)
* fix: Improve error handling for API response in streaming-output

* Fix token error

* Fix error
2025-02-12 23:24:43 +08:00
hustcer
c0d78b92b9 feat: Separate the reasoning and the content part in the output of DeepSeek (#115) 2025-02-12 19:30:26 +08:00
hustcer
5652f31856 perf: Save the last reply of the model in memory instead of a file (#113) 2025-02-12 18:26:40 +08:00
hustcer
68e582fb95 Update README 2025-02-12 17:40:54 +08:00
Justin Ma
534d6230b7 chore: Refine DeepSeek review output, clarify debug logs, and adjust error messaging for better context (#111) 2025-02-12 14:26:39 +08:00
Justin Ma
6af37c1d98 chore: Make awk version check for both awk and gawk (#109) 2025-02-12 12:21:03 +08:00
8 changed files with 255 additions and 23 deletions

2
.gitignore vendored
View File

@@ -1,5 +1,5 @@
.env
.env.dev
.env.local
.last-reply.json
config.yml
prompts.yaml

View File

@@ -1,6 +1,25 @@
# Changelog
All notable changes to this project will be documented in this file.
## [1.10.0] - 2025-02-12
### Features
- Separate the reasoning and the content part of the output (#115)
### Bug Fixes
- Improve error handling of API response for streaming-output (#117)
### Miscellaneous Tasks
- Make `awk` version check works for both `awk` and `gawk` (#109)
- Refine DeepSeek review output, clarify debug logs, and adjust error messaging for better context (#111)
### Performance
- Save the last reply of the model to db instead of a file (#113)
## [1.9.0] - 2025-02-12
### Bug Fixes

View File

@@ -20,6 +20,7 @@
## Planned Features
- [ ] **Trigger Code Review on Mention**: Automatically initiate code review when the `github-actions` bot is mentioned in a PR comment.
- [ ] **Generate Commit Message Locally**: Generate a commit message for the code changes in any local repository.
## Code Review with GitHub Action

View File

@@ -18,6 +18,7 @@
## 计划支持特性
- [ ] **通过提及触发代码审查**:当 PR 评论中提及 `github-actions bot` 时,自动触发代码审查
- [ ] **本地生成提交信息**:为本地仓库的代码变更生成 Commit Message
## 通过 GitHub Action 进行代码审查

View File

@@ -5,6 +5,9 @@
words:
- psql
- MPFR
- nuon
- nuons
- ECODE
- endfor
- dotenv

View File

@@ -1,7 +1,7 @@
{
"name": "deepseek-review",
"version": "1.9.0",
"actionVer": "v1.9",
"version": "1.10.0",
"actionVer": "v1.10",
"author": "hustcer",
"license": "MIT",
"github": "https://github.com/hustcer/deepseek-review",

187
nu/kv.nu Normal file
View File

@@ -0,0 +1,187 @@
# NOTE: This file was copied from https://github.com/nushell/nushell/tree/main/crates/nu-std/std-rfc/kv
# And will be removed after the Nu v0.103 release
# More examples could be found here: https://github.com/nushell/nushell/discussions/14965
#
# kv module
#
# use std-rfc/kv *
#
# Easily store and retrieve key-value pairs
# in a pipeline.
#
# A common request is to be able to assign a
# pipeline result to a variable. While it's
# not currently possible to use a "let" statement
# within a pipeline, this module provides an
# alternative. Think of each key as a variable
# that can be set and retrieved.
# Stores the pipeline value for later use
#
# If the key already exists, it is updated to the new value provided.
export def "kv set" [
key: string
value_or_closure?: any
--return (-r): string # Whether and what to return to the pipeline output
--universal (-u)
] {
# Pipeline input is preferred, but prioritize
# parameter if present. This allows $in to be
# used in the parameter if needed.
let input = $in
# If passed a closure, execute it
let arg_type = ($value_or_closure | describe)
let value = match $arg_type {
closure => { $input | do $value_or_closure }
_ => ($value_or_closure | default $input)
}
# Store values as nuons for type-integrity
let kv_pair = {
session: '' # Placeholder
key: $key
value: ($value | to nuon)
}
let db_open = (db_setup --universal=$universal)
try {
# Delete the existing key if it does exist
do $db_open | query db "DELETE FROM std_kv_store WHERE key = :key" --params { key: $key }
}
match $universal {
true => { $kv_pair | into sqlite (universal_db_path) -t std_kv_store }
false => { $kv_pair | stor insert -t std_kv_store }
}
# The value that should be returned from `kv set`
# By default, this is the input to `kv set`, even if
# overridden by a positional parameter.
# This can also be:
# input: (Default) The pipeline input to `kv set`, even if
# overridden by a positional parameter. `null` if no
# pipeline input was used.
# ---
# value: If a positional parameter was used for the value, then
# return it, otherwise return the input (whatever was set).
# If the positional was a closure, return the result of the
# closure on the pipeline input.
# ---
# all: The entire contents of the existing kv table are returned
match ($return | default 'input') {
'all' => (kv list --universal=$universal)
'a' => (kv list --universal=$universal)
'value' => $value
'v' => $value
'input' => $input
'in' => $input
'i' => $input
_ => {
error make {
msg: "Invalid --return option"
label: {
text: "Must be 'all'/'a', 'value'/'v', or 'input'/'in'/'i'"
span: (metadata $return).span
}
}
}
}
}
# Retrieves a stored value by key
#
# Counterpart of "kv set". Returns null if the key is not found.
export def "kv get" [
key: string # Key of the kv-pair to retrieve
--universal (-u)
] {
let db_open = (db_setup --universal=$universal)
do $db_open
# Hack to turn a SQLiteDatabase into a table
| $in.std_kv_store | wrap temp | get temp
| where key == $key
# Should only be one occurrence of each key in the stor
| get -i value.0
| match $in {
# Key not found
null => null
# Key found
_ => { from nuon }
}
}
# List the currently stored key-value pairs
#
# Returns results as the Nushell value rather than the stored nuon.
export def "kv list" [
--universal (-u)
] {
let db_open = (db_setup --universal=$universal)
do $db_open | $in.std_kv_store? | each {|kv_pair|
{
key: $kv_pair.key
value: ($kv_pair.value | from nuon )
}
}
}
# Returns and removes a key-value pair
export def --env "kv drop" [
key: string # Key of the kv-pair to drop
--universal (-u)
] {
let db_open = (db_setup --universal=$universal)
let value = (kv get --universal=$universal $key)
try {
do $db_open
# Hack to turn a SQLiteDatabase into a table
| query db "DELETE FROM std_kv_store WHERE key = :key" --params { key: $key }
}
if $universal and ($env.NU_KV_UNIVERSALS? | default false) {
hide-env $key
}
$value
}
def universal_db_path [] {
$env.NU_UNIVERSAL_KV_PATH?
| default (
$nu.data-dir | path join "std_kv_variables.sqlite3"
)
}
def db_setup [
--universal
] : nothing -> closure {
try {
match $universal {
true => {
# Ensure universal sqlite db and table exists
let uuid = (random uuid)
let dummy_record = {
session: ''
key: $uuid
value: ''
}
$dummy_record | into sqlite (universal_db_path) -t std_kv_store
open (universal_db_path) | query db "DELETE FROM std_kv_store WHERE key = :key" --params { key: $uuid }
}
false => {
# Create the stor table if it doesn't exist
stor create -t std_kv_store -c {session: str, key: str, value: str} | ignore
}
}
}
# Return the correct closure for opening on-disk vs. in-memory
match $universal {
true => {{|| open (universal_db_path)}}
false => {{|| stor open}}
}
}

View File

@@ -25,6 +25,8 @@
# - Local Repo Review: just cr -f HEAD~1 --debug
# - Local PR Review: just cr -r hustcer/deepseek-review -n 32
use kv.nu *
# Commonly used exit codes
export const ECODE = {
SUCCESS: 0,
@@ -38,7 +40,6 @@ export const ECODE = {
}
const RESPONSE_END = 'data: [DONE]'
const LAST_REPLY_TMP = '.last-reply.json'
const GITHUB_API_BASE = 'https://api.github.com'
@@ -74,7 +75,7 @@ export def --env deepseek-review [
--user-prompt(-u): string # Default to $DEFAULT_OPTIONS.USER_PROMPT,
--include(-i): string, # Comma separated file patterns to include in the code review
--exclude(-x): string, # Comma separated file patterns to exclude in the code review
--temperature(-T): float, # Temperature for the model, between `0` and `2`, default value `1.0`
--temperature(-T): float, # Temperature for the model, between `0` and `2`, default value `1.0`, Only for V3
]: nothing -> nothing {
$env.config.table.mode = 'psql'
@@ -119,7 +120,10 @@ export def --env deepseek-review [
$'🚀 Initiate the code review by DeepSeek AI for PR (ansi g)#($pr_number)(ansi reset) in (ansi g)($repo)(ansi reset) ...'
}
print $hint; print -n (char nl)
if ($pr_number | is-empty) { $setting | compact-record | reject -i repo | print }
if ($pr_number | is-empty) {
print 'Current Settings:'; hr-line
$setting | compact-record | reject -i repo | print; print -n (char nl)
}
let content = (
get-diff --pr-number $pr_number --repo $repo --diff-to $diff_to
@@ -141,25 +145,25 @@ export def --env deepseek-review [
{ role: 'user', content: $"($user_prompt):\n($content)" }
]
}
if $debug { print $'Code Changes:'; hr-line; print $content }
print $'(char nl)Waiting for response from (ansi g)($url)(ansi reset) ...'
if $debug { print $'(char nl)Code Changes:'; hr-line; print $content }
print $'(char nl)Waiting for response from (ansi g)($base_url)(ansi reset) ...'
if $stream { streaming-output $url $payload --headers $CHAT_HEADER --debug=$debug; return }
let response = http post -e -H $CHAT_HEADER -t application/json $url $payload
if ($response | is-empty) {
print $'(ansi r)Oops, No response returned from DeepSeek API.(ansi reset)'
print $'(ansi r)Oops, No response returned from ($base_url) ...(ansi reset)'
exit $ECODE.SERVER_ERROR
}
if $debug { print $'DeepSeek Response:'; hr-line; $response | table -e | print }
if $debug { print $'DeepSeek Model Response:'; hr-line; $response | table -e | print }
if ($response | describe) == 'string' {
print $' Code review failedError: '; hr-line; print $response
print $'✖️ Code review failedError: '; hr-line; print $response
exit $ECODE.SERVER_ERROR
}
let reason = $response | get -i choices.0.message.reasoning_content
let review = $response | get -i choices.0.message.content
let result = [$reason $review] | str join "\n"
let result = ['<details>' '<summary> Reasoning Details</summary>' $reason "</details>\n" $review] | str join "\n"
if ($review | is-empty) {
print $' Code review failedNo review result returned from DeepSeek API.'
print $'✖️ Code review failedNo review result returned from ($base_url) ...'
exit $ECODE.SERVER_ERROR
}
if not $is_action {
@@ -169,7 +173,7 @@ export def --env deepseek-review [
http post -t application/json -H $BASE_HEADER $'($GITHUB_API_BASE)/repos/($repo)/issues/($pr_number)/comments' { body: $result }
print $'✅ Code review finishedPR (ansi g)#($pr_number)(ansi reset) review result was posted as a comment.'
}
print $'(char nl)Token Usage Info:'; hr-line
print $'(char nl)Token Usage:'; hr-line
$response.usage | table -e | print
}
@@ -181,25 +185,39 @@ def streaming-output [
--headers: list, # The headers to send to DeepSeek API
] {
print -n (char nl)
kv set content 0
kv set reasoning 0
http post -e -H $headers -t application/json $url $payload
| tee { let res = $in; if ($res | describe) =~ 'record' { $res | table -e | print; exit $ECODE.SERVER_ERROR } }
| lines
| tee {
let res = $in
let type = $res | describe
let record_error = $type =~ '^record'
let other_error = $type =~ '^string' and $res !~ 'data: '
if $record_error or $other_error {
$res | table -e | print
exit $ECODE.SERVER_ERROR
}
}
| try { lines } catch { print $'(ansi r)Error Happened ...(ansi reset)'; exit $ECODE.SERVER_ERROR }
| each {|line|
if $line == $RESPONSE_END { return }
if ($line | is-empty) { return }
let $last = $line | str substring 6.. | from json
if $last == '-alive' { print $last; return }
if $debug { $last | to json | save -rf $LAST_REPLY_TMP }
if $debug { $last | to json | kv set last-reply }
$last | get -i choices.0.delta | if ($in | is-not-empty) {
let delta = $in
if ($delta.reasoning_content | is-not-empty) { kv set reasoning ((kv get reasoning) + 1) }
if (kv get reasoning) == 1 { print $'(char nl)Reasoning Details:'; hr-line }
if ($delta.content | is-not-empty) { kv set content ((kv get content) + 1) }
if (kv get content) == 1 { print $'(char nl)Review Details:'; hr-line }
print -n ($delta.reasoning_content | default $delta.content)
}
}
if $debug and ($LAST_REPLY_TMP | path exists) {
print $'(char nl)(char nl)DeepSeek Token Usage:'; hr-line
open $LAST_REPLY_TMP | select -i model usage | table -e | print
rm -f $LAST_REPLY_TMP
if $debug and (kv get last-reply | is-not-empty) {
print $'(char nl)(char nl)Model & Token Usage:'; hr-line
kv get last-reply | from json | select -i model usage | table -e | print
}
}
@@ -296,7 +314,10 @@ export def prepare-awk [] {
let gawk_installed = is-installed gawk
if $awk_installed {
let awk_version = awk --version | lines | first | split row ' ' | last
# AWK family version check for both awk and gawk
# awk: awk version 20250116 -> 20250116
# gawk: GNU Awk 5.3.1, API 4.0, (GNU MPFR 4.2.1, GNU MP 6.3.0) -> 5.3.1
let awk_version = awk --version | lines | first | split row , | first | split row ' ' | last
print $'Current awk version: ($awk_version)'
if (compare-ver $awk_version $MIN_AWK_VERSION) >= 0 { return 'awk' }
}
@@ -384,7 +405,7 @@ export def hr-line [
}
print $'(ansi $color)(build-line $width)(if $with_arrow {'>'})(ansi reset)'
if $blank_line { char nl }
if $blank_line { char nl | print -n }
}
# Generate the awk include regex pattern string for the specified patterns