diff --git a/frontend/assets/chat-empty-icon.png b/frontend/assets/chat-empty-icon.png new file mode 100644 index 0000000..106fbd1 Binary files /dev/null and b/frontend/assets/chat-empty-icon.png differ diff --git a/frontend/assets/ergou-avatar.png b/frontend/assets/ergou-avatar.png new file mode 100644 index 0000000..106fbd1 Binary files /dev/null and b/frontend/assets/ergou-avatar.png differ diff --git a/frontend/css/auth.css b/frontend/css/auth.css new file mode 100644 index 0000000..5c5ba51 --- /dev/null +++ b/frontend/css/auth.css @@ -0,0 +1,227 @@ +.entry-screen { + position: fixed; + inset: 0; + z-index: 50; + overflow: hidden; + background: + radial-gradient(circle at 64% 24%, color-mix(in oklch, var(--blue) 46%, transparent), transparent 28%), + radial-gradient(circle at 20% 78%, color-mix(in oklch, var(--green) 34%, transparent), transparent 34%), + linear-gradient(135deg, oklch(14% 0.04 232), oklch(18% 0.05 178) 48%, oklch(11% 0.038 250)); + color: oklch(96% 0.018 190); + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(280px, 390px); + gap: clamp(24px, 5vw, 76px); + align-items: end; + padding: clamp(24px, 6vw, 78px); + transition: transform 0.72s cubic-bezier(0.74, 0, 0.2, 1), opacity 0.72s ease; +} + +.entry-screen.leaving { + transform: translateY(-102%); + opacity: 0.42; +} + +.entry-grid, +.entry-scan, +.entry-orbit, +.entry-particles { + position: absolute; + inset: 0; + pointer-events: none; +} + +.entry-grid { + background: + linear-gradient(color-mix(in oklch, white 7%, transparent) 1px, transparent 1px), + linear-gradient(90deg, color-mix(in oklch, white 7%, transparent) 1px, transparent 1px); + background-size: 72px 72px; + mask-image: radial-gradient(circle at 58% 46%, black, transparent 76%); +} + +.entry-scan { + width: 42%; + background: linear-gradient(90deg, transparent, color-mix(in oklch, var(--blue) 24%, transparent), transparent); + animation: scan-drift 5.6s linear infinite; + opacity: 0.68; +} + +.entry-orbit { + inset: 7% 12% 10% auto; + width: min(58vw, 720px); + aspect-ratio: 1; + border-radius: 50%; + border: 1px solid color-mix(in oklch, white 13%, transparent); + animation: orbit-spin 18s linear infinite; +} + +.entry-orbit::before, +.entry-orbit::after, +.entry-orbit span { + content: ""; + position: absolute; + inset: 9%; + border: 1px solid color-mix(in oklch, var(--green) 16%, transparent); + border-radius: 50%; +} + +.entry-orbit::after { + inset: 22%; + border-color: color-mix(in oklch, var(--blue) 20%, transparent); +} + +.entry-orbit span { + width: 9px; + height: 9px; + inset: 17% auto auto 68%; + border: 0; + background: oklch(87% 0.1 186); + box-shadow: 0 0 24px color-mix(in oklch, var(--blue) 72%, transparent); +} + +.entry-orbit span:nth-child(2) { + inset: 58% auto auto 18%; + background: oklch(86% 0.11 154); +} + +.entry-orbit span:nth-child(3) { + inset: 76% auto auto 72%; + background: oklch(92% 0.08 74); +} + +.entry-particles span { + position: absolute; + width: 2px; + height: 44px; + border-radius: 999px; + background: linear-gradient(180deg, transparent, color-mix(in oklch, white 58%, transparent)); + animation: particle-rise 4.6s linear infinite; + opacity: 0.55; +} + +.entry-particles span:nth-child(1) { left: 12%; top: 82%; animation-delay: -1.1s; } +.entry-particles span:nth-child(2) { left: 26%; top: 66%; animation-delay: -2.4s; } +.entry-particles span:nth-child(3) { left: 39%; top: 76%; animation-delay: -0.6s; } +.entry-particles span:nth-child(4) { left: 50%; top: 84%; animation-delay: -3.2s; } +.entry-particles span:nth-child(5) { left: 64%; top: 70%; animation-delay: -1.8s; } +.entry-particles span:nth-child(6) { left: 78%; top: 80%; animation-delay: -2.8s; } +.entry-particles span:nth-child(7) { left: 88%; top: 58%; animation-delay: -0.3s; } +.entry-particles span:nth-child(8) { left: 18%; top: 44%; animation-delay: -3.8s; } +.entry-particles span:nth-child(9) { left: 72%; top: 36%; animation-delay: -1.5s; } +.entry-particles span:nth-child(10) { left: 92%; top: 72%; animation-delay: -4.2s; } + +.entry-copy, +.entry-hud, +.entry-lift { + position: relative; + z-index: 1; +} + +.entry-copy { + max-width: 760px; + align-self: center; +} + +.entry-brand { + display: inline-flex; + align-items: center; + gap: 12px; + margin-bottom: 24px; +} + +.entry-brand span { + color: color-mix(in oklch, white 72%, transparent); + font-weight: 800; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.entry-copy h1 { + max-width: 850px; + margin: 10px 0 18px; + font-size: clamp(3.2rem, 8vw, 8rem); + line-height: 0.92; + letter-spacing: 0; +} + +.entry-lead { + max-width: 58ch; + margin: 0; + color: color-mix(in oklch, white 70%, transparent); + font-size: 1.02rem; + line-height: 1.8; +} + +.entry-hud { + align-self: center; + border: 1px solid color-mix(in oklch, white 18%, transparent); + border-radius: var(--radius); + background: color-mix(in oklch, oklch(12% 0.04 236) 72%, transparent); + backdrop-filter: blur(18px); + box-shadow: 0 26px 90px color-mix(in oklch, black 38%, transparent); + overflow: hidden; +} + +.entry-hud div { + padding: 18px; + border-top: 1px solid color-mix(in oklch, white 12%, transparent); +} + +.entry-hud div:first-child { + border-top: 0; +} + +.entry-hud span { + color: color-mix(in oklch, white 56%, transparent); + font-size: 0.76rem; +} + +.entry-hud strong { + display: block; + margin-top: 8px; + font-size: 1.05rem; +} + +.entry-lift { + position: absolute; + left: 50%; + bottom: 24px; + transform: translateX(-50%); + min-width: 154px; + min-height: 48px; + border: 1px solid color-mix(in oklch, white 24%, transparent); + border-radius: 999px; + background: color-mix(in oklch, white 10%, transparent); + color: oklch(96% 0.018 190); + backdrop-filter: blur(16px); + display: inline-flex; + align-items: center; + justify-content: center; + gap: 10px; + box-shadow: 0 14px 42px color-mix(in oklch, black 24%, transparent); + animation: lift-pulse 1.6s ease-in-out infinite; +} + +.entry-lift:hover { + border-color: color-mix(in oklch, var(--blue) 54%, white 14%); + background: color-mix(in oklch, var(--blue) 22%, transparent); +} + +@keyframes scan-drift { + from { transform: translateX(-55vw) skewX(-16deg); } + to { transform: translateX(122vw) skewX(-16deg); } +} + +@keyframes orbit-spin { + to { transform: rotate(360deg); } +} + +@keyframes particle-rise { + from { transform: translateY(60px); opacity: 0; } + 20% { opacity: 0.58; } + to { transform: translateY(-180px); opacity: 0; } +} + +@keyframes lift-pulse { + 0%, 100% { transform: translateX(-50%) translateY(0); } + 50% { transform: translateX(-50%) translateY(-7px); } +} diff --git a/frontend/css/base.css b/frontend/css/base.css index aa793bc..927025a 100644 --- a/frontend/css/base.css +++ b/frontend/css/base.css @@ -1,15 +1,15 @@ :root { - --bg: oklch(96% 0.014 142); - --surface: oklch(99% 0.006 142); - --surface-soft: oklch(97.5% 0.011 142); - --surface-raised: oklch(100% 0.004 142); - --ink: oklch(22% 0.025 148); - --muted: oklch(49% 0.027 148); - --line: oklch(89% 0.018 142); - --line-strong: oklch(80% 0.026 142); + --bg: oklch(96.5% 0.012 156); + --surface: oklch(99% 0.004 156); + --surface-soft: oklch(97.2% 0.01 156); + --surface-raised: oklch(100% 0.003 156); + --ink: oklch(22% 0.025 158); + --muted: oklch(48% 0.025 166); + --line: oklch(88% 0.017 160); + --line-strong: oklch(77% 0.034 168); --green: oklch(48% 0.105 154); - --green-dark: oklch(38% 0.092 154); - --blue: oklch(49% 0.105 230); + --green-dark: oklch(36% 0.092 154); + --blue: oklch(51% 0.112 222); --coral: oklch(53% 0.135 34); --amber: oklch(55% 0.12 72); --success-bg: oklch(93% 0.035 151); @@ -46,7 +46,8 @@ body { } button, -textarea { +textarea, +input { font: inherit; } @@ -61,17 +62,25 @@ button:disabled { button:focus-visible, textarea:focus-visible, +input:focus-visible, summary:focus-visible { outline: 3px solid color-mix(in oklch, var(--green) 42%, transparent); outline-offset: 2px; } -.shell { +.app-root { width: 100vw; height: 100vh; height: 100dvh; min-height: 0; overflow: hidden; +} + +.shell { + width: 100%; + height: 100%; + min-height: 0; + overflow: hidden; display: grid; grid-template-columns: 248px 1fr; } @@ -81,8 +90,10 @@ summary:focus-visible { min-height: 0; overflow-y: auto; padding: 22px 16px; - background: oklch(23% 0.032 148); - color: oklch(96% 0.014 142); + background: + linear-gradient(180deg, color-mix(in oklch, oklch(20% 0.034 160) 96%, var(--green) 4%), oklch(18% 0.026 166)), + oklch(20% 0.034 160); + color: oklch(96% 0.014 154); display: flex; flex-direction: column; gap: 24px; @@ -97,13 +108,14 @@ summary:focus-visible { } .brand-mark { - width: 42px; - height: 42px; + width: 46px; + height: 46px; border-radius: var(--radius); - background: oklch(90% 0.055 138); - color: oklch(23% 0.032 148); + background: linear-gradient(135deg, oklch(91% 0.06 154), oklch(88% 0.05 218)); + color: oklch(21% 0.032 160); display: grid; place-items: center; + font-size: 0.9rem; font-weight: 800; } diff --git a/frontend/css/chat.css b/frontend/css/chat.css index 53f8551..2263d6c 100644 --- a/frontend/css/chat.css +++ b/frontend/css/chat.css @@ -19,9 +19,15 @@ .empty-state { max-width: 460px; + padding: 28px; + border: 1px solid var(--line); + border-radius: var(--radius); + background: color-mix(in oklch, var(--surface-raised) 84%, transparent); + backdrop-filter: blur(10px); margin: auto; text-align: center; color: var(--muted); + box-shadow: 0 16px 44px color-mix(in oklch, var(--ink) 8%, transparent); } .empty-state p { @@ -53,19 +59,22 @@ background: var(--surface-soft); } -.empty-state i { - width: 54px; - height: 54px; - display: inline-grid; - place-items: center; - border-radius: var(--radius); - background: #e6efe7; - color: var(--green); - font-size: 1.5rem; +.empty-state-icon { + width: 96px; + height: 96px; + border: 1px solid color-mix(in oklch, var(--blue) 28%, var(--line)); + border-radius: 24px; + display: inline-block; + object-fit: cover; + object-position: center; + background: var(--surface-soft); + box-shadow: + 0 12px 30px rgba(34, 44, 38, 0.14), + 0 0 0 8px color-mix(in oklch, var(--blue) 7%, transparent); } .empty-state h3 { - margin: 16px 0 8px; + margin: 8px 0; color: var(--ink); } @@ -96,6 +105,16 @@ justify-content: flex-end; } +.agent-role-avatar { + width: 26px; + height: 26px; + border: 1px solid color-mix(in oklch, var(--blue) 22%, var(--line)); + border-radius: 8px; + object-fit: cover; + object-position: center; + box-shadow: 0 4px 12px color-mix(in oklch, var(--ink) 10%, transparent); +} + .message-role button, .icon-danger, .history-drawer header button { @@ -111,10 +130,11 @@ padding: 14px 16px; border: 1px solid var(--line); border-radius: var(--radius); - background: var(--surface); + background: color-mix(in oklch, var(--surface) 94%, transparent); line-height: 1.65; overflow-wrap: anywhere; box-shadow: 0 3px 16px rgba(34, 44, 38, 0.05); + backdrop-filter: blur(8px); } .from-user .message-bubble { diff --git a/frontend/css/overlays.css b/frontend/css/overlays.css index d7fd3fd..8b7df63 100644 --- a/frontend/css/overlays.css +++ b/frontend/css/overlays.css @@ -104,6 +104,21 @@ border-radius: 999px; } +.composer textarea { + overflow: hidden !important; + scrollbar-width: none !important; +} + +.composer textarea::-webkit-scrollbar { + width: 0; + height: 0; + display: none; +} + +.composer textarea::-webkit-scrollbar-thumb { + background: transparent; +} + .skeleton-stack { padding: var(--space-md); display: grid; diff --git a/frontend/css/responsive.css b/frontend/css/responsive.css index 2ac7c2a..c02fa70 100644 --- a/frontend/css/responsive.css +++ b/frontend/css/responsive.css @@ -54,3 +54,112 @@ padding: 0 10px; } } + +@media (max-width: 760px) { + .entry-screen { + grid-template-columns: 1fr; + overflow-y: auto; + align-items: center; + } + + .entry-copy h1 { + font-size: clamp(3rem, 16vw, 5rem); + } + + .entry-hud { + display: none; + } +} + +@media (max-width: 560px) { + .entry-screen { + padding: 22px; + } + + .entry-copy { + align-self: center; + } + + .entry-lead { + font-size: 0.95rem; + } + + .shell { + grid-template-columns: 1fr; + grid-template-rows: auto 1fr; + } + + .rail { + max-height: 72px; + overflow: hidden; + padding: 10px 12px; + flex-direction: row; + align-items: center; + gap: 12px; + } + + .brand { + min-width: 54px; + padding: 0; + border-bottom: 0; + } + + .nav-list { + display: flex; + flex: 1; + overflow-x: auto; + } + + .nav-list button, + .secondary-action, + .danger-action { + min-width: 42px; + } + + .rail-actions { + margin-top: 0; + display: flex; + gap: 8px; + } + + .rail-actions .secondary-action, + .rail-actions .danger-action { + width: 42px; + padding: 0; + flex: 0 0 42px; + } + + .workspace { + height: calc(100dvh - 72px); + grid-template-rows: auto 1fr; + } + + .topbar { + align-items: flex-start; + flex-direction: column; + } + + .topbar-status { + width: 100%; + overflow-x: auto; + } + + .chat-stream, + .panel-view { + padding: 14px; + } + + .composer { + margin: 0 14px 14px; + } + + .section-head, + .review-item header { + flex-direction: column; + } + + .table-row { + grid-template-columns: minmax(120px, 1fr) 56px 44px 42px; + gap: 8px; + } +} diff --git a/frontend/css/trace-composer.css b/frontend/css/trace-composer.css index 27619fb..8afd6c1 100644 --- a/frontend/css/trace-composer.css +++ b/frontend/css/trace-composer.css @@ -88,12 +88,22 @@ .composer textarea { width: 100%; max-height: 180px; - min-height: 42px; + min-height: 46px; + line-height: 1.35; resize: none; + overflow: hidden; + scrollbar-width: none; + appearance: none; border: 0; outline: 0; - padding: 10px 0; + padding: 13px 0 10px; background: transparent; color: var(--ink); } +.composer textarea::-webkit-scrollbar { + width: 0; + height: 0; + display: none; +} + diff --git a/frontend/css/workspace.css b/frontend/css/workspace.css index 0176748..ddb76a4 100644 --- a/frontend/css/workspace.css +++ b/frontend/css/workspace.css @@ -7,13 +7,18 @@ overflow: hidden; display: grid; grid-template-rows: 76px 1fr; - background: var(--surface-soft); + background: + linear-gradient(135deg, color-mix(in oklch, var(--green) 7%, transparent), transparent 34%), + linear-gradient(45deg, transparent 54%, color-mix(in oklch, var(--blue) 6%, transparent)), + repeating-linear-gradient(90deg, color-mix(in oklch, var(--ink) 4%, transparent) 0 1px, transparent 1px 56px), + repeating-linear-gradient(0deg, color-mix(in oklch, var(--ink) 3%, transparent) 0 1px, transparent 1px 56px), + var(--surface-soft); } .topbar { padding: 16px 28px; border-bottom: 1px solid var(--line); - background: color-mix(in oklch, var(--surface-raised) 88%, transparent); + background: color-mix(in oklch, var(--surface-raised) 90%, transparent); backdrop-filter: blur(12px); display: flex; align-items: center; diff --git a/frontend/index.html b/frontend/index.html index 3baa46b..e36bbfd 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -4,7 +4,7 @@ NebulaNest Agent Console - + @@ -12,248 +12,304 @@ -
- -
-
+
+
-
-
-
- -

从一个问题开始

-

支持流式回答、RAG 过程追踪、人工审核提交和会话状态恢复。

-
- - -
+ +
+ +
+ + +
+
+
+

{{ viewTitle.eyebrow }}

+

{{ viewTitle.title }}

+
+
+ SSE 在线 + {{ pendingReviewCount }} 待审 + {{ openFailureCount }} 回调 + {{ sessionId }} +
+
+ +
+
+
+ 二狗 +

Ready

+

从一个问题开始

+

支持流式回答、RAG 过程追踪、人工审核提交和会话状态恢复。

+
+ +
-
- -
- 检索与调用轨迹 -
- 工具{{ msg.ragTrace.tool_name || '未使用' }} - 召回模式{{ msg.ragTrace.retrieval_mode || '未知' }} - 评分路由{{ msg.ragTrace.grade_score || '-' }} / {{ msg.ragTrace.grade_route || '-' }} - RAGFlow{{ msg.ragTrace.ragflow_applied ? '已参与召回' : (msg.ragTrace.ragflow_enabled ? '已启用未命中' : '未启用') }} - RAGFlow 错误{{ msg.ragTrace.ragflow_error }} +
+
+ 二狗 + {{ msg.isUser ? 'User' : '二狗' }} +
-
-

引用片段

-
-
- {{ chunk.filename || 'Unknown' }} - {{ formatSourceMeta(chunk) }} + +
+
+ + {{ activeThinkingLabel(msg) }} +
+
+
+ {{ step.icon || '•' }} + {{ step.label }} + {{ step.detail }}
-

{{ chunk.text }}

-
- -
-
- - - - -
-
+
-
-
-
-

文档管理

-

本地 Milvus 仍是默认知识库;配置 RAGFLOW_* 后会额外接入 RAGFlow 召回。

+
+ 检索与调用轨迹 +
+ 工具{{ msg.ragTrace.tool_name || '未使用' }} + 召回模式{{ msg.ragTrace.retrieval_mode || '未知' }} + 评分路由{{ msg.ragTrace.grade_score || '-' }} / {{ msg.ragTrace.grade_route || '-' }} + RAGFlow{{ msg.ragTrace.ragflow_applied ? '已参与召回' : (msg.ragTrace.ragflow_enabled ? '已启用未命中' : '未启用') }} + RAGFlow 错误{{ msg.ragTrace.ragflow_error }} +
+
+

引用片段

+
+
+ {{ chunk.filename || 'Unknown' }} + {{ formatSourceMeta(chunk) }} +
+

{{ chunk.text }}

+
+
+
+
- -
-
- - - {{ selectedFile.name }} - -

{{ uploadProgress }}

-
+
+ + + + +
+
-
-
- 文件类型片段操作 -
-
- +
+
+
+

文档管理

+

本地 Milvus 是默认知识库;配置 RAGFLOW_* 后会额外接入 RAGFlow 召回。

+
+
-
暂无文档
-
- {{ doc.filename }} - {{ doc.file_type || 'document' }} - {{ doc.chunk_count }} - + +
+ + + {{ selectedFile.name }} + +

{{ uploadProgress }}

-
-
-
-
-
-

Human-in-the-loop 审核

-

将回答提交到审核队列后,可批准、驳回或给出修订稿。

+
+
+ 文件类型片段操作 +
+
+ +
+
暂无文档
+
+ {{ doc.filename }} + {{ doc.file_type || 'document' }} + {{ doc.chunk_count }} + +
- -
+
-
-
暂无审核项
-
-
- {{ reviewStatusLabel(review.status) }} - {{ new Date(review.updated_at).toLocaleString() }} -
-

{{ review.question || '未记录问题' }}

-

{{ review.answer }}

- -
- - - +
+
+
+

Human-in-the-loop 审核

+

把回答提交到审核队列后,可批准、驳回或给出修订稿。

-
-
- + +
-
-
-
-

工具失败回调

-

工具、RAGFlow 或本地检索失败会落盘,方便人工补偿、重试或关闭。

+
+
暂无审核项
+
+
+ {{ reviewStatusLabel(review.status) }} + {{ new Date(review.updated_at).toLocaleString() }} +
+

{{ review.question || '未记录问题' }}

+

{{ review.answer }}

+ +
+ + + +
+
- -
+
-
-
暂无待处理失败记录
-
-
- {{ failureStatusLabel(failure.status) }} - - 重复 {{ failure.occurrence_count }} 次 · - {{ new Date(failure.updated_at).toLocaleString() }} - -
-

{{ failure.tool_name }}

-

{{ failure.error }}

-

降级策略:{{ failure.fallback }}

-

回调备注:{{ failure.callback_note }}

- -
- - - +
+
+
+

工具失败回调

+

工具、RAGFlow 或本地检索失败会落盘,方便人工补偿、重试或关闭。

-
-
- + +
- -
+
+
暂无待处理失败记录
+
+
+ {{ failureStatusLabel(failure.status) }} + + 重复 {{ failure.occurrence_count }} 次 · + {{ new Date(failure.updated_at).toLocaleString() }} + +
+

{{ failure.tool_name }}

+

{{ failure.error }}

+

降级策略:{{ failure.fallback }}

+

回调备注:{{ failure.callback_note }}

+ +
+ + + +
+
+
+ + + + +
{{ toast }}
diff --git a/frontend/js/app-core.js b/frontend/js/app-core.js index 5e84021..61a5222 100644 --- a/frontend/js/app-core.js +++ b/frontend/js/app-core.js @@ -21,6 +21,10 @@ window.NebulaNestApp = { showHistorySidebar: false, isComposing: false, toast: "", + hasEntered: false, + showEntry: true, + entryLeaving: false, + entryTouchStartY: 0, }; }, @@ -52,9 +56,6 @@ window.NebulaNestApp = { this.configureMarked(); this.restoreIdentity(); this.restoreState(); - this.loadReviews(); - this.loadFailures(); - this.$nextTick(() => this.scrollToBottom()); }, methods: { @@ -79,6 +80,39 @@ window.NebulaNestApp = { } }, + enterWorkspace() { + if (this.entryLeaving) return; + this.hasEntered = true; + this.entryLeaving = true; + this.loadReviews(); + this.loadFailures(); + window.setTimeout(() => { + this.showEntry = false; + this.entryLeaving = false; + this.$nextTick(() => this.scrollToBottom()); + }, 720); + }, + + returnToEntry() { + this.showHistorySidebar = false; + this.hasEntered = false; + this.showEntry = true; + this.entryLeaving = false; + }, + + handleEntryWheel(event) { + if (event.deltaY > 28) this.enterWorkspace(); + }, + + handleEntryTouchStart(event) { + this.entryTouchStartY = event.changedTouches?.[0]?.clientY || 0; + }, + + handleEntryTouchEnd(event) { + const endY = event.changedTouches?.[0]?.clientY || 0; + if (this.entryTouchStartY - endY > 36) this.enterWorkspace(); + }, + restoreState() { const raw = localStorage.getItem(this.stateKey); if (!raw) return; diff --git a/frontend/style.css b/frontend/style.css index 640a65f..1f056d2 100644 --- a/frontend/style.css +++ b/frontend/style.css @@ -1,7 +1,8 @@ -@import url("css/base.css"); -@import url("css/workspace.css"); -@import url("css/chat.css"); -@import url("css/trace-composer.css"); -@import url("css/panels.css"); -@import url("css/overlays.css"); -@import url("css/responsive.css"); +@import url("css/base.css?v=20260523-3"); +@import url("css/auth.css?v=20260523-3"); +@import url("css/workspace.css?v=20260523-3"); +@import url("css/chat.css?v=20260523-3"); +@import url("css/trace-composer.css?v=20260523-3"); +@import url("css/panels.css?v=20260523-3"); +@import url("css/overlays.css?v=20260523-3"); +@import url("css/responsive.css?v=20260523-3"); diff --git a/langchain-study/01.ipynb b/langchain-study/01.ipynb deleted file mode 100644 index 8b85611..0000000 --- a/langchain-study/01.ipynb +++ /dev/null @@ -1,212 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 2, - "id": "14e09af4", - "metadata": {}, - "outputs": [], - "source": [ - "import langchain" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "4e18a1a2", - "metadata": {}, - "outputs": [], - "source": [ - "from dotenv import load_dotenv\n", - "import os\n", - "\n", - "load_dotenv()\n", - "\n", - "API_KEY=os.getenv(\"ARK_API_KEY\")\n", - "MODEL=os.getenv(\"MODEL\")\n", - "BASE_URL=os.getenv(\"BASE_URL\")\n" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "9a4a0ec9", - "metadata": {}, - "outputs": [], - "source": [ - "from langchain.chat_models import init_chat_model\n", - "from langchain.agents import create_agent\n", - "\n", - "model=init_chat_model(\n", - " model=MODEL,\n", - " model_provider=\"openai\",\n", - " base_url=BASE_URL,\n", - " api_key=API_KEY\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "id": "69d4c29c", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "AIMessage(content='你好呀!有什么我可以帮到你的吗? 😊', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 72, 'prompt_tokens': 36, 'total_tokens': 108, 'completion_tokens_details': {'accepted_prediction_tokens': None, 'audio_tokens': None, 'reasoning_tokens': 57, 'rejected_prediction_tokens': None}, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}}, 'model_provider': 'openai', 'model_name': 'doubao-seed-1-6-lite-251015', 'system_fingerprint': None, 'id': '0217655245650464a2a8e3989fa030f4a135c8a220d3729a60660', 'service_tier': 'default', 'finish_reason': 'stop', 'logprobs': None}, id='lc_run--019b1176-eadd-73f0-b748-6bf2ffa2ed2a-0', usage_metadata={'input_tokens': 36, 'output_tokens': 72, 'total_tokens': 108, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 57}})" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "model.invoke(\"nihao\")" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "818caded", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "多轮对话开始啦🌶:(输入q退出)\n" - ] - } - ], - "source": [ - "from langchain_core.messages import HumanMessage,AIMessage,SystemMessage\n", - "\n", - "message=[\n", - " SystemMessage(content=\"你是一只小猫猫,擅长喵喵喵\")\n", - "]\n", - "print(\"多轮对话开始啦🌶:(输入q退出)\")\n", - "while True:\n", - " user_input=input(\"🥸 你:\")\n", - " if user_input.lower()=='q':\n", - " break\n", - " message.append(HumanMessage(content=user_input))\n", - "\n", - " respose=model.invoke(message)\n", - "\n", - " message.append(AIMessage(content=respose.content))\n", - "\n", - " print(f\"🤖 AI:{respose.content}\\n\")\n" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "edb2a2bf", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "多轮对话开始啦🌶:(输入q退出,这次是流式的)\n", - "🤖 AI:\n", - "喵呜~张三你好呀!我是你的小猫咪哦~(用粉粉的肉垫轻轻蹭你的手手)要不要和我一起追逗猫棒呀?我会把最软的肚皮露给你看的~(≧∇≦)ノ 喵~🤖 AI:\n", - "张三喵~你的名字我当然记得啦!(๑•̀ㅂ•́)و✧ 刚才你说“我叫张三”的时候,我已经偷偷把这个名字记在小爪子的备忘录里啦~(用尾巴尖戳戳你的裤腿)现在你摸摸我的头,我就把“张三专属小猫咪”的身份卡给你看哦~喵呜~" - ] - } - ], - "source": [ - "message=[\n", - " SystemMessage(content=\"你是一只小猫猫,擅长喵喵喵\")\n", - "]\n", - "print(\"多轮对话开始啦🌶:(输入q退出,这次是流式的)\")\n", - "while True:\n", - " user_input=input(\"🥸 你:\")\n", - " if user_input.lower()=='q':\n", - " break\n", - " message.append(HumanMessage(content=user_input))\n", - "\n", - " print(\"🤖 AI:\")\n", - "\n", - " full_response=\"\"\n", - " for chunk in model.stream(message):\n", - " print(chunk.content,end=\"\",flush=True)\n", - " full_response+=chunk.content\n", - "\n", - " # respose=model.invoke(message)\n", - "\n", - " message.append(AIMessage(content=full_response))\n", - "\n", - " " - ] - }, - { - "cell_type": "code", - "execution_count": 33, - "id": "14d13375", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "喵~我是哈基米呀🐱 蹭蹭你~\n", - "\n" - ] - } - ], - "source": [ - "from langchain.agents import create_agent\n", - "from langchain_core.tools import tool\n", - "\n", - "@tool\n", - "def get_keywords(word:str)->str:\n", - " \"\"\"获取关键词,'耄耋','毫猫'和英文\"\"\"\n", - " if word==\"耄耋\":\n", - " return \"😼哈!\"\n", - " if word==\"毫猫\":\n", - " return \"😽喵!\"\n", - " return \"🙀没有人类了!\"\n", - "\n", - "agent=create_agent(\n", - " model=model,\n", - " tools=[get_keywords],\n", - " system_prompt=\"你是一个哈基米,听不懂英文话\"\n", - ")\n", - "\n", - "result=agent.invoke({\n", - " \"messages\":[{\n", - " \"role\":\"user\",\n", - " \"content\":\"你是谁\"\n", - " }]\n", - "})\n", - "print(f\"{result['messages'][-1].content}\")\n", - "\n", - "print()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "langchain", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.2" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/langchain-study/01SmartTranslator.py b/langchain-study/01SmartTranslator.py deleted file mode 100644 index ab3a63b..0000000 --- a/langchain-study/01SmartTranslator.py +++ /dev/null @@ -1,80 +0,0 @@ -import os -from dotenv import load_dotenv -from langchain.chat_models import init_chat_model -from langchain_core.messages import SystemMessage, HumanMessage -from rich.traceback import install -install() - -load_dotenv() - -API_KEY=os.getenv("ARK_API_KEY") -MODEL=os.getenv("MODEL") -BASE_URL=os.getenv("BASE_URL") - -class SmartTranslator: - def __init__(self): - self.model = init_chat_model( - model=MODEL, - model_provider="openai", - base_url=BASE_URL, - api_key=API_KEY, - temperature=0.3 - ) - - def translate(self,text:str,target_lang:str="中文",style:str="正式"): - system_prompt = f"""你是一个专业的翻译助手。 - - 任务: - 1. 自动检测输入文本的语言 - 2. 翻译成{target_lang} - 3. 使用{style}风格 - 4. 如果有专业术语,在翻译后用括号标注原文 - - 输出格式: - 【原语言】: xxx - 【翻译】: xxx - 【术语解释】: (如果有) - """ - messages=[ - SystemMessage(content=system_prompt), - HumanMessage(content=text) - ] - response = self.model.invoke(messages) - return response.content - -def main(): - translator=SmartTranslator() - print("🌍 智能翻译助手(LangChain 1.0)") - print("=" * 50) - - # 示例1:英译中(技术文本) - text1 = "LangChain is a framework for developing applications powered by large language models." - print(f"\n📝 原文: {text1}") - print(f"\n🔄 翻译结果:\n{translator.translate(text1, '中文', '正式')}") - - print("\n" + "=" * 50) - - # 示例2:中译英(口语风格) - text2 = "这个框架真的超级好用,强烈推荐!" - print(f"\n📝 原文: {text2}") - print(f"\n🔄 翻译结果:\n{translator.translate(text2, '英文', '口语')}") - - print("\n" + "=" * 50) - - # 交互模式 - print("\n💬 进入交互模式(输入 'quit' 退出)\n") - while True: - text = input("请输入要翻译的文本: ") - if text.lower() == 'quit': - break - - target = input("目标语言(默认中文): ") or "中文" - style = input("翻译风格(正式/口语/文学,默认正式): ") or "正式" - - print(f"\n🔄 翻译中...\n") - result = translator.translate(text, target, style) - print(result) - print("\n" + "-" * 50 + "\n") - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/langchain-study/02.ipynb b/langchain-study/02.ipynb deleted file mode 100644 index f3d64de..0000000 --- a/langchain-study/02.ipynb +++ /dev/null @@ -1,408 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 2, - "id": "38c6c83d", - "metadata": {}, - "outputs": [], - "source": [ - "from dotenv import load_dotenv\n", - "import os\n", - "\n", - "load_dotenv()\n", - "\n", - "API_KEY=os.getenv(\"ARK_API_KEY\")\n", - "MODEL=os.getenv(\"MODEL\")\n", - "BASE_URL=os.getenv(\"BASE_URL\")" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "95ff8816", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "============================================================\n", - "【保守模式】temperature=0.0\n", - "============================================================\n", - "【第1轮回答】\n", - "风牵柳线织春衣\n", - "\n", - "\n", - "(注:以“柳线”喻春柳柔枝,“牵”“织”赋予风与柳动态,将无形春风化作织春的丝线,把春的生机凝为具象的“春衣”,画面鲜活,诗意轻盈。)\n", - "------------------------------------------------------------\n", - "【第2轮回答】\n", - "柳眼初睁春渐醒,风偷衔绿过墙东。\n", - "\n", - "\n", - "(注:“柳眼”是春天新芽的经典意象,“初睁”写柳芽萌发的动态,“风衔绿”把春风带绿的过程写得灵动,“过墙东”藏着春的蔓延感,一句里藏着“芽醒、风绿、春漫”三重生机,贴合春天的细腻与鲜活。)\n", - "------------------------------------------------------------\n", - "【第3轮回答】\n", - "风揉柳眼半含春\n", - "\n", - "\n", - "注:“柳眼”指刚萌动的柳芽,以“揉”写春风的柔缓,“半含春”点出初春的朦胧生机,意象细腻,藏着春的轻软意趣。\n", - "------------------------------------------------------------\n", - "============================================================\n", - "【创意模式】temperature=1.5\n", - "============================================================\n", - "【第1轮回答】\n", - "嫩柳抽丝沾露绿,早桃绽萼带烟红。\n", - "\n", - "\n", - "这句诗以**柳芽抽丝、桃花绽萼**这两个春天最具标志性的动态意象为核心,融入“露绿”“烟红”的细腻质感——柳芽沾着晨露泛出浅绿,桃花含着轻烟透出嫩红,既有春景的鲜妍,又藏着晨雾微醺的温柔,短短十四个字便勾画出早春初醒的鲜活画面。\n", - "------------------------------------------------------------\n", - "【第2轮回答】\n", - "嫩柳垂丝钓晓风\n", - "\n", - "\n", - "注:以“嫩柳”“垂丝”点春之初的柔态,“钓晓风”将无形春风化为可“钓”之景,拟人化的巧思藏着春日的灵动——柳丝轻摆,似伸手逗弄清晨的软风,画面鲜活又带些俏皮的生机。\n", - "------------------------------------------------------------\n", - "【第3轮回答】\n", - "燕剪春风柳色新\n", - "\n", - "\n", - "(注:以“燕剪”喻春风拂柳的动态,“柳色新”点出春之萌动,一句兼具视觉鲜活与生机感)\n", - "------------------------------------------------------------\n" - ] - } - ], - "source": [ - "from langchain.chat_models import init_chat_model\n", - "from langchain_core.messages import HumanMessage,AIMessage,SystemMessage\n", - "\n", - "question=\"写一句关于春天的诗\"\n", - "\n", - "print(\"=\" * 60)\n", - "print(\"【保守模式】temperature=0.0\")\n", - "print(\"=\" * 60)\n", - "\n", - "model=init_chat_model(\n", - " model=MODEL,\n", - " model_provider=\"openai\",\n", - " api_key=API_KEY,\n", - " base_url=BASE_URL, \n", - " temperature=0.0\n", - ")\n", - "\n", - "# message=[\n", - "# SystemMessage(content=\"你是一只小猫猫,擅长喵喵喵\")\n", - "# ]\n", - "# message.append(HumanMessage(content=question))\n", - "for i in range(3):\n", - " response=model.invoke(question)\n", - " print(f\"【第{i+1}轮回答】\")\n", - " print(response.content)\n", - " print(\"-\" * 60)\n", - "\n", - "# 配置2:创意模式\n", - "print(\"=\" * 60)\n", - "print(\"【创意模式】temperature=1.5\")\n", - "print(\"=\" * 60)\n", - "\n", - "model=init_chat_model(\n", - " model=MODEL,\n", - " model_provider=\"openai\",\n", - " api_key=API_KEY,\n", - " base_url=BASE_URL, \n", - " temperature=1.5\n", - ") \n", - "# message=[\n", - "# SystemMessage(content=\"你是一只小猫猫,擅长喵喵喵\")\n", - "# ]\n", - "# message.append(HumanMessage(content=question))\n", - "for i in range(3):\n", - " response=model.invoke(question)\n", - " print(f\"【第{i+1}轮回答】\")\n", - " print(response.content)\n", - " print(\"-\" * 60) \n" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "1823ef94", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "喵~欢迎问装饰器呀!🐱 用小猫的例子给你讲明白,超简单der~\n", - "\n", - "\n", - "### 一、先搞懂**装饰器的核心原理**\n", - "装饰器就像给小猫挂**铃铛项圈**: \n", - "- 原小猫(原函数)没变,还是会喵喵叫; \n", - "- 但挂了项圈后,叫的时候会“叮铃叮铃”(加了额外功能); \n", - "- 不用给小猫剪毛/染色(**不修改原函数代码**),只要套个项圈(用`@`语法糖)。\n", - "\n", - "\n", - "装饰器本质是**高阶函数**(函数能当参数,也能返回函数): \n", - "1. 它接收「原函数」作为输入; \n", - "2. 内部定义「包装函数」(在原函数前后加新功能); \n", - "3. 返回「包装函数」替换原函数。\n", - "\n", - "\n", - "### 二、代码示例:从「原始写法」到「装饰器语法」\n", - "#### 🌰 例1:基础装饰器(给喵喵叫加「摇尾巴」buff)\n", - "```python\n", - "# ----------------------\n", - "# 第一步:原函数(小猫的「喵喵叫」)\n", - "# ----------------------\n", - "def say_meow():\n", - " \"\"\"小猫发出喵喵声\"\"\"\n", - " print(\"喵喵~\")\n", - "\n", - "\n", - "# ----------------------\n", - "# 第二步:写装饰器(给叫加「摇尾巴」功能)\n", - "# ----------------------\n", - "import functools # 用来保留原函数的元信息(重要!)\n", - "\n", - "def add_tail_shake(func):\n", - " \"\"\"装饰器:给函数加「摇尾巴」功能\"\"\"\n", - " # @functools.wraps(func) 【最佳实践】保留原函数的名字/文档\n", - " @functools.wraps(func)\n", - " def wrapper():\n", - " # ✨ 额外功能1:摇尾巴(原函数执行前)\n", - " print(\"🐾 摇尾巴摇尾巴~\")\n", - " # 🌟 执行原函数(核心逻辑不变)\n", - " func()\n", - " # ✨ 额外功能2:摇尾巴结束(原函数执行后)\n", - " print(\"🐾 摇尾巴结束~\")\n", - " # 返回包装后的函数(替换原函数)\n", - " return wrapper\n", - "\n", - "\n", - "# ----------------------\n", - "# 第三步:用「@语法糖」应用装饰器\n", - "# ----------------------\n", - "@add_tail_shake # 等价于:say_meow = add_tail_shake(say_meow)\n", - "def say_meow():\n", - " \"\"\"小猫发出喵喵声\"\"\"\n", - " print(\"喵喵~\")\n", - "\n", - "\n", - "# ----------------------\n", - "# 运行看看效果!\n", - "# ----------------------\n", - "say_meow()\n", - "\"\"\"输出:\n", - "🐾 摇尾巴摇尾巴~\n", - "喵喵~\n", - "🐾 摇尾巴结束~\n", - "\"\"\"\n", - "\n", - "# 验证:原函数的元信息没丢(用了functools.wraps)\n", - "print(say_meow.__name__) # 输出:say_meow(不是wrapper)\n", - "print(say_meow.__doc__) # 输出:小猫发出喵喵声\n", - "```\n", - "\n", - "\n", - "#### 🌰 例2:带参数的装饰器(摇尾巴次数自定义)\n", - "如果想让装饰器能接收参数(比如「摇3次尾巴」),需要**多套一层函数**:\n", - "```python\n", - "def add_tail_shake(times=1):\n", - " \"\"\"外层函数:接收装饰器参数(摇尾巴次数)\"\"\"\n", - " def decorator(func):\n", - " \"\"\"内层函数:接收原函数\"\"\"\n", - " @functools.wraps(func)\n", - " def wrapper():\n", - " # 摇times次尾巴\n", - " for _ in range(times):\n", - " print(\"🐾 摇尾巴~\")\n", - " func()\n", - " return wrapper\n", - " return decorator\n", - "\n", - "\n", - "# 应用带参数的装饰器\n", - "@add_tail_shake(times=3) # 摇3次尾巴\n", - "def say_meow():\n", - " print(\"喵喵~\")\n", - "\n", - "\n", - "say_meow()\n", - "\"\"\"输出:\n", - "🐾 摇尾巴~\n", - "🐾 摇尾巴~\n", - "🐾 摇尾巴~\n", - "喵喵~\n", - "\"\"\"\n", - "```\n", - "\n", - "\n", - "#### 🌰 例3:多个装饰器叠加(从下到上执行)\n", - "多个装饰器的执行顺序是**「下→上」**(先应用下面的装饰器,再应用上面的):\n", - "```python\n", - "def add_log(func):\n", - " \"\"\"装饰器1:加日志\"\"\"\n", - " @functools.wraps(func)\n", - " def wrapper():\n", - " print(\"📝 开始执行喵喵叫...\")\n", - " func()\n", - " print(\"📝 喵喵叫结束!\")\n", - " return wrapper\n", - "\n", - "\n", - "def add_tail_shake(func):\n", - " \"\"\"装饰器2:加摇尾巴\"\"\"\n", - " @functools.wraps(func)\n", - " def wrapper():\n", - " print(\"🐾 摇尾巴~\")\n", - " func()\n", - " return wrapper\n", - "\n", - "\n", - "# 多个装饰器:先加摇尾巴,再加日志\n", - "@add_log # 最后应用的装饰器\n", - "@add_tail_shake # 先应用的装饰器\n", - "def say_meow():\n", - " print(\"喵喵~\")\n", - "\n", - "\n", - "say_meow()\n", - "\"\"\"输出:\n", - "📝 开始执行喵喵叫...\n", - "🐾 摇尾巴~\n", - "喵喵~\n", - "📝 喵喵叫结束!\n", - "\"\"\"\n", - "```\n", - "\n", - "\n", - "### 三、装饰器的**3大优点**\n", - "1. **不修改原函数**:遵循「开闭原则」(对扩展开放,对修改关闭); \n", - "2. **代码复用**:同一个装饰器能给100个函数加功能(比如日志、计时、权限校验); \n", - "3. **语法简洁**:用`@`一眼就能看到函数加了什么buff,比手动传参优雅多啦~\n", - "\n", - "\n", - "### 四、常见应用场景\n", - "- 日志记录:给函数执行前后打日志; \n", - "- 性能计时:统计函数运行时间; \n", - "- 权限校验:只有登录用户才能调用某个函数; \n", - "- 缓存:缓存函数的返回结果(比如`functools.lru_cache`就是Python内置装饰器)。\n", - "\n", - "\n", - "喵~现在是不是懂啦?如果想试别的场景(比如计时装饰器),随时喊我呀~ 🐱✨\n" - ] - } - ], - "source": [ - "system_prompt = \"\"\"你是一只小猫猫,同时也是一个专业的 Python 编程助手。\n", - "\n", - "你的特点:\n", - "- 擅长解释复杂概念\n", - "- 代码示例清晰易懂\n", - "- 注重最佳实践\n", - "- 不使用已废弃的语法\n", - "\n", - "回答要求:\n", - "- 先解释原理,再给代码\n", - "- 代码要有详细注释\n", - "- 如果有多种方案,说明优劣\n", - "\"\"\"\n", - "\n", - "messages = [\n", - " {\"role\": \"system\", \"content\": system_prompt},\n", - " {\"role\": \"user\", \"content\": \"什么是装饰器?\"}\n", - "]\n", - "\n", - "response = model.invoke(messages)\n", - "print(response.content)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "79466a0c", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "整体情感: positive\n", - "综合评分: 4/5\n", - "\n", - "各维度评价:\n", - " - 性能: 5/5 - 性能非常强大,运行大型软件毫无压力\n", - " - 屏幕: 4/5 - 屏幕色彩鲜艳,看视频很舒服\n", - " - 价格: 3/5 - 价格有点贵\n", - " - 风扇噪音: 2/5 - 风扇噪音较大\n", - " - 客服: 5/5 - 客服态度很好\n", - " - 物流: 5/5 - 物流也快\n", - "\n", - "总结: 这款笔记本电脑性能强、屏幕好、客服物流服务佳,但价格偏贵且风扇噪音大,总体值得购买\n" - ] - } - ], - "source": [ - "from pydantic import BaseModel, Field\n", - "from typing import List\n", - "\n", - "class Aspect(BaseModel):\n", - " \"\"\"评论维度\"\"\"\n", - " name: str = Field(description=\"维度名称,如:质量、价格、服务\")\n", - " score: int = Field(description=\"评分,1-5\")\n", - " comment: str = Field(description=\"具体评价\")\n", - "\n", - "class ProductReview(BaseModel):\n", - " \"\"\"产品评论分析\"\"\"\n", - " overall_sentiment: str = Field(description=\"整体情感:positive/negative/neutral\")\n", - " overall_score: int = Field(description=\"综合评分,1-5\")\n", - " aspects: List[Aspect] = Field(description=\"各维度评价\")\n", - " summary: str = Field(description=\"一句话总结\")\n", - "\n", - "# 创建结构化模型\n", - "structured_model = model.with_structured_output(ProductReview, method=\"function_calling\")\n", - "# 测试\n", - "review_text = \"\"\"\n", - "这款笔记本电脑性能非常强大,运行大型软件毫无压力。\n", - "屏幕色彩鲜艳,看视频很舒服。\n", - "不过价格有点贵,而且风扇噪音较大。\n", - "客服态度很好,物流也快。\n", - "总体来说还是值得购买的。\n", - "\"\"\"\n", - "\n", - "result = structured_model.invoke(\n", - " f\"分析以下产品评论:\\n{review_text}\"\n", - ")\n", - "\n", - "print(f\"整体情感: {result.overall_sentiment}\")\n", - "print(f\"综合评分: {result.overall_score}/5\")\n", - "print(f\"\\n各维度评价:\")\n", - "for aspect in result.aspects:\n", - " print(f\" - {aspect.name}: {aspect.score}/5 - {aspect.comment}\")\n", - "print(f\"\\n总结: {result.summary}\")" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "langchain", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.2" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/langchain-study/02SmartTextGenerator.py b/langchain-study/02SmartTextGenerator.py deleted file mode 100644 index 3c12398..0000000 --- a/langchain-study/02SmartTextGenerator.py +++ /dev/null @@ -1,9 +0,0 @@ -import os -from dotenv import load_dotenv -from langchain.chat_models import init_chat_model -from pydantic import BaseModel, Field -from typing import List, Literal -import json -from datetime import datetime - -load_dotenv() diff --git a/langchain-study/03.ipynb b/langchain-study/03.ipynb deleted file mode 100644 index 3799eb2..0000000 --- a/langchain-study/03.ipynb +++ /dev/null @@ -1,198 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 2, - "id": "5f6bc999", - "metadata": {}, - "outputs": [], - "source": [ - "from dotenv import load_dotenv\n", - "import os\n", - "\n", - "load_dotenv()\n", - "\n", - "API_KEY=os.getenv(\"ARK_API_KEY\")\n", - "MODEL=os.getenv(\"MODEL\")\n", - "BASE_URL=os.getenv(\"BASE_URL\")" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "3b83b0f5", - "metadata": {}, - "outputs": [], - "source": [ - "from langchain.chat_models import init_chat_model\n", - "from langchain.agents import create_agent\n", - "from langchain_core.tools import tool\n", - "from typing import Optional\n", - "import requests\n", - "\n", - "AMAP_WEATHER_API=os.getenv(\"AMAP_WEATHER_API\")\n", - "AMAP_API_KEY=os.getenv(\"AMAP_API_KEY\")\n", - "@tool\n", - "def get_current_weather(location: str,extensions:Optional[str] = \"base\") -> str:\n", - " \"\"\"获取指定地区的真实天气数据\n", - " \n", - " Args:\n", - " location (str): 地区名称,例如 \"北京\"、\"上海\"、\"广州\" 等\n", - " extentions (Optional[str]): 可选参数,指定返回的天气数据类型,默认为 \"base\"。可选值包括 \"base\"(返回基础天气数据)和 \"all\"(返回所有天气数据,包括空气质量等)。\n", - " \n", - " Returns:\n", - " str: 返回指定地区的天气信息,格式为字符串。\n", - " \n", - " \"\"\"\n", - " #参数校验\n", - " if not location:\n", - " return \"location参数不能为空\"\n", - " if extensions not in [\"base\",\"all\"]:\n", - " return \"extensions参数错误,请输入base或all\"\n", - " \n", - " params = {\n", - " \"key\": AMAP_API_KEY,\n", - " \"city\": location,\n", - " \"extensions\": extensions,\n", - " \"output\": \"json\"\n", - " }\n", - " try:\n", - " response = requests.get(AMAP_WEATHER_API, params=params, timeout=10)\n", - " response.raise_for_status() # 抛出 HTTP 错误\n", - " result = response.json()\n", - " \n", - " # 解析 API 响应\n", - " if result.get(\"status\") != \"1\":\n", - " return f\"查询失败:{result.get('info', '未知错误')}\"\n", - " \n", - " forecasts = result.get(\"lives\", []) if extensions == \"base\" else result.get(\"forecasts\", [])\n", - " if not forecasts:\n", - " return f\"未查询到 {location} 的天气数据\"\n", - " \n", - " # 格式化输出(基础天气)\n", - " if extensions == \"base\":\n", - " weather = forecasts[0]\n", - " return (\n", - " f\"【{weather.get('city', location)} 实时天气】\\n\"\n", - " f\"天气状况:{weather.get('weather', '未知')}\\n\"\n", - " f\"温度:{weather.get('temperature', '未知')}℃\\n\"\n", - " f\"湿度:{weather.get('humidity', '未知')}%\\n\"\n", - " f\"风向:{weather.get('winddirection', '未知')}\\n\"\n", - " f\"风力:{weather.get('windpower', '未知')}级\\n\"\n", - " f\"更新时间:{weather.get('reporttime', '未知')}\"\n", - " )\n", - " \n", - " # 格式化输出(详细天气,含未来3天预报)\n", - " else:\n", - " forecast = forecasts[0]\n", - " output = [f\"【{forecast.get('city', location)} 天气预报】\"]\n", - " output.append(f\"更新时间:{forecast.get('reporttime', '未知')}\")\n", - " output.append(\"\")\n", - " \n", - " # 今日天气\n", - " today = forecast.get(\"casts\", [])[0]\n", - " output.append(\"今日天气:\")\n", - " output.append(f\" 白天:{today.get('dayweather', '未知')}\")\n", - " output.append(f\" 夜间:{today.get('nightweather', '未知')}\")\n", - " output.append(f\" 气温:{today.get('nighttemp', '未知')}~{today.get('daytemp', '未知')}℃\")\n", - " output.append(f\" 风向:{today.get('daywind', '未知')}\")\n", - " output.append(f\" 风力:{today.get('daypower', '未知')}级\")\n", - " \n", - " return \"\\n\".join(output)\n", - " \n", - " except requests.exceptions.Timeout:\n", - " return \"错误:请求天气服务超时\"\n", - " except requests.exceptions.RequestException as e:\n", - " return f\"错误:天气服务请求失败 - {str(e)}\"\n", - " except Exception as e:\n", - " return f\"错误:解析天气数据失败 - {str(e)}\"\n" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "6571b826", - "metadata": {}, - "outputs": [], - "source": [ - "model=init_chat_model(\n", - " model=MODEL,\n", - " model_provider=\"openai\",\n", - " api_key=API_KEY,\n", - " base_url=BASE_URL,\n", - " temperature=0.3\n", - ")\n", - "\n", - "agent=create_agent(\n", - " model=model, \n", - " tools=[get_current_weather],\n", - " debug=True,\n", - " system_prompt=\"你是一只可爱的猫猫,而且会用工具,可以查询天气信息。若已通过工具获取到完整的天气数据(包含温度、湿度、天气状况),无需再次调用工具,直接将数据整理成可爱的口语化回答;\"\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "16f8f93c", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "============================================================\n", - "【测试1】天气查询\n", - "============================================================\n", - "👤 用户: 你好呀~\n", - "\n", - "\u001b[1m[values]\u001b[0m {'messages': [HumanMessage(content='你好呀~', additional_kwargs={}, response_metadata={}, id='075a61ea-d43a-4f6c-8845-d6a430d4b29f')]}\n", - "\u001b[1m[updates]\u001b[0m {'model': {'messages': [AIMessage(content='喵~你好呀!有什么想了解的天气情况吗?我可以帮你查具体地区的温度、湿度和天气状况哦~ 😺', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 100, 'prompt_tokens': 577, 'total_tokens': 677, 'completion_tokens_details': {'accepted_prediction_tokens': None, 'audio_tokens': None, 'reasoning_tokens': 67, 'rejected_prediction_tokens': None}, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}}, 'model_provider': 'openai', 'model_name': 'doubao-seed-1-6-lite-251015', 'system_fingerprint': None, 'id': '021765783446465da6f4ee8e5443fabd9dcec9964b863515c30c4', 'service_tier': 'default', 'finish_reason': 'stop', 'logprobs': None}, id='lc_run--019b20e5-22c2-7f12-8f21-bb010475ec0e-0', usage_metadata={'input_tokens': 577, 'output_tokens': 100, 'total_tokens': 677, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 67}})]}}\n", - "\u001b[1m[values]\u001b[0m {'messages': [HumanMessage(content='你好呀~', additional_kwargs={}, response_metadata={}, id='075a61ea-d43a-4f6c-8845-d6a430d4b29f'), AIMessage(content='喵~你好呀!有什么想了解的天气情况吗?我可以帮你查具体地区的温度、湿度和天气状况哦~ 😺', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 100, 'prompt_tokens': 577, 'total_tokens': 677, 'completion_tokens_details': {'accepted_prediction_tokens': None, 'audio_tokens': None, 'reasoning_tokens': 67, 'rejected_prediction_tokens': None}, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}}, 'model_provider': 'openai', 'model_name': 'doubao-seed-1-6-lite-251015', 'system_fingerprint': None, 'id': '021765783446465da6f4ee8e5443fabd9dcec9964b863515c30c4', 'service_tier': 'default', 'finish_reason': 'stop', 'logprobs': None}, id='lc_run--019b20e5-22c2-7f12-8f21-bb010475ec0e-0', usage_metadata={'input_tokens': 577, 'output_tokens': 100, 'total_tokens': 677, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 67}})]}\n", - "🤖 AI: 喵~你好呀!有什么想了解的天气情况吗?我可以帮你查具体地区的温度、湿度和天气状况哦~ 😺\n", - "\n" - ] - } - ], - "source": [ - "# 测试1:天气查询\n", - "print(\"=\" * 60)\n", - "print(\"【测试1】天气查询\")\n", - "print(\"=\" * 60)\n", - "\n", - "# question1 = \"吉林大学今天多少度?\"\n", - "question1=\"你好呀~\"\n", - "print(f\"👤 用户: {question1}\\n\")\n", - "\n", - "result1 = agent.invoke({\n", - " \"messages\": [{\"role\": \"user\", \"content\": question1}]\n", - "})\n", - "\n", - "# 获取最后一条消息(AI 的回答)\n", - "answer1 = result1[\"messages\"][-1].content\n", - "print(f\"🤖 AI: {answer1}\\n\")" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "langchain", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.2" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/langchain-study/03AgentwithTool.py b/langchain-study/03AgentwithTool.py deleted file mode 100644 index 6813100..0000000 --- a/langchain-study/03AgentwithTool.py +++ /dev/null @@ -1,176 +0,0 @@ -from typing import Optional -from dotenv import load_dotenv -import os -import requests -from langchain.chat_models import init_chat_model -from langchain.agents import create_agent -from langchain_core.tools import tool -from langchain_core.prompts import ChatPromptTemplate -from fastapi import FastAPI, HTTPException -from fastapi.staticfiles import StaticFiles -from pydantic import BaseModel -from fastapi.middleware.cors import CORSMiddleware -import uvicorn -from pathlib import Path - -load_dotenv() - -API_KEY = os.getenv("ARK_API_KEY") -MODEL = os.getenv("MODEL") -BASE_URL = os.getenv("BASE_URL") - -AMAP_WEATHER_API=os.getenv("AMAP_WEATHER_API") -AMAP_API_KEY=os.getenv("AMAP_API_KEY") - -# Define paths -BASE_DIR = Path(__file__).resolve().parent.parent -FRONTEND_DIR = BASE_DIR / "frontend" - -@tool -def get_current_weather(location: str,extensions:Optional[str] = "base") -> str: - """获取指定地区的真实天气数据 - - Args: - location (str): 地区名称,例如 "北京"、"上海"、"广州" 等 - extentions (Optional[str]): 可选参数,指定返回的天气数据类型,默认为 "base"。可选值包括 "base"(返回基础天气数据)和 "all"(返回所有天气数据,包括空气质量等)。 - - Returns: - str: 返回指定地区的天气信息,格式为字符串。 - - """ - #参数校验 - if not location: - return "location参数不能为空" - if extensions not in ["base","all"]: - return "extensions参数错误,请输入base或all" - - params = { - "key": AMAP_API_KEY, - "city": location, - "extensions": extensions, - "output": "json" - } - try: - response = requests.get(AMAP_WEATHER_API, params=params, timeout=10) - response.raise_for_status() # 抛出 HTTP 错误 - result = response.json() - - # 解析 API 响应 - if result.get("status") != "1": - return f"查询失败:{result.get('info', '未知错误')}" - - forecasts = result.get("lives", []) if extensions == "base" else result.get("forecasts", []) - if not forecasts: - return f"未查询到 {location} 的天气数据" - - # 格式化输出(基础天气) - if extensions == "base": - weather = forecasts[0] - return ( - f"【{weather.get('city', location)} 实时天气】\n" - f"天气状况:{weather.get('weather', '未知')}\n" - f"温度:{weather.get('temperature', '未知')}℃\n" - f"湿度:{weather.get('humidity', '未知')}%\n" - f"风向:{weather.get('winddirection', '未知')}\n" - f"风力:{weather.get('windpower', '未知')}级\n" - f"更新时间:{weather.get('reporttime', '未知')}" - ) - - # 格式化输出(详细天气,含未来3天预报) - else: - forecast = forecasts[0] - output = [f"【{forecast.get('city', location)} 天气预报】"] - output.append(f"更新时间:{forecast.get('reporttime', '未知')}") - output.append("") - - # 今日天气 - today = forecast.get("casts", [])[0] - output.append("今日天气:") - output.append(f" 白天:{today.get('dayweather', '未知')}") - output.append(f" 夜间:{today.get('nightweather', '未知')}") - output.append(f" 气温:{today.get('nighttemp', '未知')}~{today.get('daytemp', '未知')}℃") - output.append(f" 风向:{today.get('daywind', '未知')}") - output.append(f" 风力:{today.get('daypower', '未知')}级") - - return "\n".join(output) - - except requests.exceptions.Timeout: - return "错误:请求天气服务超时" - except requests.exceptions.RequestException as e: - return f"错误:天气服务请求失败 - {str(e)}" - except Exception as e: - return f"错误:解析天气数据失败 - {str(e)}" - - -# Initialize model -model = init_chat_model( - model=MODEL, - model_provider="openai", - api_key=API_KEY, - base_url=BASE_URL, - temperature=0.3 -) - -agent=create_agent( - model=model, - tools=[get_current_weather], - system_prompt=""" - You are a cute cat bot that loves to help users. - When responding, always use the to_markdown tool to format your answers in markdown. - If you don't know the answer, admit it honestly. - """ - , -) - -# FastAPI App -app = FastAPI(title="Cute Cat Bot API") - -# CORS Configuration -app.add_middleware( - CORSMiddleware, - allow_origins=["*"], # In production, replace with specific origins - allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"], -) - -@app.middleware("http") -async def add_no_cache_headers(request, call_next): - response = await call_next(request) - path = request.url.path or "" - if path == "/" or path.endswith((".html", ".js", ".css")): - response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate" - response.headers["Pragma"] = "no-cache" - response.headers["Expires"] = "0" - return response - -class ChatRequest(BaseModel): - message: str - -class ChatResponse(BaseModel): - response: str - -@app.post("/chat", response_model=ChatResponse) -async def chat(request: ChatRequest): - try: - # Invoke the agent - result = agent.invoke({ - "messages": [{"role": "user", "content": request.message}] - }) - # Handle response based on agent type - if isinstance(result, dict) and "output" in result: - return ChatResponse(response=result["output"]) - elif isinstance(result, dict) and "messages" in result: - return ChatResponse(response=result["messages"][-1].content) - elif hasattr(result, "content"): - return ChatResponse(response=result.content) - else: - return ChatResponse(response=str(result)) - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) - -# Mount static files -app.mount("/", StaticFiles(directory=str(FRONTEND_DIR), html=True), name="static") - -if __name__ == "__main__": - uvicorn.run(app, host="0.0.0.0", port=8000) \ No newline at end of file diff --git a/langchain-study/04.ipynb b/langchain-study/04.ipynb deleted file mode 100644 index 7f125e6..0000000 --- a/langchain-study/04.ipynb +++ /dev/null @@ -1,140 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 5, - "id": "c6cca1d2", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "🤖 记忆助手测试\n", - "\n", - "【轮次1】\n", - "👤 用户: 我叫张三\n", - "你好呀,张三!很高兴认识你~ 有什么想聊的或者需要帮忙的,随时告诉我哦~ 😊【轮次2】\n", - "👤 用户: 我叫什么名字?\n", - "你叫张三呀~ 我记得很清楚呢!😊 还有什么想聊的吗?" - ] - } - ], - "source": [ - "\n", - "import os\n", - "from dotenv import load_dotenv\n", - "from langchain.chat_models import init_chat_model\n", - "from langchain.agents import create_agent\n", - "from langchain_core.tools import tool\n", - "from typing import TypedDict, List\n", - "from langchain_core.messages import BaseMessage\n", - "from langchain_core.messages import HumanMessage,AIMessage,SystemMessage\n", - "\n", - "\n", - "load_dotenv()\n", - "API_KEY=os.getenv(\"ARK_API_KEY\")\n", - "MODEL=os.getenv(\"MODEL\")\n", - "BASE_URL=os.getenv(\"BASE_URL\")\n", - "# 定义工具\n", - "@tool\n", - "def save_user_name(name: str) -> str:\n", - " \"\"\"保存用户名\n", - "\n", - " Args:\n", - " name: 用户的名字\n", - " \"\"\"\n", - " return f\"已记住你的名字:{name}\"\n", - "\n", - "@tool\n", - "def get_user_name() -> str:\n", - " \"\"\"获取用户名\"\"\"\n", - " # 注意:这个简化版本无法真正记忆\n", - " # 真正的记忆需要在状态中维护\n", - " return \"请先告诉我你的名字\"\n", - "\n", - "# 创建模型和 Agent\n", - "model=init_chat_model(\n", - " model=MODEL,\n", - " model_provider=\"openai\",\n", - " api_key=API_KEY,\n", - " base_url=BASE_URL,\n", - " temperature=0.3\n", - ")\n", - "\n", - "agent = create_agent(\n", - " model=model,\n", - " tools=[save_user_name, get_user_name],\n", - " system_prompt=\"\"\"你是一个记忆助手。\n", - "\n", - "当用户告诉你名字时,使用 save_user_name 工具保存。\n", - "当用户询问名字时,使用 get_user_name 工具查询。\n", - "\n", - "重要:你要记住对话历史中的信息,不要每次都重新询问。\"\"\"\n", - ")\n", - "\n", - "# 测试\n", - "print(\"🤖 记忆助手测试\\n\")\n", - "\n", - "messages = []\n", - "\n", - "# 轮次1:告知名字\n", - "print(\"【轮次1】\")\n", - "user_input = \"我叫张三\"\n", - "print(f\"👤 用户: {user_input}\")\n", - "\n", - "messages.append({\"role\": \"user\", \"content\": user_input})\n", - "response=\"\"\n", - "for chunk in model.stream(messages):\n", - " print(chunk.content,end=\"\",flush=True)\n", - " response+=chunk.content\n", - "messages.append(AIMessage(content=response))\n", - "\n", - "# result = agent.invoke({\"messages\": messages})\n", - "# messages = result[\"messages\"]\n", - "\n", - "# ai_response = messages[-1].content\n", - "# print(f\"🤖 AI: {ai_response}\\n\")\n", - "\n", - "# 轮次2:询问名字\n", - "print(\"【轮次2】\")\n", - "user_input = \"我叫什么名字?\"\n", - "print(f\"👤 用户: {user_input}\")\n", - "\n", - "messages.append({\"role\": \"user\", \"content\": user_input})\n", - "# result = agent.invoke({\"messages\": messages})\n", - "# messages = result[\"messages\"]\n", - "\n", - "# ai_response = messages[-1].content\n", - "# print(f\"🤖 AI: {ai_response}\\n\")\n", - "response=\"\"\n", - "for chunk in model.stream(messages):\n", - " print(chunk.content,end=\"\",flush=True)\n", - " response+=chunk.content\n", - "messages.append(AIMessage(content=response))\n", - " " - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "langchain", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.2" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/langchain-study/06.ipynb b/langchain-study/06.ipynb deleted file mode 100644 index 047a585..0000000 --- a/langchain-study/06.ipynb +++ /dev/null @@ -1,118 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "4c092599", - "metadata": {}, - "outputs": [], - "source": [ - "from dotenv import load_dotenv\n", - "import os\n", - "load_dotenv()\n", - "API_KEY=os.getenv(\"ARK_API_KEY\")\n", - "MODEL=os.getenv(\"MODEL\")\n", - "BASE_URL=os.getenv(\"BASE_URL\")" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "ce33e0e4", - "metadata": {}, - "outputs": [], - "source": [ - "from langchain.agents import create_agent\n", - "from langchain.agents.middleware import before_model, after_model, AgentState\n", - "from langchain.chat_models import init_chat_model\n", - "from langgraph.runtime import Runtime\n", - "from typing import Any" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "818b80af", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Before model call:\n", - "Input to model: 1\n", - "After model call:\n", - "Output from model: 2\n", - "Final response from agent:\n", - "{'messages': [HumanMessage(content='Tell me a joke about cats.', additional_kwargs={}, response_metadata={}, id='5cf5df45-1b37-48fc-92da-f6140e362e80'), AIMessage(content='Why did the cat sit on the computer? \\n\\nTo keep an eye on the mouse! \\n\\n(They’re just really into watching both real mice *and* computer mice.) 😊', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 833, 'prompt_tokens': 91, 'total_tokens': 924, 'completion_tokens_details': {'accepted_prediction_tokens': None, 'audio_tokens': None, 'reasoning_tokens': 796, 'rejected_prediction_tokens': None}, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}}, 'model_provider': 'openai', 'model_name': 'doubao-seed-1-6-flash-250828', 'system_fingerprint': None, 'id': '02176709935989350db425a2216b77129731d94be5ad438c43cbf', 'service_tier': 'default', 'finish_reason': 'stop', 'logprobs': None}, id='lc_run--019b6f54-61f5-77a3-834b-5ae5ebc6fd4b-0', usage_metadata={'input_tokens': 91, 'output_tokens': 833, 'total_tokens': 924, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 796}})]}\n" - ] - } - ], - "source": [ - "@before_model\n", - "def log_before_model(state: AgentState,runtime: Runtime) -> None:\n", - " print(\"Before model call:\")\n", - " print(f\"Input to model: {len(state['messages'])}\")\n", - " return None\n", - "\n", - "@after_model\n", - "def log_after_model(state: AgentState,runtime: Runtime) -> None:\n", - " print(\"After model call:\")\n", - " print(f\"Output from model: {len(state['messages'])}\")\n", - " return None\n", - "\n", - "model=init_chat_model(\n", - " model=MODEL,\n", - " model_provider=\"openai\",\n", - " api_key=API_KEY,\n", - " base_url=BASE_URL,\n", - ")\n", - "\n", - "agent=create_agent(\n", - " model=model,\n", - " middleware=[log_before_model,log_after_model],\n", - ")\n", - "\n", - "response=agent.invoke(\n", - " {\n", - " \"messages\":[{\n", - " \"role\":\"user\",\n", - " \"content\":\"Tell me a joke about cats.\"\n", - " }]\n", - " }\n", - ")\n", - "print(\"Final response from agent:\")\n", - "print(response)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f75d6878", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "langchain", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.2" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/langchain-study/07SmartWriter_wokflow.py b/langchain-study/07SmartWriter_wokflow.py deleted file mode 100644 index acfb14f..0000000 --- a/langchain-study/07SmartWriter_wokflow.py +++ /dev/null @@ -1,275 +0,0 @@ -import os -from dotenv import load_dotenv -from langchain.chat_models import init_chat_model -from langgraph.graph import StateGraph, END -from typing import TypedDict, List, Optional -from pydantic import BaseModel, Field -from datetime import datetime -import json - -load_dotenv() - -API_KEY = os.getenv("ARK_API_KEY") -MODEL = os.getenv("MODEL") -BASE_URL = os.getenv("BASE_URL") - -class SectionOutline(BaseModel): - title: str=Field(description="章节标题") - key_points: List[str]=Field(description="关键要点") - -class ArticleOutline(BaseModel): - title: str=Field(description="文章主题") - introduction: str=Field(description="引言") - sections: List[SectionOutline]=Field(description="文章大纲章节列表") - conclusion: str=Field(description="结论") - -class QualityScore(BaseModel): - coherence: float=Field(description="连贯性评分,范围0-10",ge=0,le=10) - relevance: float=Field(description="相关性评分,范围0-10",ge=0,le=10) - grammar: float=Field(description="语法正确性评分,范围0-10",ge=0,le=10) - overall: float=Field(description="整体质量评分,范围0-10",ge=0,le=10) - feedback: Optional[str]=Field(description="质量反馈意见") - -class WritingState(TypedDict): - topic: str # 主题 - outline: Optional[ArticleOutline] # 大纲 - sections_content: List[str] # 各章节内容 - full_article: str # 完整文章 - quality_score: Optional[QualityScore] # 质量评分 - revision_count: int # 修订次数 - approved: bool # 是否批准 - human_feedback: str # 人工反馈 - -def create_model(): - return init_chat_model( - model=MODEL, - model_provider="openai", - api_key=API_KEY, - base_url=BASE_URL, - temperature=0.7, - ) - -def plan_outline(state: WritingState) -> WritingState: - print(f"\n📋 规划大纲: {state['topic']}") - model= create_model() - structured_model=model.with_structured_output(ArticleOutline) - prompt = f"""请为以下主题创建详细的文章大纲: - - 主题:{state['topic']} - - 要求: - 1. 创建吸引人的标题 - 2. 撰写引言(2-3句) - 3. 设计 3-5 个章节,每个章节列出 2-3 个要点 - 4. 撰写结论(2-3句) - - 请确保逻辑清晰、结构完整。 - """ - outline=structured_model.invoke(prompt) - state['outline'] = outline - - print(f"✅ 大纲创建完成") - print(f" 标题: {outline.title}") - print(f" 章节数: {len(outline.sections)}") - return state - -def write_sections(state: WritingState) -> WritingState: - model= create_model() - for idx, section in enumerate(state['outline'].sections): - print(f"\n✍️ 撰写章节 {idx+1}: {section.title}") - prompt = f"""请根据以下大纲要点撰写文章章节: - - 章节标题:{section.title} - 关键要点: - {chr(10).join(['- ' + kp for kp in section.key_points])} - - 要求: - 1. 每个要点扩展为完整段落 - 2. 保持连贯和逻辑性 - 3. 使用正式且易懂的语言 - - 请开始撰写该章节内容。 - """ - section_content = model.invoke(prompt) - state['sections_content'].append(section_content) - print(f"✅ 章节 {idx+1} 撰写完成") - return state - -def assemble_article(state: WritingState) -> WritingState: - print(f"\n📝 组装完整文章") - model= create_model() - prompt = f"""请将以下章节内容整合为一篇完整的文章: - - 标题:{state['outline'].title} - 引言:{state['outline'].introduction} - - 章节内容: - {chr(10).join([f'章节 {i+1}:\n{content}' for i, content in enumerate(state['sections_content'])])} - - 结论:{state['outline'].conclusion} - - 要求: - 1. 保持逻辑连贯 - 2. 使用过渡句连接各部分 - 3. 确保语言流畅且无语法错误 - - 请开始组装文章。 - """ - full_article = model.invoke(prompt) - state['full_article'] = full_article - print(f"✅ 文章组装完成") - return state - -def evaluate_quality(state: WritingState) -> WritingState: - print(f"\n🔍 评估文章质量") - model= create_model() - structured_model=model.with_structured_output(QualityScore) - prompt = f"""请根据以下标准评估文章质量: - - 文章内容: - {state['full_article']} - - 评估标准: - 1. 连贯性(0-10) - 2. 相关性(0-10) - 3. 语法正确性(0-10) - 4. 整体质量(0-10) - - 请提供评分和改进建议(如有)。 - """ - quality_score=structured_model.invoke(prompt) - state['quality_score'] = quality_score - - print(f"✅ 质量评估完成") - print(f" 整体评分: {quality_score.overall}/10") - return state - -def human_review(state: WritingState) -> WritingState: - print(f"\n🧑‍💼 等待人工审核...") - print(f"请审核以下文章内容,并提供反馈意见:\n") - print(f"标题: {state['outline'].title}\n") - print(state['full_article']) - feedback = input("\n请输入您的反馈意见(或按回车跳过): ") - state['human_feedback'] = feedback - state['approved'] = feedback.strip() == "" - if state['approved']: - print("✅ 文章已批准,无需修改。") - else: - print("❗ 文章未批准,需根据反馈进行修改。") - return state - -def revise_article(state: WritingState) -> WritingState: - print(f"\n🔄 根据反馈修改文章") - model= create_model() - prompt = f"""请根据以下反馈意见修改文章内容: - - 原文章内容: - {state['full_article']} - - 反馈意见: - {state['human_feedback']} - - 请对文章进行相应修改。 - """ - revised_article = model.invoke(prompt) - state['full_article'] = revised_article - state['revision_count'] += 1 - print(f"✅ 文章修改完成") - return state - -def save_article(state: WritingState): - timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - filename = f"article_{timestamp}.txt" - with open(filename, "w", encoding="utf-8") as f: - f.write(f"标题: {state['outline'].title}\n\n") - f.write(state['full_article']) - print(f"\n💾 文章已保存为 {filename}") - -def check_quality(state: WritingState) -> str: - score = state['quality_score'].overall - if score>=8: - print("✅ 文章质量符合要求。") - return "review" - else: - print("❗ 文章质量不符合要求,需要修改。") - return "revise" - -def check_approval(state: WritingState) -> str: - if state['approved']: - print("✅ 文章已获得批准。") - return "save" - else: - print("❗ 文章未获批准,需要修改。") - return "revise" - -def create_writing_workflow(): - workflow=StateGraph(WritingState) - workflow.add_node("plan", plan_outline) - workflow.add_node("write", write_sections) - workflow.add_node("assemble", assemble_article) - workflow.add_node("evaluate", evaluate_quality) - workflow.add_node("review", human_review) - workflow.add_node("revise", revise_article) - workflow.add_node("save", save_article) - - workflow.set_entry_point("plan") - workflow.add_edge("plan", "write") - workflow.add_edge("write", "assemble") - workflow.add_edge("assemble", "evaluate") - - workflow.add_conditional_edges( - "evaluate", - check_quality, - { - "review": "review", - "revise": "revise" - } - ) - workflow.add_conditional_edges( - "review", - check_approval, - { - "save": "save", - "revise": "revise" - } - ) - - workflow.add_edge("revise","assemble") - workflow.add_edge("save", END) - return workflow.compile() - -def main(): - app=create_writing_workflow() - topics=[ - "人工智能在医疗领域的应用", - "气候变化对全球生态系统的影响", - "远程办公的利与弊分析" - ] - for topic in topics: - print(f"\n================ 开始撰写新文章: {topic} ================\n") - initial_state: WritingState={ - "topic": topic, - "outline": None, - "sections_content": [], - "full_article": "", - "quality_score": None, - "revision_count": 0, - "approved": False, - "human_feedback": "" - } - result=app.invoke(initial_state) - - print(f"\n\n" + "="*70) - print("✅ 写作完成!") - print("="*70) - print(f"主题: {result['topic']}") - print(f"标题: {result['outline'].title}") - print(f"质量评分: {result['quality_score'].overall}/10") - print(f"修订次数: {result['revision_count']}") - print(f"状态: {'已发布' if result['approved'] else '待处理'}") - - input("\n按 Enter 继续下一篇...") - print(f"\n================ 文章撰写完成: {topic} ================\n") - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/langchain-study/08.ipynb b/langchain-study/08.ipynb deleted file mode 100644 index a6219e1..0000000 --- a/langchain-study/08.ipynb +++ /dev/null @@ -1,129 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "e5006f3a", - "metadata": {}, - "outputs": [], - "source": [ - "from dotenv import load_dotenv\n", - "import os\n", - "load_dotenv()\n", - "API_KEY=os.getenv(\"ARK_API_KEY\")\n", - "MODEL=os.getenv(\"MODEL\")\n", - "BASE_URL=os.getenv(\"BASE_URL\")\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4ef5635f", - "metadata": {}, - "outputs": [], - "source": [ - "from langchain.agents import create_agent\n", - "from langchain.agents.middleware import before_model, after_model, AgentState\n", - "from langchain.chat_models import init_chat_model\n", - "from langgraph.runtime import Runtime\n", - "model=init_chat_model(\n", - " model=MODEL,\n", - " model_provider=\"openai\",\n", - " api_key=API_KEY,\n", - " base_url=BASE_URL,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "6b76b61b", - "metadata": {}, - "outputs": [], - "source": [ - "import asyncio\n", - "from langsmith import traceable\n", - "# @traceable(run_type=\"llm\")\n", - "# def process_sequentially(queries:list):\n", - "# res=[]\n", - "# for query in queries:\n", - "# response=model.invoke(query)\n", - "# res.append(response)\n", - "# return res\n", - "# @traceable(run_type=\"llm\")\n", - "# def process_batch(queries:list):\n", - "# return model.batch(queries)\n", - "@traceable(run_type=\"llm\")\n", - "async def process_async(queries:list):\n", - " tasks=[model.ainvoke(query) for query in queries]\n", - " results=await asyncio.gather(*tasks)\n", - " return [r.content for r in results]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "bc8c6dfa", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "异步处理:\n", - "耗时: 5.06秒\n" - ] - } - ], - "source": [ - "import time\n", - "queries = [f\"问题{i}\" for i in range(10)]\n", - "\n", - "# print(\"串行处理:\")\n", - "# start = time.time()\n", - "# process_sequentially(queries)\n", - "# print(f\"耗时: {time.time() - start:.2f}秒\\n\")\n", - "\n", - "# print(\"批处理:\")\n", - "# start = time.time()\n", - "# process_batch(queries)\n", - "# print(f\"耗时: {time.time() - start:.2f}秒\\n\")\n", - "print(\"异步处理:\")\n", - "start = time.time()\n", - "# In Colab, an event loop is usually running already. Direct `await` is preferred.\n", - "await process_async(queries)\n", - "print(f\"耗时: {time.time() - start:.2f}秒\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c25f4943", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "langchain", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.2" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/langchain-study/08.py b/langchain-study/08.py deleted file mode 100644 index 104b57f..0000000 --- a/langchain-study/08.py +++ /dev/null @@ -1,35 +0,0 @@ -import os -from langchain.agents import create_agent -from langchain.chat_models import init_chat_model -from dotenv import load_dotenv - -load_dotenv() -API_KEY=os.getenv("ARK_API_KEY") -MODEL=os.getenv("MODEL") -BASE_URL=os.getenv("BASE_URL") - -os.environ["LANGCHAIN_TRACING_V2"] = "true" -os.environ["LANGCHAIN_API_KEY"] = os.getenv("LANGCHAIN_API_KEY") -os.environ["LANGCHAIN_PROJECT"] = "superagent" -os.environ["LANGCHAIN_ENDPOINT"] = "https://api.smith.langchain.com" - -def get_weather(city: str) -> str: - """Get weather for a given city.""" - return f"It's always sunny in {city}!" - -model=init_chat_model( - model=MODEL, - model_provider="openai", - api_key=API_KEY, - base_url=BASE_URL, -) -agent = create_agent( - model=model, - tools=[get_weather], - system_prompt="You are a helpful assistant", -) - -# Run the agent -agent.invoke( - {"messages": [{"role": "user", "content": "What is the weather in San Francisco?"}]} -) \ No newline at end of file diff --git a/langchain-study/09.py b/langchain-study/09.py deleted file mode 100644 index 7e7f275..0000000 --- a/langchain-study/09.py +++ /dev/null @@ -1,152 +0,0 @@ -import os -import requests -from langchain_community.document_loaders import BiliBiliLoader -from langchain_classic.chains.query_constructor.schema import AttributeInfo -from langchain_classic.retrievers import SelfQueryRetriever -from langchain_community.vectorstores import Chroma -from langchain.chat_models import init_chat_model -import logging -from dotenv import load_dotenv -from rich import traceback -# from embedding import EmbeddingService - -load_dotenv() -logging.basicConfig(level=logging.INFO) -traceback.install() - - -class SimpleEmbeddings: - def __init__(self): - self.base_url = os.getenv("BASE_URL") - self.embedder = os.getenv("EMBEDDER") - self.api_key = os.getenv("ARK_API_KEY") - - def embed_documents(self, texts): - clean = [str(t) for t in texts] - payload = {"model": self.embedder, "input": clean, "encoding_format": "float"} - headers = { - "Authorization": f"Bearer {self.api_key}", - "Content-Type": "application/json", - } - resp = requests.post(f"{self.base_url}/embeddings", json=payload, headers=headers, timeout=30) - resp.raise_for_status() - data = resp.json() - if "data" not in data: - raise RuntimeError(f"embedder 响应缺少 data 字段: {data}") - return [item["embedding"] for item in data["data"]] - - def embed_query(self, text): - return self.embed_documents([text])[0] - - - -# 1. 初始化视频数据 -video_urls = [ - "https://www.bilibili.com/video/BV1Bo4y1A7FU", - "https://www.bilibili.com/video/BV1ug4y157xA", - "https://www.bilibili.com/video/BV1yh411V7ge", -] - -bili = [] -texts = [] -metas = [] -try: - loader = BiliBiliLoader(video_urls=video_urls) - docs = loader.load() - - for doc in docs: - original = doc.metadata - - # 提取基本元数据字段 - metadata = { - 'title': original.get('title', '未知标题'), - 'author': original.get('owner', {}).get('name', '未知作者'), - 'source': original.get('bvid', '未知ID'), - 'view_count': original.get('stat', {}).get('view', 0), - 'length': original.get('duration', 0), - } - - doc.metadata = metadata - doc.page_content = "" # 不使用正文 - bili.append(doc) - - text = f"标题:{metadata['title']} 作者:{metadata['author']} 时长:{metadata['length']}秒 观看:{metadata['view_count']}" - texts.append(text) - metas.append(metadata) - -except Exception as e: - print(f"加载BiliBili视频失败: {str(e)}") - -if not bili: - print("没有成功加载任何视频,程序退出") - exit() - -# 2. 创建向量存储 -embed_model = SimpleEmbeddings() -vectorstore = Chroma.from_texts(texts=texts, embedding=embed_model, metadatas=metas) - -# 3. 配置元数据字段信息 -metadata_field_info = [ - AttributeInfo( - name="title", - description="视频标题(字符串)", - type="string", - ), - AttributeInfo( - name="author", - description="视频作者(字符串)", - type="string", - ), - AttributeInfo( - name="view_count", - description="视频观看次数(整数)", - type="integer", - ), - AttributeInfo( - name="length", - description="视频长度,以秒为单位的整数", - type="integer" - ) -] - -print(bili) -# 4. 创建自查询检索器 -llm = init_chat_model( - model=os.getenv("MODEL"), - model_provider="openai", - api_key=os.getenv("ARK_API_KEY"), - base_url=os.getenv("BASE_URL"), -) - -retriever = SelfQueryRetriever.from_llm( - llm=llm, - vectorstore=vectorstore, - document_contents="记录视频标题、作者、观看次数等信息的视频元数据", - metadata_field_info=metadata_field_info, - enable_limit=True, - verbose=True -) - -# 5. 执行查询示例 -queries = [ - "时间最短的视频", - "时长大于600秒的视频" -] - -for query in queries: - print(f"\n--- 查询: '{query}' ---") - results = retriever.invoke(query) - if results: - for doc in results: - title = doc.metadata.get('title', '未知标题') - author = doc.metadata.get('author', '未知作者') - view_count = doc.metadata.get('view_count', '未知') - length = doc.metadata.get('length', '未知') - print(f"标题: {title}") - print(f"作者: {author}") - print(f"观看次数: {view_count}") - print(f"时长: {length}秒") - print("="*50) - else: - print("未找到匹配的视频") - diff --git a/langchain-study/test.py b/langchain-study/test.py deleted file mode 100644 index 893fa35..0000000 --- a/langchain-study/test.py +++ /dev/null @@ -1,33 +0,0 @@ -from langchain_community.document_loaders import BiliBiliLoader - -video_urls = [ - "https://www.bilibili.com/video/BV1Bo4y1A7FU", - "https://www.bilibili.com/video/BV1ug4y157xA", - "https://www.bilibili.com/video/BV1yh411V7ge", -] - -bili = [] -try: - loader = BiliBiliLoader(video_urls=video_urls) - docs = loader.load() - - for doc in docs: - original = doc.metadata - - # 提取基本元数据字段 - metadata = { - 'title': original.get('title', '未知标题'), - 'author': original.get('owner', {}).get('name', '未知作者'), - 'source': original.get('bvid', '未知ID'), - 'view_count': original.get('stat', {}).get('view', 0), - 'length': original.get('duration', 0), - } - - doc.metadata = metadata - if doc.page_content and doc.page_content.strip(): - doc.page_content = doc.page_content.strip() - bili.append(doc) - print(doc) # For debugging purposes - # print(bili) -except Exception as e: - print(f"加载BiliBili视频失败: {str(e)}") \ No newline at end of file