-
Notifications
You must be signed in to change notification settings - Fork 95
feat: Add legal assistant AgentKit #70
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
36c1fbb
dabfb88
310c9c8
d3af141
a1a3498
ffc59c2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| ASSISTANT_LEGAL_ADVISOR="your-flow-id-here" | ||
| LAMATIC_API_URL="https://your-lamatic-api-url" | ||
| LAMATIC_PROJECT_ID="your-project-id" | ||
| LAMATIC_API_KEY="your-api-key" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. | ||
|
|
||
| # dependencies | ||
| /node_modules | ||
|
|
||
| # next.js | ||
| /.next/ | ||
| /out/ | ||
|
|
||
| # production | ||
| /build | ||
|
|
||
| # debug | ||
| npm-debug.log* | ||
| yarn-debug.log* | ||
| yarn-error.log* | ||
| .pnpm-debug.log* | ||
|
|
||
| # env files | ||
| .env* | ||
| !.env.example | ||
|
|
||
| # vercel | ||
| .vercel | ||
|
|
||
| # typescript | ||
| *.tsbuildinfo | ||
| next-env.d.ts |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,139 @@ | ||
| # Agent Kit Legal Assistant by Lamatic.ai | ||
|
|
||
| **Agent Kit Legal Assistant** is a Next.js starter kit for a legal assistant chatbot built on Lamatic Flows. | ||
|
|
||
| ## What This Kit Does and the Problem It Solves | ||
|
|
||
| Many users have legal questions but do not know where to begin, which legal area applies, or what next step to take. | ||
| This kit provides a structured legal-information assistant that can: | ||
| - Classify the likely legal area from a user question. | ||
| - Return informational summaries with context-aware references when available. | ||
| - Suggest practical next steps and follow-up questions. | ||
| - Always show a legal disclaimer so users understand it is not legal advice. | ||
|
|
||
| It is designed to: | ||
| - Understand user legal questions with optional jurisdiction/context. | ||
| - Return an informational legal summary. | ||
| - Include references to statutes, case law, or source materials when available. | ||
| - Suggest practical next steps. | ||
| - Show a clear disclaimer that responses are not legal advice. | ||
|
|
||
| ## Important Disclaimer | ||
|
|
||
| This project is for informational and educational use only. | ||
| It is not legal advice, does not create an attorney-client relationship, and should not replace consultation with a licensed attorney. | ||
| Do not submit confidential, privileged, or personally identifying information unless you have reviewed and accepted the data retention and logging policies of Lamatic and your configured model provider. | ||
|
|
||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ## Prerequisites and Required Providers | ||
|
|
||
| - Node.js 20.9+ | ||
| - npm 9+ | ||
|
Comment on lines
+29
to
+30
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mission briefing: resolve Node.js version conflict before launch. Line 29 documents 🤖 Prompt for AI Agents |
||
| - Lamatic account and deployed flow | ||
| - Lamatic API key, project ID, and API URL | ||
| - LLM provider configured in Lamatic flow (for example OpenRouter/OpenAI, Anthropic, etc.) | ||
|
|
||
| ## Lamatic Setup (Pre and Post) | ||
|
|
||
| Before running this project, build and deploy your legal flow in Lamatic, then wire its values into this kit. | ||
|
|
||
| Pre: Build in Lamatic | ||
| 1. Sign in at https://lamatic.ai | ||
| 2. Create a project | ||
| 3. Create a legal assistant flow | ||
| 4. Deploy the flow | ||
| 5. Export your flow files (`config.json`, `inputs.json`, `meta.json`, `README.md`) | ||
| 6. Copy your project API values and flow ID | ||
|
|
||
| Post: Wire into this repo | ||
| 1. Create `.env.local` | ||
| 2. Add Lamatic credentials and flow ID | ||
| 3. Install and run locally | ||
|
|
||
| ## Required Environment Variables | ||
|
|
||
| Create `.env.local`: | ||
|
|
||
| ```bash | ||
| ASSISTANT_LEGAL_ADVISOR="your-flow-id-here" | ||
| LAMATIC_API_URL="https://your-lamatic-api-url" | ||
| LAMATIC_PROJECT_ID="your-project-id" | ||
| LAMATIC_API_KEY="your-api-key" | ||
| ``` | ||
|
|
||
| ## Setup and Run Instructions | ||
|
|
||
| ```bash | ||
| cd kits/assistant/legal | ||
| npm install | ||
| npm run dev | ||
| # Open http://localhost:3000 | ||
| ``` | ||
|
|
||
| ## Live Preview | ||
|
|
||
| https://legal-drab-three.vercel.app/ | ||
|
|
||
| ## Execution Paths | ||
|
|
||
| - Client widget path: the bundled `flows/assistant-legal-advisor/config.json` uses a Chat Widget trigger and works with `components/legal-ask-widget.tsx`. | ||
| - Server orchestration path: `actions/orchestrate.ts#getLegalGuidance` expects an API Request trigger flow. Replace the bundled flow export with an API trigger flow if you want server-side `executeFlow` calls. | ||
|
|
||
| ## Usage Examples | ||
|
|
||
| Try prompts like: | ||
| - "My landlord did not return my security deposit in Goa, India. What can I do?" | ||
| - "I was terminated without notice. What steps should I consider?" | ||
| - "A contractor did not complete agreed work after payment in Singapore. What are my options?" | ||
|
|
||
| Expected behavior: | ||
| - Assistant returns an informational response with a disclaimer. | ||
| - Assistant asks for missing jurisdiction details when needed. | ||
| - Assistant suggests practical next steps without claiming to be a lawyer. | ||
|
|
||
| ## Screenshots or GIFs (Optional) | ||
|
|
||
|  | ||
|
|
||
| ## Flow Export Location | ||
|
|
||
| Place your Lamatic export in: | ||
|
|
||
| ```text | ||
| kits/assistant/legal/flows/assistant-legal-advisor/ | ||
| ``` | ||
|
|
||
| If you use a chat-trigger flow, update the flow `domains` allowlist to your actual local and production origins before deployment. | ||
|
|
||
| Expected files: | ||
| - `config.json` | ||
| - `inputs.json` | ||
| - `meta.json` | ||
| - `README.md` | ||
|
|
||
| ## Repo Structure | ||
|
|
||
| ```text | ||
| /actions | ||
| orchestrate.ts # Legal assistant flow execution and output mapping | ||
| /app | ||
| page.tsx # Legal assistant UI | ||
| /components | ||
| header.tsx # Top navigation | ||
| /lib | ||
| lamatic-client.ts # Lamatic SDK client | ||
| /flows | ||
| assistant-legal-advisor/ | ||
| /package.json | ||
| /config.json | ||
| /.env.example | ||
| ``` | ||
|
|
||
| ## Contributing | ||
|
|
||
| Follow the repository contribution guide in [CONTRIBUTING.md](../../../CONTRIBUTING.md). | ||
|
|
||
| Please also follow the community [Code of Conduct](../../../CODE_OF_CONDUCT.md). | ||
|
|
||
| ## License | ||
|
|
||
| MIT License. See [LICENSE](../../../LICENSE). | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,217 @@ | ||
| "use server" | ||
|
|
||
| import { getLamaticClient } from "@/lib/lamatic-client" | ||
| import { config } from "../orchestrate.js" | ||
| import { join } from "node:path" | ||
| import { access, readFile } from "node:fs/promises" | ||
|
|
||
| const DEFAULT_DISCLAIMER = | ||
| "This response is for informational purposes only and is not legal advice. Consult a licensed attorney for advice specific to your situation." | ||
|
|
||
| type LegalAssistantResult = { | ||
| answer: string | ||
| references: string[] | ||
| nextSteps: string[] | ||
| disclaimer: string | ||
| } | ||
|
|
||
| type FlowNode = { | ||
| data?: { | ||
| nodeId?: string | ||
| } | ||
| } | ||
|
|
||
| function toStringArray(value: unknown): string[] { | ||
| if (Array.isArray(value)) { | ||
| return value | ||
| .map((item) => (typeof item === "string" ? item.trim() : String(item).trim())) | ||
| .filter(Boolean) | ||
| } | ||
|
|
||
| if (typeof value === "string") { | ||
| return value | ||
| .split("\n") | ||
| .map((item) => item.replace(/^[-*\d.\s]+/, "").trim()) | ||
| .filter(Boolean) | ||
| } | ||
|
|
||
| return [] | ||
| } | ||
|
|
||
| function extractAnswer(result: Record<string, unknown>): string { | ||
| const candidates = [ | ||
| result.answer, | ||
| result.response, | ||
| result.summary, | ||
| result.legalSummary, | ||
| result.output, | ||
| ] | ||
|
|
||
| for (const candidate of candidates) { | ||
| if (typeof candidate === "string" && candidate.trim()) { | ||
| return candidate.trim() | ||
| } | ||
| } | ||
|
|
||
| return "" | ||
| } | ||
|
|
||
| async function usesChatTriggerExport(): Promise<boolean> { | ||
| try { | ||
| const candidates = [ | ||
| join(process.cwd(), "flows", "assistant-legal-advisor", "config.json"), | ||
| join(process.cwd(), "kits", "assistant", "legal", "flows", "assistant-legal-advisor", "config.json"), | ||
| ] | ||
|
|
||
| let flowConfigPath: string | undefined | ||
| for (const path of candidates) { | ||
| try { | ||
| await access(path) | ||
| flowConfigPath = path | ||
| break | ||
| } catch { | ||
| // Try the next candidate path. | ||
| } | ||
| } | ||
|
|
||
| if (!flowConfigPath) { | ||
| return false | ||
| } | ||
| const raw = await readFile(flowConfigPath, "utf-8") | ||
| const parsed = JSON.parse(raw) as { nodes?: FlowNode[] } | ||
| const nodes = Array.isArray(parsed.nodes) ? parsed.nodes : [] | ||
| return nodes.some((node) => node?.data?.nodeId === "chatTriggerNode") | ||
| } catch { | ||
| return false | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Server-side legal guidance orchestration. | ||
| * This path requires an API Request trigger flow. | ||
| * The bundled flows/assistant-legal-advisor export uses Chat Widget trigger | ||
| * and is intended for client-side widget usage. | ||
| */ | ||
| export async function getLegalGuidance( | ||
| question: string, | ||
| jurisdiction: string, | ||
| context: string, | ||
| ): Promise<{ | ||
| success: boolean | ||
| data?: LegalAssistantResult | ||
| error?: string | ||
| }> { | ||
| try { | ||
| if (await usesChatTriggerExport()) { | ||
| throw new Error( | ||
| "This flow export uses Chat Widget trigger and is not compatible with executeFlow API calls in this kit. Deploy an API Request trigger flow and update ASSISTANT_LEGAL_ADVISOR.", | ||
| ) | ||
| } | ||
|
|
||
| const sanitizedQuestion = question.trim() | ||
| const sanitizedJurisdiction = jurisdiction.trim() | ||
| const sanitizedContext = context.trim() | ||
|
|
||
| if (!sanitizedQuestion) { | ||
| throw new Error("Question is required") | ||
| } | ||
|
|
||
| const flows = config.flows | ||
| const firstFlowKey = Object.keys(flows)[0] | ||
|
|
||
| if (!firstFlowKey) { | ||
| throw new Error("No workflows found in configuration") | ||
| } | ||
|
|
||
| const flow = flows[firstFlowKey as keyof typeof flows] as (typeof flows)[keyof typeof flows] | ||
|
|
||
| if (!flow.workflowId) { | ||
| throw new Error("Workflow not found in config") | ||
| } | ||
|
Comment on lines
+119
to
+130
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agent, your mission: Read the flow ID from environment variables, not config imports. The briefing clearly states kit server actions must read flow IDs from environment variables. Your current approach imports This message will self-destruct if you choose to accept the fix: 🔧 Proposed fix to use environment variable- const flows = config.flows
- const firstFlowKey = Object.keys(flows)[0]
-
- if (!firstFlowKey) {
- throw new Error("No workflows found in configuration")
- }
-
- const flow = flows[firstFlowKey as keyof typeof flows] as (typeof flows)[keyof typeof flows]
-
- if (!flow.workflowId) {
- throw new Error("Workflow not found in config")
- }
+ const workflowId = process.env.ASSISTANT_LEGAL_ADVISOR
+
+ if (!workflowId) {
+ throw new Error("ASSISTANT_LEGAL_ADVISOR environment variable is not set")
+ }Then update line 164: - const response = await lamaticClient.executeFlow(flow.workflowId, workflowInput)
+ const response = await lamaticClient.executeFlow(workflowId, workflowInput)As per coding guidelines: "Kit server action files (e.g., 🤖 Prompt for AI Agents |
||
|
|
||
| const inputAliases: Record<string, unknown> = { | ||
| question: sanitizedQuestion, | ||
| query: sanitizedQuestion, | ||
| userQuery: sanitizedQuestion, | ||
| prompt: sanitizedQuestion, | ||
| jurisdiction: sanitizedJurisdiction, | ||
| region: sanitizedJurisdiction, | ||
| context: sanitizedContext, | ||
| additionalContext: sanitizedContext, | ||
| details: sanitizedContext, | ||
| } | ||
|
|
||
| const workflowInput: Record<string, unknown> = {} | ||
|
|
||
| for (const key of Object.keys(flow.inputSchema || {})) { | ||
| const value = inputAliases[key] | ||
| if (value !== undefined && value !== "") { | ||
| workflowInput[key] = value | ||
| } | ||
| } | ||
|
|
||
| if (Object.keys(workflowInput).length === 0) { | ||
| workflowInput.question = sanitizedQuestion | ||
| if (sanitizedJurisdiction) { | ||
| workflowInput.jurisdiction = sanitizedJurisdiction | ||
| } | ||
| if (sanitizedContext) { | ||
| workflowInput.context = sanitizedContext | ||
| } | ||
| } | ||
|
Comment on lines
+144
to
+161
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Agent, secondary briefing on input mapping. Once you switch to reading the flow ID from 🤖 Prompt for AI Agents |
||
|
|
||
| const lamaticClient = getLamaticClient() | ||
| const response = await lamaticClient.executeFlow(flow.workflowId, workflowInput) | ||
|
|
||
| if (response?.status === "error") { | ||
| throw new Error(response?.message || "Lamatic flow returned an error") | ||
| } | ||
|
|
||
| const result = (response?.result ?? {}) as Record<string, unknown> | ||
|
|
||
| const answer = extractAnswer(result) | ||
| if (!answer) { | ||
| throw new Error("No answer found in flow response") | ||
| } | ||
|
|
||
| const references = toStringArray( | ||
| result.references ?? result.citations ?? result.sources ?? result.statutes ?? result.caseLaws, | ||
| ) | ||
| const nextSteps = toStringArray(result.nextSteps ?? result.recommendedActions ?? result.actions ?? result.steps) | ||
|
|
||
| const disclaimer = | ||
| typeof result.disclaimer === "string" && result.disclaimer.trim() | ||
| ? result.disclaimer.trim() | ||
| : DEFAULT_DISCLAIMER | ||
|
|
||
| return { | ||
| success: true, | ||
| data: { | ||
| answer, | ||
| references, | ||
| nextSteps, | ||
| disclaimer, | ||
| }, | ||
| } | ||
| } catch (error) { | ||
| let errorMessage = "Unknown error occurred" | ||
|
|
||
| if (error instanceof Error) { | ||
| errorMessage = error.message | ||
| if (error.message.includes("fetch failed")) { | ||
| errorMessage = | ||
| "Network error: Unable to connect to Lamatic service. Check your internet and try again." | ||
| } else if (error.message.toLowerCase().includes("timed out")) { | ||
| errorMessage = | ||
| "Lamatic request timed out. Your flow may be configured as Chat Widget trigger or long-running sync flow. Use an API Request trigger or async flow/polling for this kit." | ||
| } else if (error.message.includes("API key")) { | ||
| errorMessage = "Authentication error: Check your Lamatic API configuration." | ||
| } | ||
| } | ||
|
|
||
| return { | ||
| success: false, | ||
| error: errorMessage, | ||
| } | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.