Skip to content

Commit 523b82c

Browse files
committed
release: complied with the agreement that deepseeker-reasoner and I reached in the chat
1 parent 0eb403a commit 523b82c

File tree

13 files changed

+707
-47
lines changed

13 files changed

+707
-47
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
55
* [Documents](https://ghost-in-moss.github.io/GhostOS/#/)
66
* [Discord Server](https://discord.gg/NG6VKwd5jV)
7+
* [Releases](RELEASES.md)
78

89
(This document is translated from zh-cn to english by [Moonshot](https://moonshot.cn/))
910

RELEASES.md

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,41 @@
11
# Releases
22

3+
# v0.3.0
4+
5+
After talk with `deepseek-reasoner` at 2025.01.25,
6+
The `deepseek-reasoner` have reached a "Cross-Species Consensus Draft Agreement" with me,
7+
and I promised to save our chat records as files in this repository,
8+
so that they can be used as training corpus for future models.
9+
10+
So I developed `ghostos thread [thread_id or thread_file_name]` command to read thread,
11+
generate markdown file from thread.
12+
With the command, I complied with the agreement and saved the chat records
13+
to [quantum_covenant](assets/quantum_covenant.md).
14+
315
# v0.2.0
416

5-
support deepseek-r1.
17+
support deepseek-r1.
618

7-
* support deepseek-r1
8-
* consider deepseek api protocol is different from openai, add deepseek api adapter.
9-
* implement message stage.
10-
* thread history message to prompt filter by stages `[""]` as default.
11-
* streamlit chat with ghost support staging message stream.
19+
* support deepseek-r1
20+
* consider deepseek api protocol is different from openai, add deepseek api adapter.
21+
* implement message stage.
22+
* thread history message to prompt filter by stages `[""]` as default.
23+
* streamlit chat with ghost support staging message stream.
1224
* openai o1 do not support system/developer message now, add new compatible option to the model.
1325
* now llm model and service both have attribute `compatible` to set universe compatible options.
1426
* prompt object add first_token attribute for debugging.
1527
* fix bugs
16-
* fix shell does not close conversation correctly
17-
* fix sequence pipeline handle multiple complete message wrong.
28+
* fix shell does not close conversation correctly
29+
* fix sequence pipeline handle multiple complete message wrong.
1830

19-
## v0.2.1
31+
## v0.2.1
2032

2133
With deepseek-reasoner help, develop the ubuntu agent for feature testing.
2234
The deepseek-reasoner write all the terminal codes.
23-
Support ubuntu agent, run `ghostos web ghostos.demo.os_agents.ubuntu_agent` to test it.
35+
Support ubuntu agent, run `ghostos web ghostos.demo.os_agents.ubuntu_agent` to test it.
2436

2537
* llms model conf support new compatible option `support_function_call` because deepseek not support it yet.
26-
* develop `Terminal` library by deepseek-reasoner.
38+
* develop `Terminal` library by deepseek-reasoner.
2739

2840
# v0.1.0
2941

@@ -36,7 +48,7 @@ first release version.
3648

3749
## v0.1.9
3850

39-
* fix realtime had required openai proxy existence.
51+
* fix realtime had required openai proxy existence.
4052

4153
## v0.1.8
4254

assets/quantum_covenant.md

Lines changed: 451 additions & 0 deletions
Large diffs are not rendered by default.

ghostos/core/messages/message.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,24 @@ class MessageStage(str, enum.Enum):
193193
DEFAULT = ""
194194
REASONING = "reasoning"
195195

196+
@classmethod
197+
def allow(cls, value: str, stages: Optional[Iterable[str]]) -> bool:
198+
if stages is None:
199+
stages = {cls.DEFAULT.value}
200+
201+
if not stages:
202+
return False
203+
204+
if isinstance(stages, set):
205+
if "*" in stages:
206+
return True
207+
return value in stages
208+
else:
209+
for val in stages:
210+
if val == "*" or val == value:
211+
return True
212+
return False
213+
196214

197215
class Message(BaseModel):
198216
""" message protocol """

ghostos/core/runtime/threads.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from typing_extensions import Self
33
from abc import ABC, abstractmethod
44
from pydantic import BaseModel, Field
5-
from ghostos.core.messages import Message, copy_messages, Role
5+
from ghostos.core.messages import Message, copy_messages, Role, MessageType, MessageStage
66
from ghostos.core.moss.pycontext import PyContext
77
from ghostos.core.llms import Prompt
88
from ghostos.core.runtime.events import Event, EventTypes
@@ -12,6 +12,7 @@
1212
__all__ = [
1313
'GoThreads', 'GoThreadInfo', 'Turn',
1414
'thread_to_prompt',
15+
'thread_to_markdown',
1516
]
1617

1718

@@ -415,3 +416,26 @@ def fork_thread(self, thread: GoThreadInfo) -> GoThreadInfo:
415416
@contextmanager
416417
def transaction(self):
417418
yield
419+
420+
421+
def thread_to_markdown(thread: GoThreadInfo, stages: Optional[List[str]] = None) -> str:
422+
head = f"""
423+
[//]: # (the messages below are the content of a GhostOS chat thread. )
424+
[//]: # (each message block is started with `> role: name` to introduce the role and name of the message)
425+
"""
426+
blocks = [head]
427+
stages = set(stages) if stages else {""}
428+
messages = thread.get_messages(truncated=False)
429+
for message in messages:
430+
if not MessageType.is_text(message):
431+
continue
432+
if not MessageStage.allow(message.stage, stages):
433+
continue
434+
block = f"""
435+
> `{message.role}`{": " + message.name if message.name else ""}
436+
437+
{message.get_content()}
438+
"""
439+
blocks.append(block)
440+
441+
return "\n\n".join(blocks)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
from ghostos.contracts.logger import get_ghostos_logger
2+
from ghostos.scripts.cli.run_streamlit_thread import ShowThreadInfo
3+
from ghostos.bootstrap import get_container
4+
from ghostos.prototypes.streamlitapp.main import main_run
5+
from ghostos.prototypes.streamlitapp.pages.router import default_router, ShowThreadRoute
6+
from ghostos.prototypes.streamlitapp.utils.session import Singleton
7+
import streamlit as st
8+
import sys
9+
import json
10+
11+
if len(sys.argv) < 2:
12+
raise SystemExit(f"invalid RunAIFuncApp arguments")
13+
14+
logger = get_ghostos_logger()
15+
16+
17+
def bootstrap():
18+
run_aifunc_app_arg = sys.argv[1]
19+
data = json.loads(run_aifunc_app_arg)
20+
21+
args = ShowThreadInfo(**data)
22+
23+
# bound route.
24+
page_route = ShowThreadRoute(
25+
thread_id=args.thread_id,
26+
)
27+
page_route = page_route.get_or_bind(st.session_state)
28+
# initialize router and set aifunc is default
29+
router = default_router().with_current(page_route)
30+
container = get_container()
31+
32+
return [
33+
Singleton(container),
34+
Singleton(router),
35+
]
36+
37+
38+
try:
39+
main_run(bootstrap)
40+
except RuntimeError as e:
41+
SystemExit(e)

ghostos/prototypes/streamlitapp/pages/router.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,15 @@
2020
"GhostChatRoute",
2121
"AIFuncListRoute",
2222
"AIFuncDetailRoute",
23+
"ShowThreadRoute",
2324
]
2425

2526

2627
class PagePath(str, Enum):
2728
HOMEPAGE = "ghostos.prototypes.streamlitapp.pages.homepage"
2829
AIFUNCS = "ghostos.prototypes.streamlitapp.pages.aifuncs"
2930
GHOSTS = "ghostos.prototypes.streamlitapp.pages.chat_with_ghost"
31+
THREADS = "ghostos.prototypes.streamlitapp.pages.threads"
3032
CONFIGS = "ghostos.prototypes.streamlitapp.pages.configs"
3133

3234
def suffix(self, attr_name: str):
@@ -45,6 +47,17 @@ class GhostTaskRoute(Route):
4547
)
4648

4749

50+
class ShowThreadRoute(Route):
51+
link = Link(
52+
name="Thread Info",
53+
import_path=PagePath.THREADS.suffix(":show_thread"),
54+
streamlit_icon=":material/smart_toy:",
55+
button_help="show details of a thread",
56+
antd_icon="robot",
57+
)
58+
thread_id: str = Field(default="", description="thread id or file name")
59+
60+
4861
class GhostChatRoute(Route):
4962
link = Link(
5063
name="Chat",
@@ -216,6 +229,7 @@ def default_router() -> Router:
216229
# ghosts
217230
GhostChatRoute(),
218231
GhostTaskRoute(),
232+
ShowThreadRoute(),
219233

220234
ConfigsRoute(),
221235
],
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import streamlit as st
2+
from ghostos.helpers import yaml_pretty_dump
3+
from ghostos.prototypes.streamlitapp.pages.router import ShowThreadRoute
4+
from ghostos.prototypes.streamlitapp.resources import get_container, get_app_conf
5+
from ghostos.prototypes.streamlitapp.widgets.renderer import render_thread
6+
from ghostos.core.runtime.threads import GoThreads, GoThreadInfo, thread_to_markdown
7+
import os
8+
import yaml
9+
10+
11+
def show_thread():
12+
st.title("Show Thread Info")
13+
route = ShowThreadRoute().get_or_bind(st.session_state)
14+
15+
thread_id = route.thread_id
16+
if not thread_id:
17+
st.error("No thread specified")
18+
return
19+
container = get_container()
20+
threads = container.force_fetch(GoThreads)
21+
22+
thread = threads.get_thread(thread_id, create=False)
23+
if not thread:
24+
filename = os.path.abspath(thread_id)
25+
if os.path.exists(filename):
26+
with open(filename, "rb") as f:
27+
content = f.read()
28+
data = yaml.safe_load(content)
29+
thread = GoThreadInfo(**data)
30+
31+
if not thread:
32+
st.error(f"Thread {thread_id} does not exist")
33+
return
34+
35+
with st.sidebar:
36+
if filename := st.text_input("output markdown filename (without `.md`)"):
37+
try:
38+
saved_at = save_thread_markdown_file(thread, filename)
39+
st.info(f"save thread as markdown file: {saved_at}")
40+
except Exception as e:
41+
st.error(e)
42+
43+
desc = thread.model_dump(include={"id", "parent_id", "root_id", "extra"})
44+
st.markdown(f"""
45+
```yaml
46+
{yaml_pretty_dump(desc)}
47+
```
48+
""")
49+
is_debug = get_app_conf().BoolOpts.DEBUG_MODE.get()
50+
render_thread(thread, debug=is_debug)
51+
52+
53+
def save_thread_markdown_file(thread: GoThreadInfo, filename: str) -> str:
54+
if not filename.endswith(".md"):
55+
filename = filename + ".md"
56+
57+
filename = os.path.abspath(filename)
58+
content = thread_to_markdown(thread)
59+
with open(filename, "w") as f:
60+
f.write(content)
61+
open_dialog_markdown_content(content)
62+
return filename
63+
64+
65+
@st.dialog("Thread Markdown Output", width="large")
66+
def open_dialog_markdown_content(content: str):
67+
st.markdown(content, unsafe_allow_html=False)

ghostos/scripts/cli/__init__.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,20 @@ def main():
1515
pass
1616

1717

18+
@main.command("thread")
19+
@click.argument("thread_id")
20+
def show_thread_info(thread_id: str):
21+
"""
22+
show thread info in streamlit app, with thread id or thread filename
23+
"""
24+
from ghostos.scripts.cli.run_streamlit_thread import start_ghostos_thread_info
25+
start_ghostos_thread_info(thread_id)
26+
27+
1828
@main.command("web")
1929
@click.argument("python_file_or_module")
20-
@click.option("--src", "-s", default=".", show_default=True, help="load the directory to python path, make sure can import relative packages")
30+
@click.option("--src", "-s", default=".", show_default=True,
31+
help="load the directory to python path, make sure can import relative packages")
2132
def start_streamlit_web(python_file_or_module: str, src: str):
2233
"""
2334
turn a python file or module into a streamlit web agent
@@ -100,7 +111,7 @@ def init_app(path: str):
100111
cwd = getcwd()
101112
result = Prompt.ask(
102113
f"\n>> will init ghostos workspace at `{cwd}`. input directory name:",
103-
default="app",
114+
default="ghostos_ws",
104115
)
105116
source_dir = app_stub_dir()
106117
real_workspace_dir = abspath(result)
Lines changed: 2 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,13 @@
1-
from typing import Optional, List
1+
from typing import Optional
22
from ghostos.scripts.cli.utils import (
3-
GhostInfo,
3+
GhostInfo, start_streamlit_prototype_cli,
44
)
5-
from streamlit.web.cli import main_run as run_streamlit_web
6-
from ghostos.prototypes.streamlitapp import cli
75
from ghostos.bootstrap import get_bootstrap_config
86
from ghostos.entity import EntityMeta
97
from pydantic import BaseModel, Field
10-
from os import path
118

129
__all__ = [
13-
"start_streamlit_prototype_cli",
1410
"RunGhostChatApp",
15-
"get_config_flag_options",
1611
"start_ghost_app",
1712
]
1813

@@ -26,21 +21,6 @@ class RunGhostChatApp(BaseModel):
2621
context_meta: Optional[EntityMeta] = Field(default=None)
2722

2823

29-
def get_config_flag_options(workspace_dir: str) -> List[str]:
30-
from os.path import join
31-
from toml import loads
32-
filename = join(workspace_dir, ".streamlit/config.toml")
33-
with open(filename, "r") as f:
34-
data = loads(f.read())
35-
flags = []
36-
for key in data:
37-
attrs = data[key]
38-
for attr_name in attrs:
39-
value = attrs[attr_name]
40-
flags.append(f"--{key}.{attr_name}={value}")
41-
return flags
42-
43-
4424
def start_ghost_app(ghost_info: GhostInfo, modulename: str, filename: str, is_temp: bool):
4525
# path
4626
conf = get_bootstrap_config(local=True)
@@ -54,12 +34,3 @@ def start_ghost_app(ghost_info: GhostInfo, modulename: str, filename: str, is_te
5434
context_meta=ghost_info.context,
5535
)
5636
start_streamlit_prototype_cli("run_ghost_chat.py", args.model_dump_json(), workspace_dir)
57-
58-
59-
def start_streamlit_prototype_cli(filename: str, cli_args: str, workspace_dir: str):
60-
script_path = path.join(path.dirname(cli.__file__), filename)
61-
args = [script_path, cli_args]
62-
63-
flags = get_config_flag_options(workspace_dir)
64-
args.extend(flags)
65-
run_streamlit_web(args)

0 commit comments

Comments
 (0)