QAway-to commited on
Commit
8caee6c
Β·
1 Parent(s): de51249
Files changed (3) hide show
  1. app.py +132 -28
  2. llm_streaming.py +0 -20
  3. logic.py +0 -17
app.py CHANGED
@@ -1,8 +1,120 @@
1
- # app.py
2
  import gradio as gr
3
- from logic import extract_portfolio_id, fetch_portfolio_metrics
4
- from llm_streaming import stream_llm_response
 
 
5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  with gr.Blocks() as demo:
7
  gr.Markdown("# 🧠 AI ассистСнт Tradelink")
8
 
@@ -24,35 +136,27 @@ with gr.Blocks() as demo:
24
  portfolio_input_pair_2 = gr.Textbox(label="ΠŸΠΎΡ€Ρ‚Ρ„Π΅Π»ΡŒ 2")
25
  compare_button = gr.Button("πŸ“Š Π‘Ρ€Π°Π²Π½ΠΈΡ‚ΡŒ")
26
 
27
- def on_button_click(btn_id):
28
- if btn_id == "analyze":
29
- return gr.update(visible=True), gr.update(visible=False), "Π’Ρ‹Π±Π΅Ρ€ΠΈΡ‚Π΅ ΠΏΠΎΡ€Ρ‚Ρ„Π΅Π»ΡŒ для Π°Π½Π°Π»ΠΈΠ·Π°."
30
- elif btn_id == "compare":
31
- return gr.update(visible=False), gr.update(visible=True), "Π’Ρ‹Π±Π΅Ρ€ΠΈΡ‚Π΅ Π΄Π²Π° портфСля для сравнСния."
32
- elif btn_id == "passport":
33
- return gr.update(visible=False), gr.update(visible=False), "ΠŸΠ°ΡΠΏΠΎΡ€Ρ‚ β€” это ΠΏΡ€ΠΎΠ΄ΡƒΠΊΡ‚ для управлСния Π°ΠΊΡ‚ΠΈΠ²Π°ΠΌΠΈ..."
34
- elif btn_id == "marketplace":
35
- return gr.update(visible=False), gr.update(visible=False), "ΠœΠ°Ρ€ΠΊΠ΅Ρ‚ΠΏΠ»Π΅ΠΉΡ β€” ΠΊΠ°Ρ‚Π°Π»ΠΎΠ³ стратСгий..."
36
- elif btn_id == "tellme":
37
- return gr.update(visible=False), gr.update(visible=False), "Π₯ΠΎΡ‚ΠΈΡ‚Π΅ расскаТу ΠΏΡ€ΠΎ Alpha, Beta, Sharpe?"
38
- else:
39
- return gr.update(visible=False), gr.update(visible=False), "Π’Ρ‹Π±Π΅Ρ€ΠΈΡ‚Π΅ дСйствиС."
40
 
 
41
  btn_analyze.click(lambda: on_button_click("analyze"), outputs=[analysis_block, compare_block, output])
42
  btn_compare.click(lambda: on_button_click("compare"), outputs=[analysis_block, compare_block, output])
43
  btn_passport.click(lambda: on_button_click("passport"), outputs=[analysis_block, compare_block, output])
44
  btn_marketplace.click(lambda: on_button_click("marketplace"), outputs=[analysis_block, compare_block, output])
45
  btn_tellme.click(lambda: on_button_click("tellme"), outputs=[analysis_block, compare_block, output])
 
 
 
 
 
 
 
46
 
47
- def analyze_single(portfolio_raw):
48
- pid = extract_portfolio_id(portfolio_raw)
49
- if not pid:
50
- return "❗ ID портфСля Π½Π΅ распознан."
51
- metrics = fetch_portfolio_metrics(pid)
52
- if not metrics:
53
- return "❗ ΠœΠ΅Ρ‚Ρ€ΠΈΠΊΠΈ Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½Ρ‹."
54
- metrics_text = ", ".join([f"{k}: {v}" for k, v in metrics.items()])
55
- prompt = f"Π’ΠΎΡ‚ ΠΌΠ΅Ρ‚Ρ€ΠΈΠΊΠΈ портфСля: {metrics_text}. ΠŸΡ€ΠΎΠ°Π½Π°Π»ΠΈΠ·ΠΈΡ€ΡƒΠΉ Π½Π° русском языкС, ΠΊΠ°ΠΊ финансовый Π°Π½Π°Π»ΠΈΡ‚ΠΈΠΊ."
56
- return stream_llm_response(prompt)
57
-
58
- analyze_button.click(analyze_single, inputs=portfolio_input_single, outputs=output)
 
 
1
  import gradio as gr
2
+ import requests
3
+ import re
4
+ from openai import OpenAI
5
+ from typing import Generator
6
 
7
+ # ─────────────────────────────────────────────────────────────────────────────
8
+ # 🌐 Настройка API ΠΊΠ»ΠΈΠ΅Π½Ρ‚Π° (Featherless.ai)
9
+ # ─────────────────────────────────────────────────────────────────────────────
10
+ client = OpenAI(
11
+ base_url="https://api.featherless.ai/v1",
12
+ api_key="rc_2b641fdc25954733a5ea0fca262626daf21562b4b"
13
+ )
14
+
15
+ # ─────────────────────────────────────────────────────────────────────────────
16
+ # πŸ” Π’ΡΠΏΠΎΠΌΠΎΠ³Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ
17
+ # ─────────────────────────────────────────────────────────────────────────────
18
+ def extract_portfolio_id(text):
19
+ match = re.search(r"[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}", text)
20
+ return match.group(0) if match else None
21
+
22
+ # ─────────────────────────────────────────────────────────────────────────────
23
+ # πŸ“Š Анализ ΠΎΠ΄Π½ΠΎΠ³ΠΎ портфСля
24
+ # ─────────────────────────────────────────────────────────────────────────────
25
+ def analyze_from_api(portfolio_input: str) -> Generator[str, None, None]:
26
+ try:
27
+ portfolio_id = extract_portfolio_id(portfolio_input)
28
+ if not portfolio_id:
29
+ yield "❗ НСвСрный Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ portfolioId."
30
+ return
31
+
32
+ url = f"https://api.tradelink.pro/portfolio/get?portfolioId={portfolio_id}&extended=1&declaration=1&step=day&lang=en&incViews=1"
33
+ response = requests.get(url)
34
+ json_data = response.json()
35
+ extended = json_data.get("data", {}).get("extended", {})
36
+
37
+ metrics_keys = ["alphaRatio", "betaRatio", "cagr", "sharpe", "sortino", "volatility"]
38
+ filtered = {k: v for k, v in extended.items() if k in metrics_keys and isinstance(v, (int, float))}
39
+
40
+ if not filtered:
41
+ yield "❗ ΠœΠ΅Ρ‚Ρ€ΠΈΠΊΠΈ Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½Ρ‹ ΠΈΠ»ΠΈ ΠΎΡ‚ΡΡƒΡ‚ΡΡ‚Π²ΡƒΡŽΡ‚."
42
+ return
43
+
44
+ metrics_text = ", ".join([f"{k}: {v}" for k, v in filtered.items()])
45
+ prompt = f"""Π’ΠΎΡ‚ ΠΌΠ΅Ρ‚Ρ€ΠΈΠΊΠΈ портфСля: {metrics_text}.
46
+ ΠŸΡ€ΠΎΠ°Π½Π°Π»ΠΈΠ·ΠΈΡ€ΡƒΠΉ ΠΈΡ… ΠΈ объясни ΡΠΈΠ»ΡŒΠ½Ρ‹Π΅ ΠΈ слабыС стороны. ΠžΡ‚Π²Π΅Ρ‡Π°ΠΉ Π½Π° русском языкС ΠΊΠ°ΠΊ финансовый Π°Π½Π°Π»ΠΈΡ‚ΠΈΠΊ."""
47
+
48
+ response = client.chat.completions.create(
49
+ model="meta-llama/Meta-Llama-3.1-8B-Instruct",
50
+ messages=[{"role": "system", "content": "Π’Ρ‹ β€” финансовый Π°Π½Π°Π»ΠΈΡ‚ΠΈΠΊ."},
51
+ {"role": "user", "content": prompt}],
52
+ stream=True
53
+ )
54
+
55
+ partial = ""
56
+ for chunk in response:
57
+ delta = chunk.choices[0].delta.content
58
+ if delta:
59
+ partial += delta
60
+ yield partial
61
+
62
+ except Exception as e:
63
+ yield f"❌ Ошибка: {e}"
64
+
65
+ # ─────────────────────────────────────────────────────────────────────────────
66
+ # πŸ’¬ РасскаТи / Π”ΠΈΠ°Π»ΠΎΠ³
67
+ # ─────────────────────────────────────────────────────────────────────────────
68
+ chat_history = [{"role": "system", "content": "Π’Ρ‹ β€” финансовый Π°Π½Π°Π»ΠΈΡ‚ΠΈΠΊ. ΠžΡ‚Π²Π΅Ρ‡Π°ΠΉ ΠΊΡ€Π°Ρ‚ΠΊΠΎ ΠΈ понятно."}]
69
+
70
+ def handle_tellme_stream() -> Generator[str, None, None]:
71
+ chat_history.append({"role": "user", "content": "РасскаТи ΠΏΡ€ΠΎ Alpha, Sharpe ΠΈΠ»ΠΈ Beta"})
72
+ response = client.chat.completions.create(
73
+ model="meta-llama/Meta-Llama-3.1-8B-Instruct",
74
+ messages=chat_history,
75
+ stream=True
76
+ )
77
+
78
+ collected = ""
79
+ for chunk in response:
80
+ if chunk.choices and chunk.choices[0].delta.get("content"):
81
+ token = chunk.choices[0].delta["content"]
82
+ collected += token
83
+ yield token
84
+
85
+ chat_history.append({"role": "assistant", "content": collected})
86
+
87
+ def followup_reply(user_input):
88
+ chat_history.append({"role": "user", "content": user_input})
89
+ response = client.chat.completions.create(
90
+ model="meta-llama/Meta-Llama-3.1-8B-Instruct",
91
+ messages=chat_history,
92
+ stream=False
93
+ )
94
+ answer = response.choices[0].message.content
95
+ chat_history.append({"role": "assistant", "content": answer})
96
+ return answer
97
+
98
+ # ─────────────────────────────────────────────────────────────────────────────
99
+ # 🧠 НавигационныС сцСнарии
100
+ # ─────────────────────────────────────────────────────────────────────────────
101
+ def on_button_click(btn_id):
102
+ if btn_id == "analyze":
103
+ return gr.update(visible=True), gr.update(visible=False), "Π’Ρ‹Π±Π΅Ρ€ΠΈΡ‚Π΅ ΠΏΠΎΡ€Ρ‚Ρ„Π΅Π»ΡŒ для Π°Π½Π°Π»ΠΈΠ·Π°."
104
+ elif btn_id == "compare":
105
+ return gr.update(visible=False), gr.update(visible=True), "Π’Ρ‹Π±Π΅Ρ€ΠΈΡ‚Π΅ Π΄Π²Π° портфСля для сравнСния."
106
+ elif btn_id == "passport":
107
+ return gr.update(visible=False), gr.update(visible=False), "ΠŸΠ°ΡΠΏΠΎΡ€Ρ‚ β€” это ΠΏΡ€ΠΎΠ΄ΡƒΠΊΡ‚ для управлСния Π°ΠΊΡ‚ΠΈΠ²Π°ΠΌΠΈ..."
108
+ elif btn_id == "marketplace":
109
+ return gr.update(visible=False), gr.update(visible=False), "ΠœΠ°Ρ€ΠΊΠ΅Ρ‚ΠΏΠ»Π΅ΠΉΡ β€” ΠΊΠ°Ρ‚Π°Π»ΠΎΠ³ стратСгий..."
110
+ elif btn_id == "tellme":
111
+ return gr.update(visible=False), gr.update(visible=False), None
112
+ else:
113
+ return gr.update(visible=False), gr.update(visible=False), ""
114
+
115
+ # ─────────────────────────────────────────────────────────────────────────────
116
+ # πŸŽ›οΈ Π˜Π½Ρ‚Π΅Ρ€Ρ„Π΅ΠΉΡ (Gradio Blocks)
117
+ # ─────────────────────────────────────────────────────────────────────────────
118
  with gr.Blocks() as demo:
119
  gr.Markdown("# 🧠 AI ассистСнт Tradelink")
120
 
 
136
  portfolio_input_pair_2 = gr.Textbox(label="ΠŸΠΎΡ€Ρ‚Ρ„Π΅Π»ΡŒ 2")
137
  compare_button = gr.Button("πŸ“Š Π‘Ρ€Π°Π²Π½ΠΈΡ‚ΡŒ")
138
 
139
+ with gr.Column(visible=False) as tellme_block:
140
+ gr.Markdown("## πŸ’¬ Π˜Π½Ρ‚Π΅Ρ€Π°ΠΊΡ‚ΠΈΠ²Π½Ρ‹ΠΉ Π΄ΠΈΠ°Π»ΠΎΠ³")
141
+ textbox_followup = gr.Textbox(label="Π’Π°Ρˆ ΠΎΡ‚Π²Π΅Ρ‚")
142
+ button_followup = gr.Button("ΠžΡ‚Π²Π΅Ρ‚ΠΈΡ‚ΡŒ")
 
 
 
 
 
 
 
 
 
143
 
144
+ # ΠžΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΠΈ ΠΊΠ½ΠΎΠΏΠΎΠΊ Π½Π°Π²ΠΈΠ³Π°Ρ†ΠΈΠΈ
145
  btn_analyze.click(lambda: on_button_click("analyze"), outputs=[analysis_block, compare_block, output])
146
  btn_compare.click(lambda: on_button_click("compare"), outputs=[analysis_block, compare_block, output])
147
  btn_passport.click(lambda: on_button_click("passport"), outputs=[analysis_block, compare_block, output])
148
  btn_marketplace.click(lambda: on_button_click("marketplace"), outputs=[analysis_block, compare_block, output])
149
  btn_tellme.click(lambda: on_button_click("tellme"), outputs=[analysis_block, compare_block, output])
150
+ btn_tellme.click(fn=handle_tellme_stream, outputs=output)
151
+
152
+ # ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° Π°Π½Π°Π»ΠΈΠ·Π° портфСля
153
+ analyze_button.click(fn=analyze_from_api, inputs=portfolio_input_single, outputs=output)
154
+
155
+ # ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° Π΄ΠΈΠ°Π»ΠΎΠ³Π° "РасскаТи"
156
+ button_followup.click(fn=followup_reply, inputs=textbox_followup, outputs=output)
157
 
158
+ # ─────────────────────────────────────────────────────────────────────────────
159
+ # πŸš€ Запуск
160
+ # ─────────────────────────────────────────────────────────────────────────────
161
+ if __name__ == "__main__":
162
+ demo.launch()
 
 
 
 
 
 
 
llm_streaming.py DELETED
@@ -1,20 +0,0 @@
1
- # llm_streaming.py
2
- from openai import OpenAI
3
-
4
- client = OpenAI(
5
- base_url="https://api.featherless.ai/v1",
6
- api_key="rc_2b641fdc25954733a5ef7ccd10ce6a8e1db603e386a0fca262626daf21562b4b"
7
- )
8
-
9
- def stream_llm_response(prompt):
10
- stream = client.chat.completions.create(
11
- model="meta-llama/Meta-Llama-3.1-8B-Instruct",
12
- messages=[
13
- {"role": "system", "content": "Π’Ρ‹ β€” финансовый Π°Π½Π°Π»ΠΈΡ‚ΠΈΠΊ."},
14
- {"role": "user", "content": prompt}
15
- ],
16
- stream=True
17
- )
18
- for chunk in stream:
19
- if "choices" in chunk.model_dump() and chunk.choices[0].delta.content:
20
- yield chunk.choices[0].delta.content
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
logic.py DELETED
@@ -1,17 +0,0 @@
1
- # logic.py
2
-
3
- import requests
4
- import json
5
- import re
6
-
7
- def extract_portfolio_id(raw_input: str) -> str:
8
- match = re.search(r"([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})", raw_input)
9
- return match.group(1) if match else None
10
-
11
- def fetch_portfolio_metrics(portfolio_id: str) -> dict:
12
- url = f"https://api.tradelink.pro/portfolio/get?portfolioId={portfolio_id}&extended=1"
13
- response = requests.get(url)
14
- json_data = response.json()
15
- extended = json_data.get("data", {}).get("extended", {})
16
- metrics_keys = ["alphaRatio", "betaRatio", "cagr", "sharpe", "sortino", "volatility"]
17
- return {k: v for k, v in extended.items() if k in metrics_keys and isinstance(v, (int, float))}